From 9415e4736ba9e9291544efdd5f721d46c7d6b36f Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 3 Jan 2025 11:15:03 +0100 Subject: [PATCH 001/144] Make a start on defineConfig, meta and story factory --- .../react/src/csf-factories.test.tsx | 20 ++++++ code/renderers/react/src/preview.tsx | 63 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 code/renderers/react/src/csf-factories.test.tsx create mode 100644 code/renderers/react/src/preview.tsx diff --git a/code/renderers/react/src/csf-factories.test.tsx b/code/renderers/react/src/csf-factories.test.tsx new file mode 100644 index 000000000000..4b9862963410 --- /dev/null +++ b/code/renderers/react/src/csf-factories.test.tsx @@ -0,0 +1,20 @@ +import { test } from 'vitest'; + +import { Button } from './__test__/Button'; +import { defineConfig } from './preview'; + +test('csf factories', () => { + const config = defineConfig({ + addons: [ + { + decorators: [], + }, + ], + }); + + const meta = config.meta({ component: Button, args: { primary: true } }); + + const MyStory = meta.story({ + args: {}, + }); +}); diff --git a/code/renderers/react/src/preview.tsx b/code/renderers/react/src/preview.tsx new file mode 100644 index 000000000000..e4f9b1367e54 --- /dev/null +++ b/code/renderers/react/src/preview.tsx @@ -0,0 +1,63 @@ +import type { ComponentProps, ComponentType } from 'react'; + +import { composeConfigs } from 'storybook/internal/preview-api'; +import { prepareStory } from 'storybook/internal/preview-api'; +import type { NormalizedProjectAnnotations } from 'storybook/internal/types'; + +import type { + Args, + ComponentAnnotations, + ProjectAnnotations, + Renderer, + StoryAnnotations, +} from '@storybook/csf'; + +import type { ReactRenderer } from './types'; + +export function defineConfig(config: PreviewConfigData) { + return new PreviewConfig(config); +} + +interface PreviewConfigData { + addons: ProjectAnnotations[]; +} + +class PreviewConfig { + readonly annotations: NormalizedProjectAnnotations; + + constructor(data: PreviewConfigData) { + const { addons, ...rest } = data; + this.annotations = composeConfigs([rest, ...addons]); + } + + readonly meta = , TMetaArgs extends Args>( + meta: ComponentAnnotations & { component: TComponent; args: TMetaArgs } + ) => { + return new Meta(meta as ComponentAnnotations, this); + }; +} + +class Meta { + readonly annotations: ComponentAnnotations; + + readonly config: PreviewConfig; + + constructor( + annotations: ComponentAnnotations, + config: PreviewConfig + ) { + this.annotations = annotations; + this.config = config; + } + + readonly story = (story: StoryAnnotations) => + new Story(story, this, this.config); +} + +class Story { + constructor( + public annotations: StoryAnnotations, + public meta: Meta, + public config: PreviewConfig + ) {} +} From 66c8234e4606066594f929e5f6e0fab6f18345a2 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Fri, 3 Jan 2025 17:53:12 +0800 Subject: [PATCH 002/144] CsfFile: Support CSF factories --- code/core/src/csf-tools/CsfFile.test.ts | 339 +++++++++++++++++++++++- code/core/src/csf-tools/CsfFile.ts | 124 ++++++++- 2 files changed, 454 insertions(+), 9 deletions(-) diff --git a/code/core/src/csf-tools/CsfFile.test.ts b/code/core/src/csf-tools/CsfFile.test.ts index d7194edcdf2e..61a1ebbfac10 100644 --- a/code/core/src/csf-tools/CsfFile.test.ts +++ b/code/core/src/csf-tools/CsfFile.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it, vi } from 'vitest'; import yaml from 'js-yaml'; import { dedent } from 'ts-dedent'; -import { type CsfOptions, formatCsf, isModuleMock, loadCsf } from './CsfFile'; +import { type CsfOptions, formatCsf, isModuleMock, isValidPreviewPath, loadCsf } from './CsfFile'; expect.addSnapshotSerializer({ print: (val: any) => yaml.dump(val).trimEnd(), @@ -37,6 +37,7 @@ describe('CsfFile', () => { const parsed = loadCsf(code, { makeTitle }).parse(); expect(Object.keys(parsed._stories)).toEqual(['validStory']); }); + it('filters out non-story exports', () => { const code = ` export default { title: 'foo/bar', excludeStories: ['invalidStory'] }; @@ -48,12 +49,13 @@ describe('CsfFile', () => { const parsed = loadCsf(code, { makeTitle }).parse(); expect(Object.keys(parsed._stories)).toEqual(['A', 'B']); }); + it('transforms inline default exports to constant declarations', () => { expect( transform( dedent` - export default { title: 'foo/bar' }; - `, + export default { title: 'foo/bar' }; + `, { transformInlineMeta: true } ) ).toMatchInlineSnapshot(` @@ -2061,6 +2063,318 @@ describe('CsfFile', () => { `); }); }); + + describe('csf factories', () => { + describe('normal', () => { + it('meta variable', () => { + expect( + parse( + dedent` + import { config } from '#.storybook/preview' + const meta = config.meta({ component: 'foo' }); + export const A = meta.story({}) + export const B = meta.story({}) + ` + ) + ).toMatchInlineSnapshot(` + meta: + component: '''foo''' + title: Default Title + stories: + - id: default-title--a + name: A + __stats: + play: false + render: false + loaders: false + beforeEach: false + globals: false + storyFn: false + mount: false + moduleMock: false + - id: default-title--b + name: B + __stats: + play: false + render: false + loaders: false + beforeEach: false + globals: false + storyFn: false + mount: false + moduleMock: false + `); + }); + + it('meta variable with renamed factory', () => { + expect( + parse( + dedent` + import { boo as moo } from '#.storybook/preview' + const meta = moo.meta({ component: 'foo' }); + export const A = meta.story({}) + ` + ) + ).toMatchInlineSnapshot(` + meta: + component: '''foo''' + title: Default Title + stories: + - id: default-title--a + name: A + __stats: + play: false + render: false + loaders: false + beforeEach: false + globals: false + storyFn: false + mount: false + moduleMock: false + `); + }); + + it('meta default export', () => { + expect( + parse( + dedent` + import { config } from '#.storybook/preview' + const meta = config.meta({ component: 'foo' }); + export default meta; + export const A = meta.story({}) + export const B = meta.story({}) + ` + ) + ).toMatchInlineSnapshot(` + meta: + component: '''foo''' + title: Default Title + stories: + - id: default-title--a + name: A + __stats: + play: false + render: false + loaders: false + beforeEach: false + globals: false + storyFn: false + mount: false + moduleMock: false + - id: default-title--b + name: B + __stats: + play: false + render: false + loaders: false + beforeEach: false + globals: false + storyFn: false + mount: false + moduleMock: false + `); + }); + + it('story name', () => { + expect( + parse( + dedent` + import { config } from '#.storybook/preview' + const meta = config.meta({ component: 'foo' }); + export const A = meta.story({ name: 'bar'}) + ` + ) + ).toMatchInlineSnapshot(` + meta: + component: '''foo''' + title: Default Title + stories: + - id: default-title--a + name: bar + __stats: + play: false + render: false + loaders: false + beforeEach: false + globals: false + storyFn: false + mount: false + moduleMock: false + `); + }); + + it('Object export with no-args render', () => { + expect( + parse( + dedent` + import { config } from '#.storybook/preview' + const meta = config.meta({ title: 'foo/bar' }); + export const A = meta.story({ + render: () => {} + }) + `, + true + ) + ).toMatchInlineSnapshot(` + meta: + title: foo/bar + stories: + - id: foo-bar--a + name: A + parameters: + __isArgsStory: false + __id: foo-bar--a + __stats: + play: false + render: true + loaders: false + beforeEach: false + globals: false + storyFn: false + mount: false + moduleMock: false + `); + }); + + it('Object export with args render', () => { + expect( + parse( + dedent` + import { config } from '#.storybook/preview' + const meta = config.meta({ title: 'foo/bar' }); + export const A = meta.story({ + render: (args) => {} + }); + `, + true + ) + ).toMatchInlineSnapshot(` + meta: + title: foo/bar + stories: + - id: foo-bar--a + name: A + parameters: + __isArgsStory: true + __id: foo-bar--a + __stats: + play: false + render: true + loaders: false + beforeEach: false + globals: false + storyFn: false + mount: false + moduleMock: false + `); + }); + }); + describe('errors', () => { + it('multiple meta variables', () => { + expect(() => + parse( + dedent` + import { config } from '#.storybook/preview' + const foo = config.meta({ component: 'foo' }); + export const A = foo.story({}) + const bar = config.meta({ component: 'bar' }); + export const B = bar.story({}) + ` + ) + ).toThrowErrorMatchingInlineSnapshot(` + [MultipleMetaError: CSF: multiple meta objects (line 4, col 24) + + More info: https://storybook.js.org/docs/writing-stories#default-export] + `); + }); + + it('default export and meta', () => { + expect(() => + parse( + dedent` + import { config } from '#.storybook/preview' + export default { title: 'atoms/foo' }; + const meta = config.meta({ component: 'foo' }); + export const A = meta.story({}) + export const B = meta.story({}) + ` + ) + ).toThrowErrorMatchingInlineSnapshot(` + [MultipleMetaError: CSF: multiple meta objects (line 3, col 25) + + More info: https://storybook.js.org/docs/writing-stories#default-export] + `); + }); + + it('meta and default export', () => { + expect(() => + parse( + dedent` + import { config } from '#.storybook/preview' + const meta = config.meta({ component: 'foo' }); + export default { title: 'atoms/foo' }; + export const A = meta.story({}) + export const B = meta.story({}) + ` + ) + ).toThrowErrorMatchingInlineSnapshot(` + [MultipleMetaError: CSF: multiple meta objects + + More info: https://storybook.js.org/docs/writing-stories#default-export] + `); + }); + + it('bad preview import', () => { + expect(() => + parse( + dedent` + import { config } from '#.storybook/bad-preview' + const meta = config.meta({ component: 'foo' }); + export const A = meta.story({}) + ` + ) + ).toThrowErrorMatchingInlineSnapshot(` + [BadMetaError: CSF: meta() factory must be imported from .storybook/preview configuration (line 1, col 0) + + More info: https://storybook.js.org/docs/writing-stories#default-export] + `); + }); + + it('mixed factories and non-factories', () => { + expect(() => + parse( + dedent` + import { config } from '#.storybook/preview' + const meta = config.meta({ component: 'foo' }); + export const A = meta.story({}) + export const B = {} + ` + ) + ).toThrowErrorMatchingInlineSnapshot(` + [MixedFactoryError: CSF: expected factory story (line 4, col 17) + + More info: https://storybook.js.org/docs/writing-stories#default-export] + `); + }); + + it('factory stories in non-factory file', () => { + expect(() => + parse( + dedent` + import { meta } from 'somewhere'; + export default { title: 'atoms/foo' }; + export const A = {} + export const B = meta.story({}) + ` + ) + ).toThrowErrorMatchingInlineSnapshot(` + [MixedFactoryError: CSF: expected non-factory story (line 4, col 28) + + More info: https://storybook.js.org/docs/writing-stories#default-export] + `); + }); + }); + }); }); describe('isModuleMock', () => { @@ -2082,3 +2396,22 @@ describe('isModuleMock', () => { expect(isModuleMock('#foo.mock.test.ts')).toBe(false); }); }); + +describe('isValidPreviewPath', () => { + it.each([ + ['#.storybook/preview', true], + ['../../.storybook/preview', true], + ['/path/to/.storybook/preview', true], + ['./preview', true], + ['./preview.ts', true], + ['./preview.tsx', true], + ['./preview.js', true], + ['./preview.jsx', true], + ['./preview.mjs', true], + ['foo', false], + ['#.storybook/bad-preview', false], + ['preview', false], + ])('isValidPreviewPath("%s") === %s', (path, expected) => { + expect(isValidPreviewPath(path)).toBe(expected); + }); +}); diff --git a/code/core/src/csf-tools/CsfFile.ts b/code/core/src/csf-tools/CsfFile.ts index b953395c9abc..bc311164ba26 100644 --- a/code/core/src/csf-tools/CsfFile.ts +++ b/code/core/src/csf-tools/CsfFile.ts @@ -40,6 +40,9 @@ interface BabelFile { code: string; } +const PREVIEW_FILE_REGEX = /\/preview(.(js|jsx|mjs|ts|tsx))?$/; +export const isValidPreviewPath = (filepath: string) => PREVIEW_FILE_REGEX.test(filepath); + function parseIncludeExclude(prop: t.Node) { if (t.isArrayExpression(prop)) { return prop.elements.map((e) => { @@ -75,8 +78,12 @@ function parseTags(prop: t.Node) { } const formatLocation = (node: t.Node, fileName?: string) => { - const { line, column } = node.loc?.start || {}; - return `${fileName || ''} (line ${line}, col ${column})`.trim(); + let loc = ''; + if (node.loc) { + const { line, column } = node.loc?.start || {}; + loc = `(line ${line}, col ${column})`; + } + return `${fileName || ''} ${loc}`.trim(); }; export const isModuleMock = (importPath: string) => MODULE_MOCK_REGEX.test(importPath); @@ -171,9 +178,46 @@ export interface CsfOptions { export class NoMetaError extends Error { constructor(message: string, ast: t.Node, fileName?: string) { + const msg = ``.trim(); + super(dedent` + CSF: ${message} ${formatLocation(ast, fileName)} + + More info: https://storybook.js.org/docs/writing-stories#default-export + `); + this.name = this.constructor.name; + } +} + +export class MultipleMetaError extends Error { + constructor(message: string, ast: t.Node, fileName?: string) { + const msg = `${message} ${formatLocation(ast, fileName)}`.trim(); super(dedent` CSF: ${message} ${formatLocation(ast, fileName)} + + More info: https://storybook.js.org/docs/writing-stories#default-export + `); + this.name = this.constructor.name; + } +} +export class MixedFactoryError extends Error { + constructor(message: string, ast: t.Node, fileName?: string) { + const msg = `${message} ${formatLocation(ast, fileName)}`.trim(); + super(dedent` + CSF: ${message} ${formatLocation(ast, fileName)} + + More info: https://storybook.js.org/docs/writing-stories#default-export + `); + this.name = this.constructor.name; + } +} + +export class BadMetaError extends Error { + constructor(message: string, ast: t.Node, fileName?: string) { + const msg = ``.trim(); + super(dedent` + CSF: ${message} ${formatLocation(ast, fileName)} + More info: https://storybook.js.org/docs/writing-stories#default-export `); this.name = this.constructor.name; @@ -217,6 +261,8 @@ export class CsfFile { _metaVariableName: string | undefined; + _metaIsFactory: boolean | undefined; + _storyStatements: Record = {}; _storyAnnotations: Record> = {}; @@ -263,6 +309,10 @@ export class CsfFile { } _parseMeta(declaration: t.ObjectExpression, program: t.Program) { + if (this._metaNode) { + throw new MultipleMetaError('multiple meta objects', declaration, this._options.fileName); + } + this._metaNode = declaration; const meta: StaticMeta = {}; (declaration.properties as t.ObjectProperty[]).forEach((p) => { if (t.isIdentifier(p.key)) { @@ -339,6 +389,17 @@ export class CsfFile { const { node, parent } = path; const isVariableReference = t.isIdentifier(node.declaration) && t.isProgram(parent); + /** + * Transform inline default exports into a constant declaration as it is needed for the + * Vitest plugin to compose stories using CSF1 through CSF3 should not be needed at all + * once we move to CSF4 entirely + * + * `export default {};` + * + * Becomes + * + * `const _meta = {}; export default _meta;` + */ if ( self._options.transformInlineMeta && !isVariableReference && @@ -354,6 +415,10 @@ export class CsfFile { // Preserve sourcemaps location nodes.forEach((_node: t.Node) => (_node.loc = path.node.loc)); path.replaceWithMultiple(nodes); + + // This is a bit brittle because it assumes that we will hit the inserted default export + // as the traversal continues. + return; } let metaNode: t.ObjectExpression | undefined; @@ -390,8 +455,7 @@ export class CsfFile { metaNode = decl.expression; } - if (!self._meta && metaNode && t.isProgram(parent)) { - self._metaNode = metaNode; + if (metaNode && t.isProgram(parent)) { self._parseMeta(metaNode, parent); } @@ -416,6 +480,7 @@ export class CsfFile { // export const X = ...; declarations.forEach((decl: t.VariableDeclarator | t.FunctionDeclaration) => { if (t.isIdentifier(decl.id)) { + let storyIsFactory = false; const { name: exportName } = decl.id; if (exportName === '__namedExportsOrder' && t.isVariableDeclarator(decl)) { self._namedExportsOrder = parseExportsOrder(decl.init as t.Expression); @@ -440,6 +505,28 @@ export class CsfFile { } else { storyNode = decl; } + if ( + t.isCallExpression(storyNode) && + t.isMemberExpression(storyNode.callee) && + t.isIdentifier(storyNode.callee.property) && + storyNode.callee.property.name === 'story' + ) { + storyIsFactory = true; + storyNode = storyNode.arguments[0]; + } + if (self._metaIsFactory && !storyIsFactory) { + throw new MixedFactoryError( + 'expected factory story', + storyNode as t.Node, + self._options.fileName + ); + } else if (!self._metaIsFactory && storyIsFactory) { + throw new MixedFactoryError( + 'expected non-factory story', + storyNode as t.Node, + self._options.fileName + ); + } const parameters: { [key: string]: any } = {}; if (t.isObjectExpression(storyNode)) { parameters.__isArgsStory = true; // assume default render is an args story @@ -508,7 +595,7 @@ export class CsfFile { metaNode = decl.expression; } - if (!self._meta && metaNode && t.isProgram(parent)) { + if (metaNode && t.isProgram(parent)) { self._parseMeta(metaNode, parent); } } else { @@ -570,7 +657,8 @@ export class CsfFile { }, }, CallExpression: { - enter({ node }) { + enter(path) { + const { node } = path; const { callee } = node; if (t.isIdentifier(callee) && callee.name === 'storiesOf') { throw new Error(dedent` @@ -579,6 +667,30 @@ export class CsfFile { SB8 does not support \`storiesOf\`. `); } + if ( + t.isMemberExpression(callee) && + t.isIdentifier(callee.property) && + callee.property.name === 'meta' && + t.isIdentifier(callee.object) && + node.arguments.length > 0 + ) { + const configCandidate = path.scope.getBinding(callee.object.name); + const configParent = configCandidate?.path?.parentPath?.node; + if (t.isImportDeclaration(configParent)) { + if (isValidPreviewPath(configParent.source.value)) { + const metaNode = node.arguments[0] as t.ObjectExpression; + self._metaVariableName = callee.object.name; + self._metaIsFactory = true; + self._parseMeta(metaNode, self._ast.program); + } else { + throw new BadMetaError( + 'meta() factory must be imported from .storybook/preview configuration', + configParent, + self._options.fileName + ); + } + } + } }, }, ImportDeclaration: { From 9fc6fbe57d0e5d4614d6a5480ececab5b148b9be Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 3 Jan 2025 11:52:37 +0100 Subject: [PATCH 003/144] Try it out in Button.stories.tsx --- code/.storybook/preview.tsx | 7 +++++++ .../components/Button/Button.stories.tsx | 5 +++-- code/renderers/react/package.json | 9 +++++++++ code/renderers/react/src/preview.tsx | 14 ++++++-------- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index 23f95a0c5d5e..5ef325bde62b 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -17,6 +17,7 @@ import { import { DocsContext } from '@storybook/blocks'; import { global } from '@storybook/global'; import type { Decorator, Loader, ReactRenderer } from '@storybook/react'; +import { defineConfig } from '@storybook/react/preview'; import { DocsPageWrapper } from '../lib/blocks/src/components'; import { isChromatic } from './isChromatic'; @@ -361,3 +362,9 @@ export const parameters = { }; export const tags = ['test', 'vitest', '!a11ytest']; + +export const config = defineConfig({ + parameters, + tags, + decorators, +}); diff --git a/code/core/src/components/components/Button/Button.stories.tsx b/code/core/src/components/components/Button/Button.stories.tsx index 93407ce8cc8d..1dad7723d25f 100644 --- a/code/core/src/components/components/Button/Button.stories.tsx +++ b/code/core/src/components/components/Button/Button.stories.tsx @@ -4,13 +4,14 @@ import React from 'react'; import { FaceHappyIcon } from '@storybook/icons'; import type { Meta, StoryObj } from '@storybook/react'; +import { config } from '../../../../../.storybook/preview'; import { Button } from './Button'; -const meta = { +const meta = config.meta({ title: 'Button', component: Button, args: { children: 'Button' }, -} satisfies Meta; +}); export default meta; type Story = StoryObj; diff --git a/code/renderers/react/package.json b/code/renderers/react/package.json index e863f283dc37..7e71f0d8df19 100644 --- a/code/renderers/react/package.json +++ b/code/renderers/react/package.json @@ -25,6 +25,11 @@ "import": "./dist/index.mjs", "require": "./dist/index.js" }, + "./preview": { + "types": "./dist/preview.d.ts", + "import": "./dist/preview.mjs", + "require": "./dist/preview.js" + }, "./experimental-playwright": { "types": "./dist/playwright.d.ts", "import": "./dist/playwright.mjs", @@ -44,6 +49,9 @@ "*": [ "dist/index.d.ts" ], + "preview": [ + "dist/preview.d.ts" + ], "experimental-playwright": [ "dist/playwright.d.ts" ] @@ -116,6 +124,7 @@ "entries": [ "./src/index.ts", "./src/preset.ts", + "./src/preview.tsx", "./src/entry-preview.tsx", "./src/entry-preview-docs.ts", "./src/entry-preview-rsc.tsx", diff --git a/code/renderers/react/src/preview.tsx b/code/renderers/react/src/preview.tsx index e4f9b1367e54..e86f24d0b9f3 100644 --- a/code/renderers/react/src/preview.tsx +++ b/code/renderers/react/src/preview.tsx @@ -1,16 +1,14 @@ -import type { ComponentProps, ComponentType } from 'react'; +import type { ComponentType } from 'react'; import { composeConfigs } from 'storybook/internal/preview-api'; -import { prepareStory } from 'storybook/internal/preview-api'; -import type { NormalizedProjectAnnotations } from 'storybook/internal/types'; - import type { Args, ComponentAnnotations, + NormalizedProjectAnnotations, ProjectAnnotations, Renderer, StoryAnnotations, -} from '@storybook/csf'; +} from 'storybook/internal/types'; import type { ReactRenderer } from './types'; @@ -18,8 +16,8 @@ export function defineConfig(config: PreviewConfigData) { return new PreviewConfig(config); } -interface PreviewConfigData { - addons: ProjectAnnotations[]; +interface PreviewConfigData extends ProjectAnnotations { + addons?: ProjectAnnotations[]; } class PreviewConfig { @@ -27,7 +25,7 @@ class PreviewConfig { constructor(data: PreviewConfigData) { const { addons, ...rest } = data; - this.annotations = composeConfigs([rest, ...addons]); + this.annotations = composeConfigs([rest, ...(addons ?? [])]); } readonly meta = , TMetaArgs extends Args>( From cdda2b55134e5786b006c949a8645eb24a3de29e Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 3 Jan 2025 11:54:14 +0100 Subject: [PATCH 004/144] Ignore eslint --- .../components/Button/Button.stories.tsx | 250 +++++++++--------- 1 file changed, 124 insertions(+), 126 deletions(-) diff --git a/code/core/src/components/components/Button/Button.stories.tsx b/code/core/src/components/components/Button/Button.stories.tsx index 1dad7723d25f..0bf5d216a1f1 100644 --- a/code/core/src/components/components/Button/Button.stories.tsx +++ b/code/core/src/components/components/Button/Button.stories.tsx @@ -7,15 +7,13 @@ import type { Meta, StoryObj } from '@storybook/react'; import { config } from '../../../../../.storybook/preview'; import { Button } from './Button'; +// eslint-disable-next-line storybook/default-exports const meta = config.meta({ title: 'Button', component: Button, args: { children: 'Button' }, }); -export default meta; -type Story = StoryObj; - const Stack = ({ children }: { children: ReactNode }) => (
{children}
); @@ -24,9 +22,9 @@ const Row = ({ children }: { children: ReactNode }) => (
{children}
); -export const Base: Story = {}; +export const Base = meta.story({}); -export const Variants: Story = { +export const Variants = meta.story({ render: (args) => ( @@ -64,125 +62,125 @@ export const Variants: Story = { ), -}; - -export const Active: Story = { - args: { - active: true, - children: ( - <> - - Button - - ), - }, - render: (args) => ( - - - - - ), -}; - -export const Disabled: Story = { - args: { - disabled: true, - children: 'Disabled Button', - }, -}; - -export const WithHref: Story = { - render: () => ( - - - - - ), -}; +}); -export const Animated: Story = { - args: { - variant: 'outline', - }, - render: (args) => ( - - - - - - - - - - - - - - - - - - ), -}; +// export const Active: Story = { +// args: { +// active: true, +// children: ( +// <> +// +// Button +// +// ), +// }, +// render: (args) => ( +// +// +// +// +// ), +// }; +// +// export const Disabled: Story = { +// args: { +// disabled: true, +// children: 'Disabled Button', +// }, +// }; +// +// export const WithHref: Story = { +// render: () => ( +// +// +// +// +// ), +// }; +// +// export const Animated: Story = { +// args: { +// variant: 'outline', +// }, +// render: (args) => ( +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// ), +// }; From 5d05b3cdc6e61289edd0535bb19ea86e74deeb8e Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 3 Jan 2025 11:58:29 +0100 Subject: [PATCH 005/144] Rename Docs, fix later --- .../core/src/components/components/Button/{Docs.mdx => Docs.TODO} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename code/core/src/components/components/Button/{Docs.mdx => Docs.TODO} (100%) diff --git a/code/core/src/components/components/Button/Docs.mdx b/code/core/src/components/components/Button/Docs.TODO similarity index 100% rename from code/core/src/components/components/Button/Docs.mdx rename to code/core/src/components/components/Button/Docs.TODO From 79107a52a465e1e19f32f9b617d8f213ee121c4f Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 3 Jan 2025 12:21:33 +0100 Subject: [PATCH 006/144] Get POC working!! --- .../components/Button/Button.stories.tsx | 1 + .../modules/store/csf/processCSFFile.ts | 20 +++++++++++++++++++ code/renderers/react/src/preview.tsx | 2 ++ 3 files changed, 23 insertions(+) diff --git a/code/core/src/components/components/Button/Button.stories.tsx b/code/core/src/components/components/Button/Button.stories.tsx index 0bf5d216a1f1..7fb5dd4039aa 100644 --- a/code/core/src/components/components/Button/Button.stories.tsx +++ b/code/core/src/components/components/Button/Button.stories.tsx @@ -9,6 +9,7 @@ import { Button } from './Button'; // eslint-disable-next-line storybook/default-exports const meta = config.meta({ + id: 'button-component', title: 'Button', component: Button, args: { children: 'Button' }, diff --git a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts index e0913f88992f..cc7128e90e6b 100644 --- a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts +++ b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts @@ -46,6 +46,26 @@ export function processCSFFile( // eslint-disable-next-line @typescript-eslint/naming-convention const { default: defaultExport, __namedExportsOrder, ...namedExports } = moduleExports; + const firstStory: any = Object.values(namedExports)[0]; + if (!defaultExport && 'isCSFFactory' in firstStory) { + const meta: NormalizedComponentAnnotations = + normalizeComponentAnnotations(firstStory.meta.annotations, title, importPath); + checkDisallowedParameters(meta.parameters); + + const csfFile: CSFFile = { meta, stories: {}, moduleExports }; + + Object.keys(namedExports).forEach((key) => { + if (isExportStory(key, meta)) { + const storyMeta = normalizeStory(key, namedExports[key].annotations, meta); + checkDisallowedParameters(storyMeta.parameters); + + csfFile.stories[storyMeta.id] = storyMeta; + } + }); + + return csfFile; + } + const meta: NormalizedComponentAnnotations = normalizeComponentAnnotations( defaultExport, title, diff --git a/code/renderers/react/src/preview.tsx b/code/renderers/react/src/preview.tsx index e86f24d0b9f3..4a2b84a07c23 100644 --- a/code/renderers/react/src/preview.tsx +++ b/code/renderers/react/src/preview.tsx @@ -58,4 +58,6 @@ class Story { public meta: Meta, public config: PreviewConfig ) {} + + readonly isCSFFactory = true; } From 60c6f269e31a432fc8116fdbbaf45be013fdf4db Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Fri, 3 Jan 2025 19:57:54 +0800 Subject: [PATCH 007/144] ConfigFile: Handle defineConfig typesafe factory --- code/core/src/csf-tools/ConfigFile.test.ts | 81 ++++++++++++++++++++++ code/core/src/csf-tools/ConfigFile.ts | 38 +++++++--- 2 files changed, 108 insertions(+), 11 deletions(-) diff --git a/code/core/src/csf-tools/ConfigFile.test.ts b/code/core/src/csf-tools/ConfigFile.test.ts index e3409d5eb681..7e0f9dc80ead 100644 --- a/code/core/src/csf-tools/ConfigFile.test.ts +++ b/code/core/src/csf-tools/ConfigFile.test.ts @@ -243,6 +243,20 @@ describe('ConfigFile', () => { ).toEqual('bar'); }); }); + + describe('factory config', () => { + it('found scalar', () => { + expect( + getField( + ['core', 'builder'], + dedent` + import { defineConfig } from '@storybook/react-vite/preview'; + export const foo = defineConfig({ core: { builder: 'webpack5' } }); + ` + ) + ).toEqual('webpack5'); + }); + }); }); describe('setField', () => { @@ -479,6 +493,73 @@ describe('ConfigFile', () => { `); }); }); + + describe('factory config', () => { + it('missing export', () => { + expect( + setField( + ['core', 'builder'], + 'webpack5', + dedent` + import { defineConfig } from '@storybook/react-vite/preview'; + export const foo = defineConfig({ + addons: [], + }); + ` + ) + ).toMatchInlineSnapshot(` + import { defineConfig } from '@storybook/react-vite/preview'; + export const foo = defineConfig({ + addons: [], + + core: { + builder: 'webpack5' + } + }); + `); + }); + it('missing field', () => { + expect( + setField( + ['core', 'builder'], + 'webpack5', + dedent` + import { defineConfig } from '@storybook/react-vite/preview'; + export const foo = defineConfig({ + core: { foo: 'bar' }, + }); + ` + ) + ).toMatchInlineSnapshot(` + import { defineConfig } from '@storybook/react-vite/preview'; + export const foo = defineConfig({ + core: { + foo: 'bar', + builder: 'webpack5' + }, + }); + `); + }); + it('found scalar', () => { + expect( + setField( + ['core', 'builder'], + 'webpack5', + dedent` + import { defineConfig } from '@storybook/react-vite/preview'; + export const foo = defineConfig({ + core: { builder: 'webpack4' }, + }); + ` + ) + ).toMatchInlineSnapshot(` + import { defineConfig } from '@storybook/react-vite/preview'; + export const foo = defineConfig({ + core: { builder: 'webpack5' }, + }); + `); + }); + }); }); describe('appendToArray', () => { diff --git a/code/core/src/csf-tools/ConfigFile.ts b/code/core/src/csf-tools/ConfigFile.ts index dc9f973d5ad6..5ee29fc2ca5d 100644 --- a/code/core/src/csf-tools/ConfigFile.ts +++ b/code/core/src/csf-tools/ConfigFile.ts @@ -187,6 +187,20 @@ export class ConfigFile { this.fileName = fileName; } + _parseExportsObject(exportsObject: t.ObjectExpression, parent?: t.Node) { + this._exportsObject = exportsObject; + (exportsObject.properties as t.ObjectProperty[]).forEach((p) => { + const exportName = propKey(p); + if (exportName) { + let exportVal = p.value; + if (t.isIdentifier(exportVal)) { + exportVal = _findVarInitialization(exportVal.name, parent as t.Program) as any; + } + this._exports[exportName] = exportVal as t.Expression; + } + }); + } + parse() { // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; @@ -202,17 +216,7 @@ export class ConfigFile { decl = unwrap(decl); if (t.isObjectExpression(decl)) { - self._exportsObject = decl; - (decl.properties as t.ObjectProperty[]).forEach((p) => { - const exportName = propKey(p); - if (exportName) { - let exportVal = p.value; - if (t.isIdentifier(exportVal)) { - exportVal = _findVarInitialization(exportVal.name, parent as t.Program) as any; - } - self._exports[exportName] = exportVal as t.Expression; - } - }); + self._parseExportsObject(decl, parent); } else { logger.warn( getCsfParsingErrorMessage({ @@ -315,6 +319,18 @@ export class ConfigFile { } }, }, + CallExpression: { + enter: ({ node }) => { + if ( + t.isIdentifier(node.callee) && + node.callee.name === 'defineConfig' && + node.arguments.length === 1 && + t.isObjectExpression(node.arguments[0]) + ) { + self._parseExportsObject(node.arguments[0]); + } + }, + }, }); return self; } From 6b0abf0bfa3b5749af17f8c3f529d56743d91ba7 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 3 Jan 2025 15:40:27 +0100 Subject: [PATCH 008/144] take CSF factories into account in portable stories --- .../modules/store/csf/portable-stories.ts | 8 +- .../src/__test__/Button.csf4.stories.tsx | 288 ++++++++++++++++++ .../portable-stories-factory.test.tsx.snap | 185 +++++++++++ .../portable-stories-factory.test.tsx | 247 +++++++++++++++ code/renderers/react/src/preview.tsx | 4 +- 5 files changed, 730 insertions(+), 2 deletions(-) create mode 100644 code/renderers/react/src/__test__/Button.csf4.stories.tsx create mode 100644 code/renderers/react/src/__test__/__snapshots__/portable-stories-factory.test.tsx.snap create mode 100644 code/renderers/react/src/__test__/portable-stories-factory.test.tsx 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 774137dcf85a..c843826609eb 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 @@ -275,7 +275,13 @@ export function composeStories( globalConfig: ProjectAnnotations, composeStoryFn: ComposeStoryFn = defaultComposeStory ) { - const { default: meta, __esModule, __namedExportsOrder, ...stories } = storiesImport; + const { default: metaExport, __esModule, __namedExportsOrder, ...stories } = storiesImport; + let meta = metaExport; + const firstStory = Object.values(stories)[0] as any; + if (!meta && 'isCSFFactory' in firstStory) { + meta = firstStory.meta.annotations; + } + const composedStories = Object.entries(stories).reduce((storiesMap, [exportsName, story]) => { if (!isExportStory(exportsName, meta)) { return storiesMap; diff --git a/code/renderers/react/src/__test__/Button.csf4.stories.tsx b/code/renderers/react/src/__test__/Button.csf4.stories.tsx new file mode 100644 index 000000000000..358961add9a5 --- /dev/null +++ b/code/renderers/react/src/__test__/Button.csf4.stories.tsx @@ -0,0 +1,288 @@ +import React, { useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; + +import { expect, fn, mocked, userEvent, within } from '@storybook/test'; + +import { action } from '@storybook/addon-actions'; + +import { defineConfig } from '../preview'; +import { Button } from './Button'; + +// eslint-disable-next-line storybook/default-exports +const config = defineConfig({ args: { children: 'TODO: THIS IS NOT WORKING YET' } }); + +const meta = config.meta({ + id: 'button-component', + title: 'Example/CSF4/Button', + component: Button, + argTypes: { + backgroundColor: { control: 'color' }, + }, + args: {}, +}); + +export const CSF2Secondary = meta.story({ + render: (args) => { + return + + ); + }, + name: 'WithLocale', +}); + +export const CSF2StoryWithParamsAndDecorator = meta.story({ + render: (args: any) => { + return + + ); + }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + await step('Step label', async () => { + const inputEl = canvas.getByTestId('input'); + const buttonEl = canvas.getByRole('button'); + await userEvent.click(buttonEl); + await userEvent.type(inputEl, 'Hello world!'); + + await expect(inputEl).toHaveValue('Hello world!'); + await expect(buttonEl).toHaveTextContent('I am clicked'); + }); + }, +}); + +export const CSF3InputFieldFilled = meta.story({ + render: () => { + return ; + }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + await step('Step label', async () => { + const inputEl = canvas.getByTestId('input'); + await userEvent.type(inputEl, 'Hello world!'); + await expect(inputEl).toHaveValue('Hello world!'); + }); + }, +}); + +const mockFn = fn(); +export const LoaderStory = meta.story({ + args: { + mockFn, + }, + loaders: [ + async () => { + mockFn.mockReturnValueOnce('mockFn return value'); + return { + value: 'loaded data', + }; + }, + ], + render: (args: any & { mockFn: (val: string) => string }, { loaded }) => { + const data = args.mockFn('render'); + return ( +
+
{loaded.value}
+
{String(data)}
+
+ ); + }, + play: async () => { + expect(mockFn).toHaveBeenCalledWith('render'); + }, +}); + +export const MountInPlayFunction = meta.story({ + args: { + mockFn: fn(), + }, + play: async ({ args, mount, context }: any) => { + // equivalent of loaders + const loadedData = await Promise.resolve('loaded data'); + mocked(args.mockFn).mockReturnValueOnce('mockFn return value'); + // equivalent of render + const data = args.mockFn('render'); + // TODO refactor this in the mount args PR + context.originalStoryFn = () => ( +
+
{loadedData}
+
{String(data)}
+
+ ); + await mount(); + + // equivalent of play + expect(args.mockFn).toHaveBeenCalledWith('render'); + }, +}); + +export const MountInPlayFunctionThrow = meta.story({ + play: async () => { + throw new Error('Error thrown in play'); + }, +}); + +export const WithActionArg = meta.story({ + args: { + someActionArg: action('some-action-arg'), + }, + render: (args: any) => { + args.someActionArg('in render'); + return ( + + , + modalContainer + ) + : null; + + return ( + <> + + {modalContent} + + ); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const openModalButton = await canvas.getByRole('button', { name: /open modal/i }); + await userEvent.click(openModalButton); + await expect(within(document.body).getByRole('dialog')).toBeInTheDocument(); + }, +}); diff --git a/code/renderers/react/src/__test__/__snapshots__/portable-stories-factory.test.tsx.snap b/code/renderers/react/src/__test__/__snapshots__/portable-stories-factory.test.tsx.snap new file mode 100644 index 000000000000..3f00ff746281 --- /dev/null +++ b/code/renderers/react/src/__test__/__snapshots__/portable-stories-factory.test.tsx.snap @@ -0,0 +1,185 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Renders CSF2Secondary story 1`] = ` + +
+ +
+ +`; + +exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = ` + +
+ +
+ +`; + +exports[`Renders CSF3Button story 1`] = ` + +
+ +
+ +`; + +exports[`Renders CSF3ButtonWithRender story 1`] = ` + +
+
+

+ I am a custom render function +

+ +
+
+ +`; + +exports[`Renders CSF3InputFieldFilled story 1`] = ` + +
+ +
+ +`; + +exports[`Renders CSF3Primary story 1`] = ` + +
+ +
+ +`; + +exports[`Renders HooksStory story 1`] = ` + +
+ +
+ +
+ +`; + +exports[`Renders LoaderStory story 1`] = ` + +
+
+
+ loaded data +
+
+ mockFn return value +
+
+
+ +`; + +exports[`Renders Modal story 1`] = ` + +
+ +
+ + +`; + +exports[`Renders MountInPlayFunction story 1`] = ` + +
+
+
+ loaded data +
+
+ mockFn return value +
+
+
+ +`; + +exports[`Renders WithActionArg story 1`] = ` + +
+
+ +`; + +exports[`Renders WithActionArgType story 1`] = ` + +
+
+ nothing +
+
+ +`; diff --git a/code/renderers/react/src/__test__/portable-stories-factory.test.tsx b/code/renderers/react/src/__test__/portable-stories-factory.test.tsx new file mode 100644 index 000000000000..7ad6669c2026 --- /dev/null +++ b/code/renderers/react/src/__test__/portable-stories-factory.test.tsx @@ -0,0 +1,247 @@ +// @vitest-environment happy-dom + +/* eslint-disable import/namespace */ +import { cleanup, render, screen } from '@testing-library/react'; +import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; + +import React from 'react'; + +import { addons } from 'storybook/internal/preview-api'; + +import type { ProjectAnnotations } from '@storybook/csf'; +import type { Meta, ReactRenderer } from '@storybook/react'; + +import * as addonActionsPreview from '@storybook/addon-actions/preview'; + +import { expectTypeOf } from 'expect-type'; + +import { composeStories, composeStory, setProjectAnnotations } from '..'; +import type { Button } from './Button'; +import * as ButtonStories from './Button.csf4.stories'; +import * as ComponentWithErrorStories from './ComponentWithError.stories'; + +const HooksStory = composeStory( + ButtonStories.HooksStory, + ButtonStories.CSF3Primary.meta.annotations +); + +const projectAnnotations = setProjectAnnotations([]); + +// example with composeStories, returns an object with all stories composed with args/decorators +const { CSF3Primary, LoaderStory, MountInPlayFunction, MountInPlayFunctionThrow } = + composeStories(ButtonStories); +const { ThrowsError } = composeStories(ComponentWithErrorStories); + +beforeAll(async () => { + await projectAnnotations.beforeAll?.(); +}); + +afterEach(() => { + cleanup(); +}); + +// example with composeStory, returns a single story composed with args/decorators +const Secondary = composeStory( + ButtonStories.CSF2Secondary, + ButtonStories.CSF3Primary.meta.annotations +); +describe('renders', () => { + it('renders primary button', () => { + render(Hello world); + const buttonElement = screen.getByText(/Hello world/i); + expect(buttonElement).not.toBeNull(); + }); + + it('reuses args from composed story', () => { + render(); + const buttonElement = screen.getByRole('button'); + expect(buttonElement.textContent).toEqual(Secondary.args.children); + }); + + it('onclick handler is called', async () => { + const onClickSpy = vi.fn(); + render(); + const buttonElement = screen.getByRole('button'); + buttonElement.click(); + expect(onClickSpy).toHaveBeenCalled(); + }); + + it('reuses args from composeStories', () => { + const { getByText } = render(); + const buttonElement = getByText(/foo/i); + expect(buttonElement).not.toBeNull(); + }); + + it('should throw error when rendering a component with a render error', async () => { + await expect(() => ThrowsError.run()).rejects.toThrowError('Error in render'); + }); + + it('should render component mounted in play function', async () => { + await MountInPlayFunction.run(); + + expect(screen.getByTestId('spy-data').textContent).toEqual('mockFn return value'); + expect(screen.getByTestId('loaded-data').textContent).toEqual('loaded data'); + }); + + it('should throw an error in play function', () => { + expect(() => MountInPlayFunctionThrow.run()).rejects.toThrowError('Error thrown in play'); + }); + + it('should call and compose loaders data', async () => { + await LoaderStory.load(); + const { getByTestId } = render(); + expect(getByTestId('spy-data').textContent).toEqual('mockFn return value'); + expect(getByTestId('loaded-data').textContent).toEqual('loaded data'); + // spy assertions happen in the play function and should work + await LoaderStory.run!(); + }); +}); + +describe('projectAnnotations', () => { + it('renders with default projectAnnotations', () => { + setProjectAnnotations([ + { + parameters: { injected: true }, + globalTypes: { + locale: { defaultValue: 'en' }, + }, + }, + ]); + const WithEnglishText = composeStory( + ButtonStories.CSF2StoryWithLocale, + ButtonStories.CSF3Primary.meta.annotations + ); + const { getByText } = render(); + const buttonElement = getByText('Hello!'); + expect(buttonElement).not.toBeNull(); + expect(WithEnglishText.parameters?.injected).toBe(true); + }); + + it('renders with custom projectAnnotations via composeStory params', () => { + const WithPortugueseText = composeStory( + ButtonStories.CSF2StoryWithLocale, + ButtonStories.CSF3Primary.meta.annotations, + { + initialGlobals: { locale: 'pt' }, + } + ); + const { getByText } = render(); + const buttonElement = getByText('Olá!'); + expect(buttonElement).not.toBeNull(); + }); + + it('has action arg from argTypes when addon-actions annotations are added', () => { + const Story = composeStory( + ButtonStories.WithActionArgType, + ButtonStories.CSF3Primary.meta.annotations, + addonActionsPreview as ProjectAnnotations + ); + expect(Story.args.someActionArg).toHaveProperty('isAction', true); + }); +}); + +describe('CSF3', () => { + it('renders with inferred globalRender', () => { + const Primary = composeStory( + ButtonStories.CSF3Button, + ButtonStories.CSF3Primary.meta.annotations + ); + + render(Hello world); + const buttonElement = screen.getByText(/Hello world/i); + expect(buttonElement).not.toBeNull(); + }); + + it('renders with custom render function', () => { + const Primary = composeStory( + ButtonStories.CSF3ButtonWithRender, + ButtonStories.CSF3Primary.meta.annotations + ); + + render(); + expect(screen.getByTestId('custom-render')).not.toBeNull(); + }); + + it('renders with play function without canvas element', async () => { + const CSF3InputFieldFilled = composeStory( + ButtonStories.CSF3InputFieldFilled, + ButtonStories.CSF3Primary.meta.annotations + ); + await CSF3InputFieldFilled.run(); + + const input = screen.getByTestId('input') as HTMLInputElement; + expect(input.value).toEqual('Hello world!'); + }); + + it('renders with play function with canvas element', async () => { + const CSF3InputFieldFilled = composeStory( + ButtonStories.CSF3InputFieldFilled, + ButtonStories.CSF3Primary.meta.annotations + ); + + const div = document.createElement('div'); + document.body.appendChild(div); + + await CSF3InputFieldFilled.run({ canvasElement: div }); + + const input = screen.getByTestId('input') as HTMLInputElement; + expect(input.value).toEqual('Hello world!'); + + document.body.removeChild(div); + }); + + it('renders with hooks', async () => { + await HooksStory.run(); + + const input = screen.getByTestId('input') as HTMLInputElement; + expect(input.value).toEqual('Hello world!'); + }); +}); + +// common in addons that need to communicate between manager and preview +it('should pass with decorators that need addons channel', () => { + const PrimaryWithChannels = composeStory( + ButtonStories.CSF3Primary, + ButtonStories.CSF3Primary.meta.annotations, + { + decorators: [ + (StoryFn: any) => { + addons.getChannel(); + return StoryFn(); + }, + ], + } + ); + render(Hello world); + const buttonElement = screen.getByText(/Hello world/i); + expect(buttonElement).not.toBeNull(); +}); + +describe('ComposeStories types', () => { + // this file tests Typescript types that's why there are no assertions + it('Should support typescript operators', () => { + type ComposeStoriesParam = Parameters[0]; + + expectTypeOf({ + ...ButtonStories, + default: ButtonStories.CSF3Primary.meta.annotations as Meta, + }).toMatchTypeOf(); + + expectTypeOf({ + ...ButtonStories, + default: ButtonStories.CSF3Primary.meta.annotations satisfies Meta, + }).toMatchTypeOf(); + }); +}); + +const testCases = Object.values(composeStories(ButtonStories)).map( + (Story) => [Story.storyName, Story] as [string, typeof Story] +); +it.each(testCases)('Renders %s story', async (_storyName, Story) => { + if (_storyName === 'CSF2StoryWithLocale' || _storyName === 'MountInPlayFunctionThrow') { + return; + } + + await Story.run(); + expect(document.body).toMatchSnapshot(); +}); diff --git a/code/renderers/react/src/preview.tsx b/code/renderers/react/src/preview.tsx index 4a2b84a07c23..5c1d0a0b4021 100644 --- a/code/renderers/react/src/preview.tsx +++ b/code/renderers/react/src/preview.tsx @@ -57,7 +57,9 @@ class Story { public annotations: StoryAnnotations, public meta: Meta, public config: PreviewConfig - ) {} + ) { + Object.assign(this, annotations); + } readonly isCSFFactory = true; } From b4e6e2586899ced7e74ef997eaa220722ceb12f6 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 3 Jan 2025 17:43:30 +0100 Subject: [PATCH 009/144] take CSF factories into account in the vitest plugin --- .../test/src/vitest-plugin/test-utils.ts | 3 +- .../components/Button/Button.stories.tsx | 1 - code/core/src/csf-tools/CsfFile.ts | 2 +- .../vitest-plugin/transformer.test.ts | 1067 +++++++++++------ 4 files changed, 723 insertions(+), 350 deletions(-) diff --git a/code/addons/test/src/vitest-plugin/test-utils.ts b/code/addons/test/src/vitest-plugin/test-utils.ts index a00ff6d7f6fc..abd6fd0bbdd8 100644 --- a/code/addons/test/src/vitest-plugin/test-utils.ts +++ b/code/addons/test/src/vitest-plugin/test-utils.ts @@ -27,11 +27,12 @@ export const testStory = ( return async (context: TestContext & TaskContext & { story: ComposedStoryFn }) => { const composedStory = composeStory( story, - meta, + 'isCSFFactory' in story ? (meta as any).annotations : meta, { initialGlobals: (await getInitialGlobals?.()) ?? {} }, undefined, exportName ); + if (composedStory === undefined || skipTags?.some((tag) => composedStory.tags.includes(tag))) { context.skip(); } diff --git a/code/core/src/components/components/Button/Button.stories.tsx b/code/core/src/components/components/Button/Button.stories.tsx index 7fb5dd4039aa..2a88929db216 100644 --- a/code/core/src/components/components/Button/Button.stories.tsx +++ b/code/core/src/components/components/Button/Button.stories.tsx @@ -2,7 +2,6 @@ import type { ReactNode } from 'react'; import React from 'react'; import { FaceHappyIcon } from '@storybook/icons'; -import type { Meta, StoryObj } from '@storybook/react'; import { config } from '../../../../../.storybook/preview'; import { Button } from './Button'; diff --git a/code/core/src/csf-tools/CsfFile.ts b/code/core/src/csf-tools/CsfFile.ts index bc311164ba26..5e6eaac9fa29 100644 --- a/code/core/src/csf-tools/CsfFile.ts +++ b/code/core/src/csf-tools/CsfFile.ts @@ -679,7 +679,7 @@ export class CsfFile { if (t.isImportDeclaration(configParent)) { if (isValidPreviewPath(configParent.source.value)) { const metaNode = node.arguments[0] as t.ObjectExpression; - self._metaVariableName = callee.object.name; + self._metaVariableName = callee.property.name; self._metaIsFactory = true; self._parseMeta(metaNode, self._ast.program); } else { diff --git a/code/core/src/csf-tools/vitest-plugin/transformer.test.ts b/code/core/src/csf-tools/vitest-plugin/transformer.test.ts index 5b030ac19c73..b475380df2e4 100644 --- a/code/core/src/csf-tools/vitest-plugin/transformer.test.ts +++ b/code/core/src/csf-tools/vitest-plugin/transformer.test.ts @@ -58,280 +58,653 @@ describe('transformer', () => { }); }); - describe('default exports (meta)', () => { - it('should add title to inline default export if not present', async () => { - const code = ` - export default { - component: Button, - }; - export const Story = {}; - `; + describe('CSF v1/v2/v3', () => { + describe('default exports (meta)', () => { + it('should add title to inline default export if not present', async () => { + const code = ` + export default { + component: Button, + }; + export const Story = {}; + `; - const result = await transform({ code }); + const result = await transform({ code }); - expect(getStoryTitle).toHaveBeenCalled(); + expect(getStoryTitle).toHaveBeenCalled(); - expect(result.code).toMatchInlineSnapshot(` - import { test as _test, expect as _expect } from "vitest"; - import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; - const _meta = { - component: Button, - title: "automatic/calculated/title" - }; - export default _meta; - export const Story = {}; - const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); - if (_isRunningFromThisFile) { - _test("Story", _testStory("Story", Story, _meta, [])); - } - `); + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + const _meta = { + component: Button, + title: "automatic/calculated/title" + }; + export default _meta; + export const Story = {}; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Story", _testStory("Story", Story, _meta, [])); + } + `); + }); + + it('should overwrite title to inline default export if already present', async () => { + const code = ` + export default { + title: 'Button', + component: Button, + }; + export const Story = {}; + `; + + const result = await transform({ code }); + + expect(getStoryTitle).toHaveBeenCalled(); + + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + const _meta = { + title: "automatic/calculated/title", + component: Button + }; + export default _meta; + export const Story = {}; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Story", _testStory("Story", Story, _meta, [])); + } + `); + }); + + it('should add title to const declared default export if not present', async () => { + const code = ` + const meta = { + component: Button, + }; + export default meta; + + export const Story = {}; + `; + + const result = await transform({ code }); + + expect(getStoryTitle).toHaveBeenCalled(); + + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + const meta = { + component: Button, + title: "automatic/calculated/title" + }; + export default meta; + export const Story = {}; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Story", _testStory("Story", Story, meta, [])); + } + `); + }); + + it('should overwrite title to const declared default export if already present', async () => { + const code = ` + const meta = { + title: 'Button', + component: Button, + }; + export default meta; + + export const Story = {}; + `; + + const result = await transform({ code }); + + expect(getStoryTitle).toHaveBeenCalled(); + + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + const meta = { + title: "automatic/calculated/title", + component: Button + }; + export default meta; + export const Story = {}; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Story", _testStory("Story", Story, meta, [])); + } + `); + }); }); - it('should overwrite title to inline default export if already present', async () => { - const code = ` - export default { - title: 'Button', - component: Button, - }; - export const Story = {}; - `; + describe('named exports (stories)', () => { + it('should add test statement to inline exported stories', async () => { + const code = ` + export default { + component: Button, + } + export const Primary = { + args: { + label: 'Primary Button', + }, + }; + `; - const result = await transform({ code }); + const result = await transform({ code }); - expect(getStoryTitle).toHaveBeenCalled(); + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + const _meta = { + component: Button, + title: "automatic/calculated/title" + }; + export default _meta; + export const Primary = { + args: { + label: 'Primary Button' + } + }; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Primary", _testStory("Primary", Primary, _meta, [])); + } + `); + }); - expect(result.code).toMatchInlineSnapshot(` - import { test as _test, expect as _expect } from "vitest"; - import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; - const _meta = { - title: "automatic/calculated/title", - component: Button - }; - export default _meta; - export const Story = {}; - const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); - if (_isRunningFromThisFile) { - _test("Story", _testStory("Story", Story, _meta, [])); - } - `); + describe("use the story's name as test title", () => { + it('should support CSF v3 via name property', async () => { + const code = ` + export default { component: Button } + export const Primary = { name: "custom name" };`; + const result = await transform({ code }); + + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + const _meta = { + component: Button, + title: "automatic/calculated/title" + }; + export default _meta; + export const Primary = { + name: "custom name" + }; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("custom name", _testStory("Primary", Primary, _meta, [])); + } + `); + }); + + it('should support CSF v1/v2 via storyName property', async () => { + const code = ` + export default { component: Button } + export const Story = () => {} + Story.storyName = 'custom name';`; + const result = await transform({ code: code }); + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + const _meta = { + component: Button, + title: "automatic/calculated/title" + }; + export default _meta; + export const Story = () => {}; + Story.storyName = 'custom name'; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("custom name", _testStory("Story", Story, _meta, [])); + } + `); + }); + }); + + it('should add test statement to const declared exported stories', async () => { + const code = ` + export default {}; + const Primary = { + args: { + label: 'Primary Button', + }, + }; + + export { Primary }; + `; + + const result = await transform({ code }); + + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + const _meta = { + title: "automatic/calculated/title" + }; + export default _meta; + const Primary = { + args: { + label: 'Primary Button' + } + }; + export { Primary }; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Primary", _testStory("Primary", Primary, _meta, [])); + } + `); + }); + + it('should add test statement to const declared renamed exported stories', async () => { + const code = ` + export default {}; + const Primary = { + args: { + label: 'Primary Button', + }, + }; + + export { Primary as PrimaryStory }; + `; + + const result = await transform({ code }); + + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + const _meta = { + title: "automatic/calculated/title" + }; + export default _meta; + const Primary = { + args: { + label: 'Primary Button' + } + }; + export { Primary as PrimaryStory }; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("PrimaryStory", _testStory("PrimaryStory", Primary, _meta, [])); + } + `); + }); + + it('should add tests for multiple stories', async () => { + const code = ` + export default {}; + const Primary = { + args: { + label: 'Primary Button', + }, + }; + + export const Secondary = {} + + export { Primary }; + `; + + const result = await transform({ code }); + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + const _meta = { + title: "automatic/calculated/title" + }; + export default _meta; + const Primary = { + args: { + label: 'Primary Button' + } + }; + export const Secondary = {}; + export { Primary }; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Secondary", _testStory("Secondary", Secondary, _meta, [])); + _test("Primary", _testStory("Primary", Primary, _meta, [])); + } + `); + }); + + it('should exclude exports via excludeStories', async () => { + const code = ` + export default { + title: 'Button', + component: Button, + excludeStories: ['nonStory'], + } + export const Story = {}; + export const nonStory = 123 + `; + + const result = await transform({ code }); + + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + const _meta = { + title: "automatic/calculated/title", + component: Button, + excludeStories: ['nonStory'] + }; + export default _meta; + export const Story = {}; + export const nonStory = 123; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Story", _testStory("Story", Story, _meta, [])); + } + `); + }); + + it('should return a describe with skip if there are no valid stories', async () => { + const code = ` + export default { + title: 'Button', + component: Button, + tags: ['!test'] + } + export const Story = {} + `; + const result = await transform({ code }); + + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, describe as _describe } from "vitest"; + const _meta = { + title: "automatic/calculated/title", + component: Button, + tags: ['!test'] + }; + export default _meta; + export const Story = {}; + _describe.skip("No valid tests found"); + `); + }); }); - it('should add title to const declared default export if not present', async () => { - const code = ` - const meta = { - component: Button, - }; - export default meta; + describe('tags filtering mechanism', () => { + it('should only include stories from tags.include', async () => { + const code = ` + export default {}; + export const Included = { tags: ['include-me'] }; + + export const NotIncluded = {} + `; - export const Story = {}; - `; + const result = await transform({ + code, + tagsFilter: { include: ['include-me'], exclude: [], skip: [] }, + }); - const result = await transform({ code }); + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + const _meta = { + title: "automatic/calculated/title" + }; + export default _meta; + export const Included = { + tags: ['include-me'] + }; + export const NotIncluded = {}; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Included", _testStory("Included", Included, _meta, [])); + } + `); + }); - expect(getStoryTitle).toHaveBeenCalled(); + it('should exclude stories from tags.exclude', async () => { + const code = ` + export default {}; + export const Included = {}; + + export const NotIncluded = { tags: ['exclude-me'] } + `; - expect(result.code).toMatchInlineSnapshot(` - import { test as _test, expect as _expect } from "vitest"; - import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; - const meta = { - component: Button, - title: "automatic/calculated/title" - }; - export default meta; - export const Story = {}; - const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); - if (_isRunningFromThisFile) { - _test("Story", _testStory("Story", Story, meta, [])); - } - `); + const result = await transform({ + code, + tagsFilter: { include: ['test'], exclude: ['exclude-me'], skip: [] }, + }); + + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + const _meta = { + title: "automatic/calculated/title" + }; + export default _meta; + export const Included = {}; + export const NotIncluded = { + tags: ['exclude-me'] + }; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Included", _testStory("Included", Included, _meta, [])); + } + `); + }); + + it('should pass skip tags to testStory call using tags.skip', async () => { + const code = ` + export default {}; + export const Skipped = { tags: ['skip-me'] }; + `; + + const result = await transform({ + code, + tagsFilter: { include: ['test'], exclude: [], skip: ['skip-me'] }, + }); + + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + const _meta = { + title: "automatic/calculated/title" + }; + export default _meta; + export const Skipped = { + tags: ['skip-me'] + }; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Skipped", _testStory("Skipped", Skipped, _meta, ["skip-me"])); + } + `); + }); }); - it('should overwrite title to const declared default export if already present', async () => { - const code = ` - const meta = { - title: 'Button', - component: Button, - }; - export default meta; - - export const Story = {}; - `; + describe('source map calculation', () => { + it('should remap the location of an inline named export to its relative testStory function', async () => { + const originalCode = ` + const meta = { + title: 'Button', + component: Button, + } + export default meta; + export const Primary = {}; + `; - const result = await transform({ code }); + const { code: transformedCode, map } = await transform({ + code: originalCode, + }); - expect(getStoryTitle).toHaveBeenCalled(); + expect(transformedCode).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + const meta = { + title: "automatic/calculated/title", + component: Button + }; + export default meta; + export const Primary = {}; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Primary", _testStory("Primary", Primary, meta, [])); + } + `); - expect(result.code).toMatchInlineSnapshot(` - import { test as _test, expect as _expect } from "vitest"; - import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; - const meta = { - title: "automatic/calculated/title", - component: Button - }; - export default meta; - export const Story = {}; - const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); - if (_isRunningFromThisFile) { - _test("Story", _testStory("Story", Story, meta, [])); - } - `); + const consumer = await new SourceMapConsumer(map as unknown as RawSourceMap); + + // Locate `__test("Primary"...` in the transformed code + const testPrimaryLine = + transformedCode.split('\n').findIndex((line) => line.includes('_test("Primary"')) + 1; + const testPrimaryColumn = transformedCode + .split('\n') + [testPrimaryLine - 1].indexOf('_test("Primary"'); + + // Get the original position from the source map for `__test("Primary"...` + const originalPosition = consumer.originalPositionFor({ + line: testPrimaryLine, + column: testPrimaryColumn, + }); + + // Locate `export const Primary` in the original code + const originalPrimaryLine = + originalCode.split('\n').findIndex((line) => line.includes('export const Primary')) + 1; + const originalPrimaryColumn = originalCode + .split('\n') + [originalPrimaryLine - 1].indexOf('export const Primary'); + + // The original locations of the transformed code should match with the ones of the original code + expect(originalPosition.line, 'original line location').toBe(originalPrimaryLine); + expect(originalPosition.column, 'original column location').toBe(originalPrimaryColumn); + }); }); }); - describe('named exports (stories)', () => { - it('should add test statement to inline exported stories', async () => { - const code = ` - export default { - component: Button, - } - export const Primary = { - args: { - label: 'Primary Button', - }, - }; + describe('CSF Factories', () => { + describe('default exports (meta)', () => { + it('should add title to inline default export if not present', async () => { + const code = ` + import { config } from '#.storybook/preview'; + const meta = config.meta({ component: Button }); + export const Story = meta.story({}); `; - const result = await transform({ code }); - - expect(result.code).toMatchInlineSnapshot(` - import { test as _test, expect as _expect } from "vitest"; - import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; - const _meta = { - component: Button, - title: "automatic/calculated/title" - }; - export default _meta; - export const Primary = { - args: { - label: 'Primary Button' - } - }; - const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); - if (_isRunningFromThisFile) { - _test("Primary", _testStory("Primary", Primary, _meta, [])); - } - `); - }); - - describe("use the story's name as test title", () => { - it('should support CSF v3 via name property', async () => { - const code = ` - export default { component: Button } - export const Primary = { name: "custom name" };`; const result = await transform({ code }); + expect(getStoryTitle).toHaveBeenCalled(); + expect(result.code).toMatchInlineSnapshot(` import { test as _test, expect as _expect } from "vitest"; import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; - const _meta = { + import { config } from '#.storybook/preview'; + const meta = config.meta({ component: Button, title: "automatic/calculated/title" - }; - export default _meta; - export const Primary = { - name: "custom name" - }; + }); + export const Story = meta.story({}); const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("custom name", _testStory("Primary", Primary, _meta, [])); + _test("Story", _testStory("Story", Story, meta, [])); } `); }); + }); - it('should support CSF v1/v2 via storyName property', async () => { + describe('named exports (stories)', () => { + it("should use the story's name as test title", async () => { const code = ` - export default { component: Button } - export const Story = () => {} - Story.storyName = 'custom name';`; - const result = await transform({ code: code }); + import { config } from '#.storybook/preview'; + const meta = config.meta({ component: Button }); + export const Primary = meta.story({ name: "custom name" });`; + const result = await transform({ code }); + expect(result.code).toMatchInlineSnapshot(` import { test as _test, expect as _expect } from "vitest"; import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; - const _meta = { + import { config } from '#.storybook/preview'; + const meta = config.meta({ component: Button, title: "automatic/calculated/title" - }; - export default _meta; - export const Story = () => {}; - Story.storyName = 'custom name'; + }); + export const Primary = meta.story({ + name: "custom name" + }); const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("custom name", _testStory("Story", Story, _meta, [])); + _test("custom name", _testStory("Primary", Primary, meta, [])); } `); }); - }); - it('should add test statement to const declared exported stories', async () => { - const code = ` - export default {}; - const Primary = { + it('should add test statement to const declared exported stories', async () => { + const code = ` + import { config } from '#.storybook/preview'; + const meta = config.meta({ component: Button }); + const Primary = meta.story({ args: { label: 'Primary Button', - }, - }; + } + }); export { Primary }; `; - const result = await transform({ code }); + const result = await transform({ code }); - expect(result.code).toMatchInlineSnapshot(` - import { test as _test, expect as _expect } from "vitest"; - import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; - const _meta = { - title: "automatic/calculated/title" - }; - export default _meta; - const Primary = { - args: { - label: 'Primary Button' + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + import { config } from '#.storybook/preview'; + const meta = config.meta({ + component: Button, + title: "automatic/calculated/title" + }); + const Primary = meta.story({ + args: { + label: 'Primary Button' + } + }); + export { Primary }; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Primary", _testStory("Primary", Primary, meta, [])); } - }; - export { Primary }; - const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); - if (_isRunningFromThisFile) { - _test("Primary", _testStory("Primary", Primary, _meta, [])); - } - `); - }); + `); + }); - it('should add test statement to const declared renamed exported stories', async () => { - const code = ` - export default {}; - const Primary = { + it('should add test statement to const declared renamed exported stories', async () => { + const code = ` + import { config } from '#.storybook/preview'; + const meta = config.meta({ component: Button }); + const Primary = meta.story({ args: { label: 'Primary Button', - }, - }; + } + }); export { Primary as PrimaryStory }; `; - const result = await transform({ code }); + const result = await transform({ code }); - expect(result.code).toMatchInlineSnapshot(` - import { test as _test, expect as _expect } from "vitest"; - import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; - const _meta = { - title: "automatic/calculated/title" - }; - export default _meta; - const Primary = { - args: { - label: 'Primary Button' + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + import { config } from '#.storybook/preview'; + const meta = config.meta({ + component: Button, + title: "automatic/calculated/title" + }); + const Primary = meta.story({ + args: { + label: 'Primary Button' + } + }); + export { Primary as PrimaryStory }; + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("PrimaryStory", _testStory("PrimaryStory", Primary, meta, [])); } - }; - export { Primary as PrimaryStory }; - const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); - if (_isRunningFromThisFile) { - _test("PrimaryStory", _testStory("PrimaryStory", Primary, _meta, [])); - } - `); - }); + `); + }); - it('should add tests for multiple stories', async () => { - const code = ` + it('should add tests for multiple stories', async () => { + const code = ` export default {}; const Primary = { args: { @@ -344,8 +717,8 @@ describe('transformer', () => { export { Primary }; `; - const result = await transform({ code }); - expect(result.code).toMatchInlineSnapshot(` + const result = await transform({ code }); + expect(result.code).toMatchInlineSnapshot(` import { test as _test, expect as _expect } from "vitest"; import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; const _meta = { @@ -365,10 +738,10 @@ describe('transformer', () => { _test("Primary", _testStory("Primary", Primary, _meta, [])); } `); - }); + }); - it('should exclude exports via excludeStories', async () => { - const code = ` + it('should exclude exports via excludeStories', async () => { + const code = ` export default { title: 'Button', component: Button, @@ -378,9 +751,9 @@ describe('transformer', () => { export const nonStory = 123 `; - const result = await transform({ code }); + const result = await transform({ code }); - expect(result.code).toMatchInlineSnapshot(` + expect(result.code).toMatchInlineSnapshot(` import { test as _test, expect as _expect } from "vitest"; import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; const _meta = { @@ -396,10 +769,10 @@ describe('transformer', () => { _test("Story", _testStory("Story", Story, _meta, [])); } `); - }); + }); - it('should return a describe with skip if there are no valid stories', async () => { - const code = ` + it('should return a describe with skip if there are no valid stories', async () => { + const code = ` export default { title: 'Button', component: Button, @@ -407,9 +780,9 @@ describe('transformer', () => { } export const Story = {} `; - const result = await transform({ code }); + const result = await transform({ code }); - expect(result.code).toMatchInlineSnapshot(` + expect(result.code).toMatchInlineSnapshot(` import { test as _test, describe as _describe } from "vitest"; const _meta = { title: "automatic/calculated/title", @@ -420,156 +793,156 @@ describe('transformer', () => { export const Story = {}; _describe.skip("No valid tests found"); `); + }); }); - }); - describe('tags filtering mechanism', () => { - it('should only include stories from tags.include', async () => { - const code = ` - export default {}; - export const Included = { tags: ['include-me'] }; + describe('tags filtering mechanism', () => { + it('should only include stories from tags.include', async () => { + const code = ` + import { config } from '#.storybook/preview'; + const meta = config.meta({}); + export const Included = meta.story({ tags: ['include-me'] }); - export const NotIncluded = {} + export const NotIncluded = meta.story({}); `; - const result = await transform({ - code, - tagsFilter: { include: ['include-me'], exclude: [], skip: [] }, + const result = await transform({ + code, + tagsFilter: { include: ['include-me'], exclude: [], skip: [] }, + }); + + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + import { config } from '#.storybook/preview'; + const meta = config.meta({ + title: "automatic/calculated/title" + }); + export const Included = meta.story({ + tags: ['include-me'] + }); + export const NotIncluded = meta.story({}); + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Included", _testStory("Included", Included, meta, [])); + } + `); }); - expect(result.code).toMatchInlineSnapshot(` - import { test as _test, expect as _expect } from "vitest"; - import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; - const _meta = { - title: "automatic/calculated/title" - }; - export default _meta; - export const Included = { - tags: ['include-me'] - }; - export const NotIncluded = {}; - const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); - if (_isRunningFromThisFile) { - _test("Included", _testStory("Included", Included, _meta, [])); - } - `); - }); + it('should exclude stories from tags.exclude', async () => { + const code = ` + import { config } from '#.storybook/preview'; + const meta = config.meta({}); + export const Included = meta.story({}); - it('should exclude stories from tags.exclude', async () => { - const code = ` - export default {}; - export const Included = {}; + export const NotIncluded = meta.story({ tags: ['exclude-me'] }); + `; - export const NotIncluded = { tags: ['exclude-me'] } - `; + const result = await transform({ + code, + tagsFilter: { include: ['test'], exclude: ['exclude-me'], skip: [] }, + }); - const result = await transform({ - code, - tagsFilter: { include: ['test'], exclude: ['exclude-me'], skip: [] }, + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + import { config } from '#.storybook/preview'; + const meta = config.meta({ + title: "automatic/calculated/title" + }); + export const Included = meta.story({}); + export const NotIncluded = meta.story({ + tags: ['exclude-me'] + }); + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Included", _testStory("Included", Included, meta, [])); + } + `); }); - expect(result.code).toMatchInlineSnapshot(` - import { test as _test, expect as _expect } from "vitest"; - import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; - const _meta = { - title: "automatic/calculated/title" - }; - export default _meta; - export const Included = {}; - export const NotIncluded = { - tags: ['exclude-me'] - }; - const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); - if (_isRunningFromThisFile) { - _test("Included", _testStory("Included", Included, _meta, [])); - } - `); - }); - - it('should pass skip tags to testStory call using tags.skip', async () => { - const code = ` - export default {}; - export const Skipped = { tags: ['skip-me'] }; + it('should pass skip tags to testStory call using tags.skip', async () => { + const code = ` + import { config } from '#.storybook/preview'; + const meta = config.meta({}); + export const Skipped = meta.story({ tags: ['skip-me'] }); `; - const result = await transform({ - code, - tagsFilter: { include: ['test'], exclude: [], skip: ['skip-me'] }, - }); + const result = await transform({ + code, + tagsFilter: { include: ['test'], exclude: [], skip: ['skip-me'] }, + }); - expect(result.code).toMatchInlineSnapshot(` - import { test as _test, expect as _expect } from "vitest"; - import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; - const _meta = { - title: "automatic/calculated/title" - }; - export default _meta; - export const Skipped = { - tags: ['skip-me'] - }; - const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); - if (_isRunningFromThisFile) { - _test("Skipped", _testStory("Skipped", Skipped, _meta, ["skip-me"])); - } - `); + expect(result.code).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + import { config } from '#.storybook/preview'; + const meta = config.meta({ + title: "automatic/calculated/title" + }); + export const Skipped = meta.story({ + tags: ['skip-me'] + }); + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Skipped", _testStory("Skipped", Skipped, meta, ["skip-me"])); + } + `); + }); }); - }); - describe('source map calculation', () => { - it('should remap the location of an inline named export to its relative testStory function', async () => { - const originalCode = ` - const meta = { - title: 'Button', - component: Button, - } - export default meta; - export const Primary = {}; + describe('source map calculation', () => { + it('should remap the location of an inline named export to its relative testStory function', async () => { + const originalCode = ` + import { config } from '#.storybook/preview'; + const meta = config.meta({}); + export const Primary = meta.story({}); `; - const { code: transformedCode, map } = await transform({ - code: originalCode, - }); - - expect(transformedCode).toMatchInlineSnapshot(` - import { test as _test, expect as _expect } from "vitest"; - import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; - const meta = { - title: "automatic/calculated/title", - component: Button - }; - export default meta; - export const Primary = {}; - const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); - if (_isRunningFromThisFile) { - _test("Primary", _testStory("Primary", Primary, meta, [])); - } - `); - - const consumer = await new SourceMapConsumer(map as unknown as RawSourceMap); + const { code: transformedCode, map } = await transform({ + code: originalCode, + }); - // Locate `__test("Primary"...` in the transformed code - const testPrimaryLine = - transformedCode.split('\n').findIndex((line) => line.includes('_test("Primary"')) + 1; - const testPrimaryColumn = transformedCode - .split('\n') - [testPrimaryLine - 1].indexOf('_test("Primary"'); + expect(transformedCode).toMatchInlineSnapshot(` + import { test as _test, expect as _expect } from "vitest"; + import { testStory as _testStory } from "@storybook/experimental-addon-test/internal/test-utils"; + import { config } from '#.storybook/preview'; + const meta = config.meta({ + title: "automatic/calculated/title" + }); + export const Primary = meta.story({}); + const _isRunningFromThisFile = import.meta.url.includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); + if (_isRunningFromThisFile) { + _test("Primary", _testStory("Primary", Primary, meta, [])); + } + `); - // Get the original position from the source map for `__test("Primary"...` - const originalPosition = consumer.originalPositionFor({ - line: testPrimaryLine, - column: testPrimaryColumn, + const consumer = await new SourceMapConsumer(map as unknown as RawSourceMap); + + // Locate `__test("Primary"...` in the transformed code + const testPrimaryLine = + transformedCode.split('\n').findIndex((line) => line.includes('_test("Primary"')) + 1; + const testPrimaryColumn = transformedCode + .split('\n') + [testPrimaryLine - 1].indexOf('_test("Primary"'); + + // Get the original position from the source map for `__test("Primary"...` + const originalPosition = consumer.originalPositionFor({ + line: testPrimaryLine, + column: testPrimaryColumn, + }); + + // Locate `export const Primary` in the original code + const originalPrimaryLine = + originalCode.split('\n').findIndex((line) => line.includes('export const Primary')) + 1; + const originalPrimaryColumn = originalCode + .split('\n') + [originalPrimaryLine - 1].indexOf('export const Primary'); + + // The original locations of the transformed code should match with the ones of the original code + expect(originalPosition.line, 'original line location').toBe(originalPrimaryLine); + expect(originalPosition.column, 'original column location').toBe(originalPrimaryColumn); }); - - // Locate `export const Primary` in the original code - const originalPrimaryLine = - originalCode.split('\n').findIndex((line) => line.includes('export const Primary')) + 1; - const originalPrimaryColumn = originalCode - .split('\n') - [originalPrimaryLine - 1].indexOf('export const Primary'); - - // The original locations of the transformed code should match with the ones of the original code - expect(originalPosition.line, 'original line location').toBe(originalPrimaryLine); - expect(originalPosition.column, 'original column location').toBe(originalPrimaryColumn); }); }); From c370fec8e3d7dac1b9791c40b36ad93045bc8ff8 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Mon, 6 Jan 2025 14:49:42 +0100 Subject: [PATCH 010/144] Handle project annotations --- .../src/codegen-modern-iframe-script.ts | 13 +++++++++++-- .../templates/virtualModuleModernEntry.js | 16 +++++++++++++++- .../src/preview-api/modules/store/StoryStore.ts | 2 +- .../modules/store/csf/processCSFFile.ts | 3 +++ code/core/src/types/modules/story.ts | 1 + code/renderers/react/src/preview.tsx | 2 ++ 6 files changed, 33 insertions(+), 4 deletions(-) diff --git a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts index dffb3144d67a..c43686fd248d 100644 --- a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts +++ b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts @@ -14,7 +14,7 @@ export async function generateModernIframeScriptCode(options: Options, projectRo [], options ); - const previewAnnotationURLs = [...previewAnnotations, previewOrConfigFile] + const [previewFileUrl, ...previewAnnotationURLs] = [...previewAnnotations, previewOrConfigFile] .filter(Boolean) .map((path) => processPreviewAnnotation(path, projectRoot)); @@ -23,6 +23,15 @@ export async function generateModernIframeScriptCode(options: Options, projectRo // modules are provided, the rest are null. We can just re-import everything again in that case. const getPreviewAnnotationsFunction = ` const getProjectAnnotations = async (hmrPreviewAnnotationModules = []) => { + const preview = await import('${previewFileUrl}'); + const csfFactoryPreview = Object.values(preview).find(module => { + return 'isCSFFactoryPreview' in module + }); + + if (csfFactoryPreview) { + return csfFactoryPreview.annotations; + } + const configs = await Promise.all([${previewAnnotationURLs .map( (previewAnnotation, index) => @@ -30,7 +39,7 @@ export async function generateModernIframeScriptCode(options: Options, projectRo `hmrPreviewAnnotationModules[${index}] ?? import('${previewAnnotation}')` ) .join(',\n')}]) - return composeConfigs(configs); + return composeConfigs([...configs, preview]); }`; // eslint-disable-next-line @typescript-eslint/no-shadow diff --git a/code/builders/builder-webpack5/templates/virtualModuleModernEntry.js b/code/builders/builder-webpack5/templates/virtualModuleModernEntry.js index 4ae082c43b2e..423bee0c36d0 100644 --- a/code/builders/builder-webpack5/templates/virtualModuleModernEntry.js +++ b/code/builders/builder-webpack5/templates/virtualModuleModernEntry.js @@ -5,7 +5,21 @@ import { global } from '@storybook/global'; import { importFn } from '{{storiesFilename}}'; -const getProjectAnnotations = () => composeConfigs(['{{previewAnnotations_requires}}']); +const getProjectAnnotations = () => { + const previewAnnotations = ['{{previewAnnotations_requires}}']; + // the last one in this array is the user preview + const preview = previewAnnotations[previewAnnotations.length - 1]; + + const csfFactoryPreview = Object.values(preview).find((module) => { + return 'isCSFFactoryPreview' in module; + }); + + if (csfFactoryPreview) { + return csfFactoryPreview.annotations; + } + + return composeConfigs(previewAnnotations); +}; const channel = createBrowserChannel({ page: 'preview' }); addons.setChannel(channel); diff --git a/code/core/src/preview-api/modules/store/StoryStore.ts b/code/core/src/preview-api/modules/store/StoryStore.ts index 01404a5f1bf5..79b2013d1c5e 100644 --- a/code/core/src/preview-api/modules/store/StoryStore.ts +++ b/code/core/src/preview-api/modules/store/StoryStore.ts @@ -215,7 +215,7 @@ export class StoryStore { const story = this.prepareStoryWithCache( storyAnnotations, componentAnnotations, - this.projectAnnotations + csfFile.projectAnnotations ?? this.projectAnnotations ); this.args.setInitial(story); this.hooks[story.id] = this.hooks[story.id] || new HooksContext(); diff --git a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts index cc7128e90e6b..bc6c17f7090f 100644 --- a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts +++ b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts @@ -47,6 +47,7 @@ export function processCSFFile( const { default: defaultExport, __namedExportsOrder, ...namedExports } = moduleExports; const firstStory: any = Object.values(namedExports)[0]; + // CSF4 if (!defaultExport && 'isCSFFactory' in firstStory) { const meta: NormalizedComponentAnnotations = normalizeComponentAnnotations(firstStory.meta.annotations, title, importPath); @@ -63,6 +64,8 @@ export function processCSFFile( } }); + csfFile.projectAnnotations = firstStory.config.annotations; + return csfFile; } diff --git a/code/core/src/types/modules/story.ts b/code/core/src/types/modules/story.ts index c4b07fa1cd30..a823863defaf 100644 --- a/code/core/src/types/modules/story.ts +++ b/code/core/src/types/modules/story.ts @@ -98,6 +98,7 @@ export type NormalizedStoryAnnotations = export type CSFFile = { meta: NormalizedComponentAnnotations; stories: Record>; + projectAnnotations?: NormalizedProjectAnnotations; moduleExports: ModuleExports; }; diff --git a/code/renderers/react/src/preview.tsx b/code/renderers/react/src/preview.tsx index 4a2b84a07c23..655f246598d4 100644 --- a/code/renderers/react/src/preview.tsx +++ b/code/renderers/react/src/preview.tsx @@ -33,6 +33,8 @@ class PreviewConfig { ) => { return new Meta(meta as ComponentAnnotations, this); }; + + readonly isCSFFactoryPreview = true; } class Meta { From d04b25810c20d341b75f48d964a3c8d3312cfe6d Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Mon, 6 Jan 2025 15:23:31 +0100 Subject: [PATCH 011/144] Fix runtime --- .../builder-vite/src/codegen-modern-iframe-script.ts | 2 +- code/renderers/react/src/preview.tsx | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts index c43686fd248d..7a17d5642447 100644 --- a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts +++ b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts @@ -14,7 +14,7 @@ export async function generateModernIframeScriptCode(options: Options, projectRo [], options ); - const [previewFileUrl, ...previewAnnotationURLs] = [...previewAnnotations, previewOrConfigFile] + const [previewFileUrl, ...previewAnnotationURLs] = [previewOrConfigFile, ...previewAnnotations] .filter(Boolean) .map((path) => processPreviewAnnotation(path, projectRoot)); diff --git a/code/renderers/react/src/preview.tsx b/code/renderers/react/src/preview.tsx index 8cd9c5cec2d2..a43f89a59587 100644 --- a/code/renderers/react/src/preview.tsx +++ b/code/renderers/react/src/preview.tsx @@ -10,10 +10,15 @@ import type { StoryAnnotations, } from 'storybook/internal/types'; +import * as reactAnnotations from './entry-preview'; +import * as reactDocsAnnotations from './entry-preview-docs'; import type { ReactRenderer } from './types'; export function defineConfig(config: PreviewConfigData) { - return new PreviewConfig(config); + return new PreviewConfig({ + ...config, + addons: [reactAnnotations, reactDocsAnnotations, ...(config.addons ?? [])], + }); } interface PreviewConfigData extends ProjectAnnotations { From 410f98838af5aed96898731a09022bd71a75acd3 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Mon, 6 Jan 2025 15:42:09 +0100 Subject: [PATCH 012/144] Fix loaders --- code/.storybook/preview.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index 5ef325bde62b..c8baf121ab4d 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -121,7 +121,7 @@ const ThemedSetRoot = () => { // eslint-disable-next-line no-underscore-dangle const preview = (window as any).__STORYBOOK_PREVIEW__ as PreviewWeb | undefined; const channel = (window as any).__STORYBOOK_ADDONS_CHANNEL__ as Channel | undefined; -export const loaders = [ +const loaders = [ /** * This loader adds a DocsContext to the story, which is required for the most Blocks to work. A * story will specify which stories they need in the index with: @@ -170,7 +170,7 @@ export const loaders = [ }, ] as Loader[]; -export const decorators = [ +const decorators = [ // This decorator adds the DocsContext created in the loader above (Story, { loaded: { docsContext } }) => docsContext ? ( @@ -308,7 +308,7 @@ export const decorators = [ }, ] satisfies Decorator[]; -export const parameters = { +const parameters = { options: { storySort: (a, b) => a.title === b.title ? 0 : a.id.localeCompare(b.id, undefined, { numeric: true }), @@ -361,10 +361,9 @@ export const parameters = { }, }; -export const tags = ['test', 'vitest', '!a11ytest']; - export const config = defineConfig({ parameters, - tags, decorators, + loaders, + tags: ['test', 'vitest', '!a11ytest'], }); From 25e9575662f61ae3239ba1802e2746332c9fd236 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Tue, 7 Jan 2025 00:08:41 +0800 Subject: [PATCH 013/144] ConfigFile: Fix tag parsing issue --- code/core/src/csf-tools/ConfigFile.test.ts | 15 +++++++++++++++ code/core/src/csf-tools/ConfigFile.ts | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/code/core/src/csf-tools/ConfigFile.test.ts b/code/core/src/csf-tools/ConfigFile.test.ts index 7e0f9dc80ead..b6895ac323f3 100644 --- a/code/core/src/csf-tools/ConfigFile.test.ts +++ b/code/core/src/csf-tools/ConfigFile.test.ts @@ -256,6 +256,21 @@ describe('ConfigFile', () => { ) ).toEqual('webpack5'); }); + it('tags', () => { + expect( + getField( + ['tags'], + dedent` + import { defineConfig } from '@storybook/react-vite/preview'; + const parameters = {}; + export const config = defineConfig({ + parameters, + tags: ['test', 'vitest', '!a11ytest'], + }); + ` + ) + ).toEqual(['test', 'vitest', '!a11ytest']); + }); }); }); diff --git a/code/core/src/csf-tools/ConfigFile.ts b/code/core/src/csf-tools/ConfigFile.ts index 5ee29fc2ca5d..8d1e11f295c4 100644 --- a/code/core/src/csf-tools/ConfigFile.ts +++ b/code/core/src/csf-tools/ConfigFile.ts @@ -187,14 +187,14 @@ export class ConfigFile { this.fileName = fileName; } - _parseExportsObject(exportsObject: t.ObjectExpression, parent?: t.Node) { + _parseExportsObject(exportsObject: t.ObjectExpression) { this._exportsObject = exportsObject; (exportsObject.properties as t.ObjectProperty[]).forEach((p) => { const exportName = propKey(p); if (exportName) { let exportVal = p.value; if (t.isIdentifier(exportVal)) { - exportVal = _findVarInitialization(exportVal.name, parent as t.Program) as any; + exportVal = _findVarInitialization(exportVal.name, this._ast.program) as any; } this._exports[exportName] = exportVal as t.Expression; } @@ -216,7 +216,7 @@ export class ConfigFile { decl = unwrap(decl); if (t.isObjectExpression(decl)) { - self._parseExportsObject(decl, parent); + self._parseExportsObject(decl); } else { logger.warn( getCsfParsingErrorMessage({ From c061e8249b3bc6ccee09d010ba7dcb4a6769f91d Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Tue, 7 Jan 2025 00:09:14 +0800 Subject: [PATCH 014/144] CsfFile: Add CSF factories telemetry --- code/core/src/csf-tools/CsfFile.test.ts | 68 +++++++++++++++++++++++++ code/core/src/csf-tools/CsfFile.ts | 4 +- code/core/src/types/modules/indexer.ts | 1 + 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/code/core/src/csf-tools/CsfFile.test.ts b/code/core/src/csf-tools/CsfFile.test.ts index 61a1ebbfac10..5c686c7dbe83 100644 --- a/code/core/src/csf-tools/CsfFile.test.ts +++ b/code/core/src/csf-tools/CsfFile.test.ts @@ -131,6 +131,7 @@ describe('CsfFile', () => { __isArgsStory: false __id: foo-bar--basic __stats: + factory: false play: false render: false loaders: false @@ -162,6 +163,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: false render: false loaders: false @@ -191,6 +193,7 @@ describe('CsfFile', () => { - id: foo-bar--include-a name: Include A __stats: + factory: false play: false render: false loaders: false @@ -218,6 +221,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: Some story __stats: + factory: false play: false render: false loaders: false @@ -246,6 +250,7 @@ describe('CsfFile', () => { - id: default-title--a name: A __stats: + factory: false play: false render: false loaders: false @@ -257,6 +262,7 @@ describe('CsfFile', () => { - id: default-title--b name: B __stats: + factory: false play: false render: false loaders: false @@ -285,6 +291,7 @@ describe('CsfFile', () => { - id: custom-id--a name: A __stats: + factory: false play: false render: false loaders: false @@ -296,6 +303,7 @@ describe('CsfFile', () => { - id: custom-id--b name: B __stats: + factory: false play: false render: false loaders: false @@ -324,6 +332,7 @@ describe('CsfFile', () => { - id: custom-meta-id--just-custom-meta-id name: Just Custom Meta Id __stats: + factory: false play: false render: false loaders: false @@ -335,6 +344,7 @@ describe('CsfFile', () => { - id: custom-id name: Custom Paremeters Id __stats: + factory: false play: false render: false loaders: false @@ -364,6 +374,7 @@ describe('CsfFile', () => { - id: foo-bar-baz--a name: A __stats: + factory: false play: false render: false loaders: false @@ -375,6 +386,7 @@ describe('CsfFile', () => { - id: foo-bar-baz--b name: B __stats: + factory: false play: false render: false loaders: false @@ -408,6 +420,7 @@ describe('CsfFile', () => { __isArgsStory: true __id: foo-bar--a __stats: + factory: false play: false render: false loaders: false @@ -422,6 +435,7 @@ describe('CsfFile', () => { __isArgsStory: true __id: foo-bar--b __stats: + factory: false play: false render: false loaders: false @@ -455,6 +469,7 @@ describe('CsfFile', () => { __isArgsStory: true __id: foo-bar--a __stats: + factory: false play: false render: false loaders: false @@ -469,6 +484,7 @@ describe('CsfFile', () => { __isArgsStory: true __id: foo-bar--b __stats: + factory: false play: false render: false loaders: false @@ -499,6 +515,7 @@ describe('CsfFile', () => { - id: foo-bar-baz--a name: A __stats: + factory: false play: false render: false loaders: false @@ -510,6 +527,7 @@ describe('CsfFile', () => { - id: foo-bar-baz--b name: B __stats: + factory: false play: false render: false loaders: false @@ -540,6 +558,7 @@ describe('CsfFile', () => { - id: foo-bar-baz--a name: A __stats: + factory: false play: false render: false loaders: false @@ -551,6 +570,7 @@ describe('CsfFile', () => { - id: foo-bar-baz--b name: B __stats: + factory: false play: false render: false loaders: false @@ -579,6 +599,7 @@ describe('CsfFile', () => { - id: default-title--a name: A __stats: + factory: false play: false render: false loaders: false @@ -590,6 +611,7 @@ describe('CsfFile', () => { - id: default-title--b name: B __stats: + factory: false play: false render: false loaders: false @@ -622,6 +644,7 @@ describe('CsfFile', () => { __isArgsStory: true __id: foo-bar--a __stats: + factory: false play: false render: false loaders: false @@ -653,6 +676,7 @@ describe('CsfFile', () => { __isArgsStory: false __id: foo-bar--a __stats: + factory: false play: false render: false loaders: false @@ -685,6 +709,7 @@ describe('CsfFile', () => { __id: foo-bar--page docsOnly: true __stats: + factory: false play: false render: false loaders: false @@ -722,6 +747,7 @@ describe('CsfFile', () => { __id: foo-bar--page docsOnly: true __stats: + factory: false play: false render: false loaders: false @@ -754,6 +780,7 @@ describe('CsfFile', () => { __isArgsStory: false __id: foo-bar--a __stats: + factory: false play: false render: false loaders: false @@ -768,6 +795,7 @@ describe('CsfFile', () => { __isArgsStory: true __id: foo-bar--b __stats: + factory: false play: false render: false loaders: false @@ -840,6 +868,7 @@ describe('CsfFile', () => { __isArgsStory: true __id: foo-bar--b __stats: + factory: false play: false render: false loaders: false @@ -854,6 +883,7 @@ describe('CsfFile', () => { __isArgsStory: false __id: foo-bar--a __stats: + factory: false play: false render: false loaders: false @@ -968,6 +998,7 @@ describe('CsfFile', () => { - id: default-title--a name: A __stats: + factory: false play: false render: false loaders: false @@ -979,6 +1010,7 @@ describe('CsfFile', () => { - id: default-title--b name: B __stats: + factory: false play: false render: false loaders: false @@ -1032,6 +1064,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: false render: false loaders: false @@ -1043,6 +1076,7 @@ describe('CsfFile', () => { - id: foo-bar--b name: B __stats: + factory: false play: false render: false loaders: false @@ -1120,6 +1154,7 @@ describe('CsfFile', () => { __isArgsStory: false __id: foo-bar--a __stats: + factory: false play: false render: true loaders: false @@ -1152,6 +1187,7 @@ describe('CsfFile', () => { __isArgsStory: true __id: foo-bar--a __stats: + factory: false play: false render: true loaders: false @@ -1182,6 +1218,7 @@ describe('CsfFile', () => { __isArgsStory: true __id: foo-bar--a __stats: + factory: false play: false render: false loaders: false @@ -1214,6 +1251,7 @@ describe('CsfFile', () => { __isArgsStory: true __id: foo-bar--a __stats: + factory: false play: false render: false loaders: false @@ -1295,6 +1333,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: false render: false loaders: false @@ -1328,6 +1367,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: false render: true loaders: false @@ -1363,6 +1403,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: false render: true loaders: false @@ -1423,6 +1464,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: true render: false loaders: false @@ -1458,6 +1500,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: true render: true loaders: false @@ -1489,6 +1532,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: true render: false loaders: false @@ -1519,6 +1563,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: true render: false loaders: false @@ -1552,6 +1597,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: true render: false loaders: false @@ -1585,6 +1631,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: true render: true loaders: true @@ -1617,6 +1664,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: true render: false loaders: false @@ -1667,6 +1715,7 @@ describe('CsfFile', () => { - play-fn __id: component-id--a __stats: + factory: false play: true render: false loaders: false @@ -1687,6 +1736,7 @@ describe('CsfFile', () => { - play-fn __id: component-id--b __stats: + factory: false play: true render: false loaders: false @@ -1725,6 +1775,7 @@ describe('CsfFile', () => { - component-tag __id: custom-story-id __stats: + factory: false play: false render: false loaders: false @@ -1768,6 +1819,7 @@ describe('CsfFile', () => { - inherit-tag-dup __id: custom-foo-title--a __stats: + factory: false play: false render: false loaders: false @@ -1826,6 +1878,7 @@ describe('CsfFile', () => { tags: [] __id: custom-foo-title--a __stats: + factory: false play: false render: true loaders: false @@ -1863,6 +1916,7 @@ describe('CsfFile', () => { tags: [] __id: custom-foo-title--a __stats: + factory: false play: false render: true loaders: false @@ -1900,6 +1954,7 @@ describe('CsfFile', () => { tags: [] __id: custom-foo-title--a __stats: + factory: false play: false render: true loaders: false @@ -1937,6 +1992,7 @@ describe('CsfFile', () => { tags: [] __id: custom-foo-title--a __stats: + factory: false play: false render: true loaders: false @@ -1967,6 +2023,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: false render: false loaders: false @@ -1997,6 +2054,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: false render: false loaders: false @@ -2026,6 +2084,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: false render: false loaders: false @@ -2052,6 +2111,7 @@ describe('CsfFile', () => { - id: foo-bar--a name: A __stats: + factory: false play: false render: false loaders: false @@ -2084,6 +2144,7 @@ describe('CsfFile', () => { - id: default-title--a name: A __stats: + factory: true play: false render: false loaders: false @@ -2095,6 +2156,7 @@ describe('CsfFile', () => { - id: default-title--b name: B __stats: + factory: true play: false render: false loaders: false @@ -2123,6 +2185,7 @@ describe('CsfFile', () => { - id: default-title--a name: A __stats: + factory: true play: false render: false loaders: false @@ -2153,6 +2216,7 @@ describe('CsfFile', () => { - id: default-title--a name: A __stats: + factory: true play: false render: false loaders: false @@ -2164,6 +2228,7 @@ describe('CsfFile', () => { - id: default-title--b name: B __stats: + factory: true play: false render: false loaders: false @@ -2192,6 +2257,7 @@ describe('CsfFile', () => { - id: default-title--a name: bar __stats: + factory: true play: false render: false loaders: false @@ -2225,6 +2291,7 @@ describe('CsfFile', () => { __isArgsStory: false __id: foo-bar--a __stats: + factory: true play: false render: true loaders: false @@ -2258,6 +2325,7 @@ describe('CsfFile', () => { __isArgsStory: true __id: foo-bar--a __stats: + factory: true play: false render: true loaders: false diff --git a/code/core/src/csf-tools/CsfFile.ts b/code/core/src/csf-tools/CsfFile.ts index 5e6eaac9fa29..14563a732724 100644 --- a/code/core/src/csf-tools/CsfFile.ts +++ b/code/core/src/csf-tools/CsfFile.ts @@ -567,7 +567,9 @@ export class CsfFile { id: 'FIXME', name, parameters, - __stats: {}, + __stats: { + factory: storyIsFactory, + }, }; } }); diff --git a/code/core/src/types/modules/indexer.ts b/code/core/src/types/modules/indexer.ts index 042eb1791a28..12a1e3628b67 100644 --- a/code/core/src/types/modules/indexer.ts +++ b/code/core/src/types/modules/indexer.ts @@ -91,6 +91,7 @@ export interface IndexInputStats { beforeEach?: boolean; moduleMock?: boolean; globals?: boolean; + factory?: boolean; } /** The base input for indexing a story or docs entry. */ From 3466750414e174f60fd26e75805654510d5ac57e Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Tue, 7 Jan 2025 00:24:13 +0800 Subject: [PATCH 015/144] CsfFile: Fix bad meta definition --- code/core/src/csf-tools/CsfFile.test.ts | 17 +++++++++++++++++ code/core/src/csf-tools/CsfFile.ts | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/code/core/src/csf-tools/CsfFile.test.ts b/code/core/src/csf-tools/CsfFile.test.ts index 5c686c7dbe83..abb22209a619 100644 --- a/code/core/src/csf-tools/CsfFile.test.ts +++ b/code/core/src/csf-tools/CsfFile.test.ts @@ -2408,6 +2408,23 @@ describe('CsfFile', () => { `); }); + it.only('local defineConfig', () => { + expect(() => + parse( + dedent` + import { defineConfig } from '@storybook/react/preview'; + const config = defineConfig({ }); + const meta = config.meta({ component: 'foo' }); + export const A = meta.story({}) + ` + ) + ).toThrowErrorMatchingInlineSnapshot(` + [BadMetaError: CSF: meta() factory must be imported from .storybook/preview configuration (line 4, col 28) + + More info: https://storybook.js.org/docs/writing-stories#default-export] + `); + }); + it('mixed factories and non-factories', () => { expect(() => parse( diff --git a/code/core/src/csf-tools/CsfFile.ts b/code/core/src/csf-tools/CsfFile.ts index 14563a732724..bd963f86aa46 100644 --- a/code/core/src/csf-tools/CsfFile.ts +++ b/code/core/src/csf-tools/CsfFile.ts @@ -521,8 +521,8 @@ export class CsfFile { self._options.fileName ); } else if (!self._metaIsFactory && storyIsFactory) { - throw new MixedFactoryError( - 'expected non-factory story', + throw new BadMetaError( + 'meta() factory must be imported from .storybook/preview configuration', storyNode as t.Node, self._options.fileName ); From 84f2ae609c9fdecb744c4035f1ec797a16c19256 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 6 Jan 2025 18:14:07 +0100 Subject: [PATCH 016/144] Build: Add CSF4 story in react-vite framework --- .../template/stories/csf4.stories.tsx | 15 +++++++++++++++ scripts/tasks/sandbox-parts.ts | 19 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 code/frameworks/react-vite/template/stories/csf4.stories.tsx diff --git a/code/frameworks/react-vite/template/stories/csf4.stories.tsx b/code/frameworks/react-vite/template/stories/csf4.stories.tsx new file mode 100644 index 000000000000..40bf4bcaf956 --- /dev/null +++ b/code/frameworks/react-vite/template/stories/csf4.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { config } from '#.storybook/preview'; + +// eslint-disable-next-line storybook/default-exports +const Button = ({ label }) => ; + +const meta = config.meta({ + component: Button, + args: { + label: 'Hello world!', + }, +}); + +export const Story = meta.story({}); diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 271fbba9dcd8..6005a1ec7525 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -19,7 +19,7 @@ import { join, relative, resolve, sep } from 'path'; import slash from 'slash'; import dedent from 'ts-dedent'; -import { babelParse } from '../../code/core/src/babel/babelParse'; +import { babelParse, types as t } from '../../code/core/src/babel'; import { detectLanguage } from '../../code/core/src/cli/detect'; import { SupportedLanguage } from '../../code/core/src/cli/project_types'; import { JsPackageManagerFactory, versions as storybookPackages } from '../../code/core/src/common'; @@ -822,6 +822,22 @@ export const extendPreview: Task['run'] = async ({ template, sandboxDir }) => { logger.log('📝 Extending preview.js'); const previewConfig = await readConfig({ cwd: sandboxDir, fileName: 'preview' }); + if (template.expected.framework === '@storybook/react-vite') { + // add CSF4 style config + previewConfig.setImport(['defineConfig'], '@storybook/react/preview'); + previewConfig.setBodyDeclaration( + t.exportNamedDeclaration( + t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier('config'), + t.callExpression(t.identifier('defineConfig'), [t.identifier('preview')]) + ), + ]), + [] + ) + ); + } + if (template.expected.builder.includes('vite')) { previewConfig.setFieldValue(['tags'], ['vitest', '!a11ytest']); } @@ -837,6 +853,7 @@ export async function setImportMap(cwd: string) { storybook: './template-stories/core/utils.mock.ts', default: './template-stories/core/utils.ts', }, + '#*': ['./*', './*.ts', './*.tsx'], }; await writeJson(join(cwd, 'package.json'), packageJson, { spaces: 2 }); From 3753cde16c726232c36c855f563b6e5372fd759c Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Tue, 7 Jan 2025 01:41:37 +0800 Subject: [PATCH 017/144] CsfFile: Improve meta error handling and fix failing test --- code/core/src/csf-tools/CsfFile.test.ts | 2 +- code/core/src/csf-tools/CsfFile.ts | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/code/core/src/csf-tools/CsfFile.test.ts b/code/core/src/csf-tools/CsfFile.test.ts index abb22209a619..639e1a6dbfc6 100644 --- a/code/core/src/csf-tools/CsfFile.test.ts +++ b/code/core/src/csf-tools/CsfFile.test.ts @@ -2408,7 +2408,7 @@ describe('CsfFile', () => { `); }); - it.only('local defineConfig', () => { + it('local defineConfig', () => { expect(() => parse( dedent` diff --git a/code/core/src/csf-tools/CsfFile.ts b/code/core/src/csf-tools/CsfFile.ts index bd963f86aa46..d7ed7fe8eeb9 100644 --- a/code/core/src/csf-tools/CsfFile.ts +++ b/code/core/src/csf-tools/CsfFile.ts @@ -521,11 +521,19 @@ export class CsfFile { self._options.fileName ); } else if (!self._metaIsFactory && storyIsFactory) { - throw new BadMetaError( - 'meta() factory must be imported from .storybook/preview configuration', - storyNode as t.Node, - self._options.fileName - ); + if (self._metaNode) { + throw new MixedFactoryError( + 'expected non-factory story', + storyNode as t.Node, + self._options.fileName + ); + } else { + throw new BadMetaError( + 'meta() factory must be imported from .storybook/preview configuration', + storyNode as t.Node, + self._options.fileName + ); + } } const parameters: { [key: string]: any } = {}; if (t.isObjectExpression(storyNode)) { From 5f7c6af5fba507b8ec895fb4d24c5c76476e433d Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 7 Jan 2025 11:56:41 +0100 Subject: [PATCH 018/144] Add addons array --- code/.storybook/preview.tsx | 14 ++++++++++++++ code/core/template/stories/preview.ts | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index c8baf121ab4d..b7f174196027 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -19,7 +19,20 @@ import { global } from '@storybook/global'; import type { Decorator, Loader, ReactRenderer } from '@storybook/react'; import { defineConfig } from '@storybook/react/preview'; +// TODO add empty preview +// import * as storysource from '@storybook/addon-storysource'; +// import * as designs from '@storybook/addon-designs/preview'; +// import * as test from '@storybook/experimental-addon-test/preview'; +import * as a11y from '@storybook/addon-a11y/preview'; +// @ts-expect-error Must be typed +import * as essentials from '@storybook/addon-essentials/preview'; +// @ts-expect-error Must be typed +import * as addonThemes from '@storybook/addon-themes/preview'; + +import * as addonsPreview from '../addons/toolbars/template/stories/preview'; +import * as templatePreview from '../core/template/stories/preview'; import { DocsPageWrapper } from '../lib/blocks/src/components'; +import '../renderers/react/template/components/index'; import { isChromatic } from './isChromatic'; const { document } = global; @@ -362,6 +375,7 @@ const parameters = { }; export const config = defineConfig({ + addons: [addonThemes, essentials, a11y, addonsPreview, templatePreview], parameters, decorators, loaders, diff --git a/code/core/template/stories/preview.ts b/code/core/template/stories/preview.ts index bba2716864bc..23eb1b8ec26e 100644 --- a/code/core/template/stories/preview.ts +++ b/code/core/template/stories/preview.ts @@ -1,5 +1,6 @@ /* eslint-disable no-underscore-dangle */ import type { PartialStoryFn, StoryContext } from '@storybook/core/types'; +import type { ReactRenderer } from '@storybook/react/src'; declare global { interface Window { @@ -30,7 +31,7 @@ export const parameters = { export const loaders = [async () => ({ projectValue: 2 })]; -const testProjectDecorator = (storyFn: PartialStoryFn, context: StoryContext) => { +const testProjectDecorator = (storyFn: PartialStoryFn, context: StoryContext) => { if (context.parameters.useProjectDecorator) { return storyFn({ args: { ...context.args, text: `project ${context.args.text}` } }); } From 3eead181994fc07eee0755c7b5a2ece49fd2cd27 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 7 Jan 2025 12:01:52 +0100 Subject: [PATCH 019/144] Csf Tools: Allow ConfigFile to create new import types --- code/core/src/csf-tools/ConfigFile.test.ts | 18 +++++ code/core/src/csf-tools/ConfigFile.ts | 84 +++++++++++++++------- 2 files changed, 78 insertions(+), 24 deletions(-) diff --git a/code/core/src/csf-tools/ConfigFile.test.ts b/code/core/src/csf-tools/ConfigFile.test.ts index b6895ac323f3..18b2365f25ec 100644 --- a/code/core/src/csf-tools/ConfigFile.test.ts +++ b/code/core/src/csf-tools/ConfigFile.test.ts @@ -1242,6 +1242,24 @@ describe('ConfigFile', () => { export default config; `); }); + + it(`supports setting a namespaced import`, () => { + const config = loadConfig('').parse(); + config.setImport({ namespace: 'path' }, 'path'); + + const parsed = babelPrint(config._ast); + + expect(parsed).toMatchInlineSnapshot(`import * as path from 'path';`); + }); + + it(`supports setting import without specifier`, () => { + const config = loadConfig('').parse(); + config.setImport(null, 'path'); + + const parsed = babelPrint(config._ast); + + expect(parsed).toMatchInlineSnapshot(`import 'path';`); + }); }); describe('setRequireImport', () => { diff --git a/code/core/src/csf-tools/ConfigFile.ts b/code/core/src/csf-tools/ConfigFile.ts index 8d1e11f295c4..60bea05b56dd 100644 --- a/code/core/src/csf-tools/ConfigFile.ts +++ b/code/core/src/csf-tools/ConfigFile.ts @@ -817,16 +817,21 @@ export class ConfigFile { * * // import foo from 'bar'; * setImport('foo', 'bar'); + * + * // import * as foo from 'bar'; + * setImport({ namespace: 'foo' }, 'bar'); + * + * // import 'bar'; + * setImport(null, 'bar'); * ``` * * @param importSpecifiers - The import specifiers to set. If a string is passed in, a default * import will be set. Otherwise, an array of named imports will be set * @param fromImport - The module to import from */ - setImport(importSpecifier: string[] | string, fromImport: string) { + setImport(importSpecifier: string[] | string | { namespace: string } | null, fromImport: string) { const getNewImportSpecifier = (specifier: string) => t.importSpecifier(t.identifier(specifier), t.identifier(specifier)); - /** * Returns true, when the given import declaration has the given import specifier * @@ -855,26 +860,41 @@ export class ConfigFile { * hasImportSpecifier(declaration, 'foo'); * ``` */ + const hasNamespaceImportSpecifier = (declaration: t.ImportDeclaration, name: string) => + declaration.specifiers.find( + (specifier) => + t.isImportNamespaceSpecifier(specifier) && + t.isIdentifier(specifier.local) && + specifier.local.name === name + ); + + /** Returns true when the given import declaration has a default import specifier */ const hasDefaultImportSpecifier = (declaration: t.ImportDeclaration, name: string) => - declaration.specifiers.find((specifier) => t.isImportDefaultSpecifier(specifier)); + declaration.specifiers.find( + (specifier) => + t.isImportDefaultSpecifier(specifier) && + t.isIdentifier(specifier.local) && + specifier.local.name === name + ); const importDeclaration = this._ast.program.body.find( (node) => t.isImportDeclaration(node) && node.source.value === fromImport ) as t.ImportDeclaration | undefined; - // if the import specifier is a string, we're dealing with default imports - if (typeof importSpecifier === 'string') { - // If the import declaration with the given source exists + // Handle side-effect imports (e.g., import 'foo') + if (importSpecifier === null) { + if (!importDeclaration) { + this._ast.program.body.unshift(t.importDeclaration([], t.stringLiteral(fromImport))); + } + // Handle default imports e.g. import foo from 'bar' + } else if (typeof importSpecifier === 'string') { if (importDeclaration) { if (!hasDefaultImportSpecifier(importDeclaration, importSpecifier)) { - // If the import declaration hasn'types a default specifier, we add it importDeclaration.specifiers.push( t.importDefaultSpecifier(t.identifier(importSpecifier)) ); } - // If the import declaration with the given source doesn'types exist } else { - // Add the import declaration to the top of the file this._ast.program.body.unshift( t.importDeclaration( [t.importDefaultSpecifier(t.identifier(importSpecifier))], @@ -882,22 +902,38 @@ export class ConfigFile { ) ); } - // if the import specifier is an array, we're dealing with named imports - } else if (importDeclaration) { - importSpecifier.forEach((specifier) => { - if (!hasImportSpecifier(importDeclaration, specifier)) { - importDeclaration.specifiers.push(getNewImportSpecifier(specifier)); + // Handle named imports e.g. import { foo } from 'bar' + } else if (Array.isArray(importSpecifier)) { + if (importDeclaration) { + importSpecifier.forEach((specifier) => { + if (!hasImportSpecifier(importDeclaration, specifier)) { + importDeclaration.specifiers.push(getNewImportSpecifier(specifier)); + } + }); + } else { + this._ast.program.body.unshift( + t.importDeclaration( + importSpecifier.map(getNewImportSpecifier), + t.stringLiteral(fromImport) + ) + ); + } + // Handle namespace imports e.g. import * as foo from 'bar' + } else if (importSpecifier.namespace) { + if (importDeclaration) { + if (!hasNamespaceImportSpecifier(importDeclaration, importSpecifier.namespace)) { + importDeclaration.specifiers.push( + t.importNamespaceSpecifier(t.identifier(importSpecifier.namespace)) + ); } - }); - } else { - this._ast.program.body.unshift( - t.importDeclaration( - importSpecifier.map((specifier) => - t.importSpecifier(t.identifier(specifier), t.identifier(specifier)) - ), - t.stringLiteral(fromImport) - ) - ); + } else { + this._ast.program.body.unshift( + t.importDeclaration( + [t.importNamespaceSpecifier(t.identifier(importSpecifier.namespace))], + t.stringLiteral(fromImport) + ) + ); + } } } } From 5efce1ea1332b6fd2fa83ebc2609bee1aae405c1 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 7 Jan 2025 12:17:22 +0100 Subject: [PATCH 020/144] fix sandbox annotations for react-vite --- .../template/stories/csf4.stories.tsx | 4 +-- scripts/tasks/sandbox-parts.ts | 33 ++++++++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/code/frameworks/react-vite/template/stories/csf4.stories.tsx b/code/frameworks/react-vite/template/stories/csf4.stories.tsx index 40bf4bcaf956..7ae4ee5b095c 100644 --- a/code/frameworks/react-vite/template/stories/csf4.stories.tsx +++ b/code/frameworks/react-vite/template/stories/csf4.stories.tsx @@ -3,10 +3,8 @@ import React from 'react'; import { config } from '#.storybook/preview'; // eslint-disable-next-line storybook/default-exports -const Button = ({ label }) => ; - const meta = config.meta({ - component: Button, + component: globalThis.Components.Button, args: { label: 'Hello world!', }, diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 6005a1ec7525..60c4479294be 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -825,12 +825,43 @@ export const extendPreview: Task['run'] = async ({ template, sandboxDir }) => { if (template.expected.framework === '@storybook/react-vite') { // add CSF4 style config previewConfig.setImport(['defineConfig'], '@storybook/react/preview'); + // and all of the addons/previewAnnotations that are needed + previewConfig.setImport(null, '../src/stories/components'); + previewConfig.setImport({ namespace: 'addonA11yAnnotations' }, '@storybook/addon-a11y/preview'); + previewConfig.setImport( + { namespace: 'addonActionsAnnotations' }, + '@storybook/addon-actions/preview' + ); + previewConfig.setImport( + { namespace: 'addonTestAnnotations' }, + '@storybook/experimental-addon-test/preview' + ); + previewConfig.setImport({ namespace: 'coreAnnotations' }, '../template-stories/core/preview'); + previewConfig.setImport( + { namespace: 'toolbarAnnotations' }, + '../template-stories/addons/toolbars/preview' + ); + previewConfig.setBodyDeclaration( t.exportNamedDeclaration( t.variableDeclaration('const', [ t.variableDeclarator( t.identifier('config'), - t.callExpression(t.identifier('defineConfig'), [t.identifier('preview')]) + t.callExpression(t.identifier('defineConfig'), [ + t.objectExpression([ + t.spreadElement(t.identifier('preview')), + t.objectProperty( + t.identifier('addons'), + t.arrayExpression([ + t.identifier('addonA11yAnnotations'), + t.identifier('addonActionsAnnotations'), + t.identifier('addonTestAnnotations'), + t.identifier('coreAnnotations'), + t.identifier('toolbarAnnotations'), + ]) + ), + ]), + ]) ), ]), [] From dd5d1be9bf90f30ecc2a06166e4a3cfe569be29b Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 7 Jan 2025 12:17:41 +0100 Subject: [PATCH 021/144] fix test snapshots --- code/core/src/core-server/utils/StoryIndexGenerator.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/core/src/core-server/utils/StoryIndexGenerator.test.ts b/code/core/src/core-server/utils/StoryIndexGenerator.test.ts index f199c05cfe7f..7d7b1474b2e3 100644 --- a/code/core/src/core-server/utils/StoryIndexGenerator.test.ts +++ b/code/core/src/core-server/utils/StoryIndexGenerator.test.ts @@ -95,6 +95,7 @@ describe('StoryIndexGenerator', () => { expect(stats).toMatchInlineSnapshot(` { "beforeEach": 0, + "factory": 0, "globals": 0, "loaders": 0, "moduleMock": 0, @@ -463,6 +464,7 @@ describe('StoryIndexGenerator', () => { expect(stats).toMatchInlineSnapshot(` { "beforeEach": 1, + "factory": 0, "globals": 0, "loaders": 1, "moduleMock": 0, @@ -728,6 +730,7 @@ describe('StoryIndexGenerator', () => { expect(stats).toMatchInlineSnapshot(` { "beforeEach": 1, + "factory": 0, "globals": 0, "loaders": 1, "moduleMock": 0, From b70fdccd6e13304561fd5abfdf888fc19fb5c154 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 7 Jan 2025 12:18:25 +0100 Subject: [PATCH 022/144] Add essentials preview entry --- code/.storybook/preview.tsx | 1 + code/addons/essentials/package.json | 6 ++++++ code/addons/essentials/src/preview.ts | 12 ++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 code/addons/essentials/src/preview.ts diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index b7f174196027..87721593370d 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -1,3 +1,4 @@ +/* eslint-disable import/namespace */ import * as React from 'react'; import { Fragment, useEffect } from 'react'; diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json index eb43c219ea1b..5093579e1be5 100644 --- a/code/addons/essentials/package.json +++ b/code/addons/essentials/package.json @@ -27,6 +27,11 @@ "import": "./dist/index.mjs", "require": "./dist/index.js" }, + "./preview": { + "types": "./dist/preview.d.ts", + "import": "./dist/preview.mjs", + "require": "./dist/preview.js" + }, "./actions/preview": { "types": "./dist/actions/preview.d.ts", "import": "./dist/actions/preview.mjs", @@ -129,6 +134,7 @@ "./src/viewport/manager.ts" ], "previewEntries": [ + "./src/preview.ts", "./src/actions/preview.ts", "./src/backgrounds/preview.ts", "./src/docs/preview.ts", diff --git a/code/addons/essentials/src/preview.ts b/code/addons/essentials/src/preview.ts new file mode 100644 index 000000000000..5e765d953586 --- /dev/null +++ b/code/addons/essentials/src/preview.ts @@ -0,0 +1,12 @@ +/* eslint-disable import/namespace */ +import { composeConfigs } from 'storybook/internal/preview-api'; + +import * as actions from './actions/preview'; +import * as backgrounds from './backgrounds/preview'; +import * as docs from './docs/preview'; +import * as highlight from './highlight/preview'; +import * as measure from './measure/preview'; +import * as outline from './outline/preview'; +import * as viewport from './viewport/preview'; + +export default composeConfigs([actions, docs, backgrounds, viewport, measure, outline, highlight]); From 1f3600e98c2e5a04ac9f601c29c557d35b429eae Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 7 Jan 2025 12:45:11 +0100 Subject: [PATCH 023/144] fix portable stories annotations --- code/addons/test/src/vitest-plugin/test-utils.ts | 4 ++-- code/renderers/react/src/preview.tsx | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/code/addons/test/src/vitest-plugin/test-utils.ts b/code/addons/test/src/vitest-plugin/test-utils.ts index abd6fd0bbdd8..95233e3b0a3f 100644 --- a/code/addons/test/src/vitest-plugin/test-utils.ts +++ b/code/addons/test/src/vitest-plugin/test-utils.ts @@ -26,10 +26,10 @@ export const testStory = ( ) => { return async (context: TestContext & TaskContext & { story: ComposedStoryFn }) => { const composedStory = composeStory( - story, + 'isCSFFactory' in story ? (story as any).annotations : story, 'isCSFFactory' in story ? (meta as any).annotations : meta, { initialGlobals: (await getInitialGlobals?.()) ?? {} }, - undefined, + 'isCSFFactory' in story ? (story as any).config?.annotations : undefined, exportName ); diff --git a/code/renderers/react/src/preview.tsx b/code/renderers/react/src/preview.tsx index a43f89a59587..ee89b30b1f9d 100644 --- a/code/renderers/react/src/preview.tsx +++ b/code/renderers/react/src/preview.tsx @@ -30,7 +30,7 @@ class PreviewConfig { constructor(data: PreviewConfigData) { const { addons, ...rest } = data; - this.annotations = composeConfigs([rest, ...(addons ?? [])]); + this.annotations = composeConfigs([...(addons ?? []), rest]); } readonly meta = , TMetaArgs extends Args>( @@ -64,9 +64,7 @@ class Story { public annotations: StoryAnnotations, public meta: Meta, public config: PreviewConfig - ) { - Object.assign(this, annotations); - } + ) {} readonly isCSFFactory = true; } From 03a8b9ccdba43ebcad86af04e1aedd24e4b19faf Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 7 Jan 2025 15:58:18 +0100 Subject: [PATCH 024/144] fix portable stories kitchen sink warning --- .../react/.storybook/main.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/test-storybooks/portable-stories-kitchen-sink/react/.storybook/main.ts b/test-storybooks/portable-stories-kitchen-sink/react/.storybook/main.ts index fff0f008dd37..c55997f2b59e 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/.storybook/main.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/.storybook/main.ts @@ -17,6 +17,13 @@ const config: StorybookConfig = { }, viteFinal: (config) => ({ ...config, + resolve: { + ...config.resolve, + alias: { + ...config.resolve?.alias, + 'test-alias': join(__dirname, 'aliased.ts'), + }, + }, optimizeDeps: { ...config.optimizeDeps, include: [ @@ -34,17 +41,5 @@ const config: StorybookConfig = { } `, staticDirs: [{ from: './test-static-dirs', to:'test-static-dirs' }], - viteFinal: (config) => { - return { - ...config, - resolve: { - ...config.resolve, - alias: { - ...config.resolve?.alias, - 'test-alias': join(__dirname, 'aliased.ts'), - }, - } - }; - }, }; export default config; From b1bd157fd80ebabb5b6d37f483e9e5d14ce69e53 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 7 Jan 2025 17:02:38 +0100 Subject: [PATCH 025/144] fix portable stories tests --- code/.storybook/storybook.setup.ts | 17 +------ .../modules/store/csf/portable-stories.ts | 49 ++++++++++++------- .../portable-stories-factory.test.tsx.snap | 4 +- .../portable-stories-factory.test.tsx | 37 +++++++------- 4 files changed, 54 insertions(+), 53 deletions(-) diff --git a/code/.storybook/storybook.setup.ts b/code/.storybook/storybook.setup.ts index ce62499fa0a6..8408bfe53160 100644 --- a/code/.storybook/storybook.setup.ts +++ b/code/.storybook/storybook.setup.ts @@ -3,25 +3,12 @@ import { beforeAll, vi, expect as vitestExpect } from 'vitest'; import { setProjectAnnotations } from '@storybook/react'; import { userEvent as storybookEvent, expect as storybookExpect } from '@storybook/test'; -// eslint-disable-next-line import/namespace -import * as testAnnotations from '@storybook/experimental-addon-test/preview'; - -import * as a11yAddonAnnotations from '@storybook/addon-a11y/preview'; - -import * as coreAnnotations from '../addons/toolbars/template/stories/preview'; -import * as componentAnnotations from '../core/template/stories/preview'; -// register global components used in many stories -import '../renderers/react/template/components'; -import * as projectAnnotations from './preview'; +import { config } from './preview'; vi.spyOn(console, 'warn').mockImplementation((...args) => console.log(...args)); const annotations = setProjectAnnotations([ - a11yAddonAnnotations, - projectAnnotations, - componentAnnotations, - coreAnnotations, - testAnnotations, + config.annotations, { // experiment with injecting Vitest's interactivity API over our userEvent while tests run in browser mode // https://vitest.dev/guide/browser/interactivity-api.html 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 c843826609eb..88bf9a4eee92 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 @@ -89,6 +89,21 @@ export function setProjectAnnotations( const cleanups: CleanupCallback[] = []; +export function getAnnotations( + story: LegacyStoryAnnotationsOrFn, + meta?: ComponentAnnotations, + projectAnnotations?: ProjectAnnotations +) { + const isCsfFactory = + (typeof story === 'function' || typeof story === 'object') && 'isCSFFactory' in story; + + return { + storyAnnotations: isCsfFactory ? (story as any)?.annotations : story, + componentAnnotations: isCsfFactory ? (story as any)?.meta?.annotations : meta, + projectAnnotations: isCsfFactory ? (story as any)?.config?.annotations : projectAnnotations, + }; +} + export function composeStory( storyAnnotations: LegacyStoryAnnotationsOrFn, componentAnnotations: ComponentAnnotations, @@ -277,26 +292,24 @@ export function composeStories( ) { const { default: metaExport, __esModule, __namedExportsOrder, ...stories } = storiesImport; let meta = metaExport; - const firstStory = Object.values(stories)[0] as any; - if (!meta && 'isCSFFactory' in firstStory) { - meta = firstStory.meta.annotations; - } - const composedStories = Object.entries(stories).reduce((storiesMap, [exportsName, story]) => { - if (!isExportStory(exportsName, meta)) { - return storiesMap; - } + const composedStories = Object.entries(stories).reduce( + (storiesMap, [exportsName, story]: [string, any]) => { + const { storyAnnotations, componentAnnotations } = getAnnotations(story); + if (!meta && componentAnnotations) { + meta = componentAnnotations; + } - const result = Object.assign(storiesMap, { - [exportsName]: composeStoryFn( - story as LegacyStoryAnnotationsOrFn, - meta, - globalConfig, - exportsName - ), - }); - return result; - }, {}); + if (!isExportStory(exportsName, meta)) { + return storiesMap; + } + const result = Object.assign(storiesMap, { + [exportsName]: composeStoryFn(storyAnnotations, meta, globalConfig, exportsName), + }); + return result; + }, + {} + ); return composedStories; } diff --git a/code/renderers/react/src/__test__/__snapshots__/portable-stories-factory.test.tsx.snap b/code/renderers/react/src/__test__/__snapshots__/portable-stories-factory.test.tsx.snap index 3f00ff746281..32b527376ecd 100644 --- a/code/renderers/react/src/__test__/__snapshots__/portable-stories-factory.test.tsx.snap +++ b/code/renderers/react/src/__test__/__snapshots__/portable-stories-factory.test.tsx.snap @@ -51,9 +51,7 @@ exports[`Renders CSF3ButtonWithRender story 1`] = ` + /> diff --git a/code/renderers/react/src/__test__/portable-stories-factory.test.tsx b/code/renderers/react/src/__test__/portable-stories-factory.test.tsx index 7ad6669c2026..a63b4fb029a1 100644 --- a/code/renderers/react/src/__test__/portable-stories-factory.test.tsx +++ b/code/renderers/react/src/__test__/portable-stories-factory.test.tsx @@ -21,7 +21,7 @@ import * as ButtonStories from './Button.csf4.stories'; import * as ComponentWithErrorStories from './ComponentWithError.stories'; const HooksStory = composeStory( - ButtonStories.HooksStory, + ButtonStories.HooksStory.annotations, ButtonStories.CSF3Primary.meta.annotations ); @@ -42,7 +42,7 @@ afterEach(() => { // example with composeStory, returns a single story composed with args/decorators const Secondary = composeStory( - ButtonStories.CSF2Secondary, + ButtonStories.CSF2Secondary.annotations, ButtonStories.CSF3Primary.meta.annotations ); describe('renders', () => { @@ -108,7 +108,7 @@ describe('projectAnnotations', () => { }, ]); const WithEnglishText = composeStory( - ButtonStories.CSF2StoryWithLocale, + ButtonStories.CSF2StoryWithLocale.annotations, ButtonStories.CSF3Primary.meta.annotations ); const { getByText } = render(); @@ -119,7 +119,7 @@ describe('projectAnnotations', () => { it('renders with custom projectAnnotations via composeStory params', () => { const WithPortugueseText = composeStory( - ButtonStories.CSF2StoryWithLocale, + ButtonStories.CSF2StoryWithLocale.annotations, ButtonStories.CSF3Primary.meta.annotations, { initialGlobals: { locale: 'pt' }, @@ -132,7 +132,7 @@ describe('projectAnnotations', () => { it('has action arg from argTypes when addon-actions annotations are added', () => { const Story = composeStory( - ButtonStories.WithActionArgType, + ButtonStories.WithActionArgType.annotations, ButtonStories.CSF3Primary.meta.annotations, addonActionsPreview as ProjectAnnotations ); @@ -143,7 +143,7 @@ describe('projectAnnotations', () => { describe('CSF3', () => { it('renders with inferred globalRender', () => { const Primary = composeStory( - ButtonStories.CSF3Button, + ButtonStories.CSF3Button.annotations, ButtonStories.CSF3Primary.meta.annotations ); @@ -154,7 +154,7 @@ describe('CSF3', () => { it('renders with custom render function', () => { const Primary = composeStory( - ButtonStories.CSF3ButtonWithRender, + ButtonStories.CSF3ButtonWithRender.annotations, ButtonStories.CSF3Primary.meta.annotations ); @@ -164,7 +164,7 @@ describe('CSF3', () => { it('renders with play function without canvas element', async () => { const CSF3InputFieldFilled = composeStory( - ButtonStories.CSF3InputFieldFilled, + ButtonStories.CSF3InputFieldFilled.annotations, ButtonStories.CSF3Primary.meta.annotations ); await CSF3InputFieldFilled.run(); @@ -175,19 +175,22 @@ describe('CSF3', () => { it('renders with play function with canvas element', async () => { const CSF3InputFieldFilled = composeStory( - ButtonStories.CSF3InputFieldFilled, + ButtonStories.CSF3InputFieldFilled.annotations, ButtonStories.CSF3Primary.meta.annotations ); - const div = document.createElement('div'); - document.body.appendChild(div); + let divElement; + try { + divElement = document.createElement('div'); + document.body.appendChild(divElement); - await CSF3InputFieldFilled.run({ canvasElement: div }); + await CSF3InputFieldFilled.run({ canvasElement: divElement }); - const input = screen.getByTestId('input') as HTMLInputElement; - expect(input.value).toEqual('Hello world!'); - - document.body.removeChild(div); + const input = screen.getByTestId('input') as HTMLInputElement; + expect(input.value).toEqual('Hello world!'); + } finally { + document.body.removeChild(divElement); + } }); it('renders with hooks', async () => { @@ -201,7 +204,7 @@ describe('CSF3', () => { // common in addons that need to communicate between manager and preview it('should pass with decorators that need addons channel', () => { const PrimaryWithChannels = composeStory( - ButtonStories.CSF3Primary, + ButtonStories.CSF3Primary.annotations, ButtonStories.CSF3Primary.meta.annotations, { decorators: [ From ea5b8cb7c7f8b805f56ce2001bca58931638fbb1 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 7 Jan 2025 17:03:02 +0100 Subject: [PATCH 026/144] use globalThis to access feature flags --- code/addons/actions/src/runtime/action.ts | 2 +- code/addons/backgrounds/src/preview.ts | 8 +++++--- code/addons/viewport/src/preview.ts | 3 +-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/code/addons/actions/src/runtime/action.ts b/code/addons/actions/src/runtime/action.ts index 6fea0cb90a74..81e6d6002634 100644 --- a/code/addons/actions/src/runtime/action.ts +++ b/code/addons/actions/src/runtime/action.ts @@ -74,7 +74,7 @@ export function action(name: string, options: ActionOptions = {}): HandlerFuncti ); if (storyRenderer) { - const deprecated = !window?.FEATURES?.disallowImplicitActionsInRenderV8; + const deprecated = !globalThis?.FEATURES?.disallowImplicitActionsInRenderV8; const error = new ImplicitActionsDuringRendering({ phase: storyRenderer.phase!, name, diff --git a/code/addons/backgrounds/src/preview.ts b/code/addons/backgrounds/src/preview.ts index 212a88c0bec2..43d590a8f55b 100644 --- a/code/addons/backgrounds/src/preview.ts +++ b/code/addons/backgrounds/src/preview.ts @@ -7,7 +7,7 @@ import { withBackground } from './legacy/withBackgroundLegacy'; import { withGrid } from './legacy/withGridLegacy'; import type { Config, GlobalState } from './types'; -export const decorators: Addon_DecoratorFunction[] = FEATURES?.backgroundsStoryGlobals +export const decorators: Addon_DecoratorFunction[] = globalThis.FEATURES?.backgroundsStoryGlobals ? [withBackgroundAndGrid] : [withGrid, withBackground]; @@ -20,7 +20,7 @@ export const parameters = { }, disable: false, // TODO: remove in 9.0 - ...(!FEATURES?.backgroundsStoryGlobals && { + ...(!globalThis.FEATURES?.backgroundsStoryGlobals && { values: Object.values(DEFAULT_BACKGROUNDS), }), } satisfies Partial, @@ -30,4 +30,6 @@ const modern: Record = { [KEY]: { value: undefined, grid: false }, }; -export const initialGlobals = FEATURES?.backgroundsStoryGlobals ? modern : { [KEY]: null }; +export const initialGlobals = globalThis.FEATURES?.backgroundsStoryGlobals + ? modern + : { [KEY]: null }; diff --git a/code/addons/viewport/src/preview.ts b/code/addons/viewport/src/preview.ts index 9c8ddc9cfb0e..448909996c78 100644 --- a/code/addons/viewport/src/preview.ts +++ b/code/addons/viewport/src/preview.ts @@ -1,5 +1,4 @@ import { PARAM_KEY as KEY } from './constants'; -import { MINIMAL_VIEWPORTS } from './defaults'; import type { GlobalState } from './types'; const modern: Record = { @@ -9,4 +8,4 @@ const modern: Record = { // TODO: remove in 9.0 const legacy = { viewport: 'reset', viewportRotated: false }; -export const initialGlobals = FEATURES?.viewportStoryGlobals ? modern : legacy; +export const initialGlobals = globalThis.FEATURES?.viewportStoryGlobals ? modern : legacy; From 03f8b11bf3d79e7812ae9f2f8857eb7d22a9e3af Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 7 Jan 2025 17:57:09 +0100 Subject: [PATCH 027/144] fix Storybook build with temporary workaround --- code/.storybook/preview.tsx | 4 ++-- code/addons/essentials/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index 87721593370d..2c3fed68bf20 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -25,8 +25,8 @@ import { defineConfig } from '@storybook/react/preview'; // import * as designs from '@storybook/addon-designs/preview'; // import * as test from '@storybook/experimental-addon-test/preview'; import * as a11y from '@storybook/addon-a11y/preview'; -// @ts-expect-error Must be typed -import * as essentials from '@storybook/addon-essentials/preview'; +// @ts-expect-error TODO: @kasperpeulen investigate the issue with /preview entrypoint +import * as essentials from '@storybook/addon-essentials/preview-TODO-INVESTIGATE-THIS'; // @ts-expect-error Must be typed import * as addonThemes from '@storybook/addon-themes/preview'; diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json index 5093579e1be5..6549b2f6eeb3 100644 --- a/code/addons/essentials/package.json +++ b/code/addons/essentials/package.json @@ -27,7 +27,7 @@ "import": "./dist/index.mjs", "require": "./dist/index.js" }, - "./preview": { + "./preview-TODO-INVESTIGATE-THIS": { "types": "./dist/preview.d.ts", "import": "./dist/preview.mjs", "require": "./dist/preview.js" From 07dcd780bf655a3f713f6a2bfba5fee29a40fff9 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 8 Jan 2025 08:44:34 +0100 Subject: [PATCH 028/144] do not use csf4 in bench sandboxes --- scripts/tasks/sandbox-parts.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 60c4479294be..b0d576a9edbe 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -822,7 +822,10 @@ export const extendPreview: Task['run'] = async ({ template, sandboxDir }) => { logger.log('📝 Extending preview.js'); const previewConfig = await readConfig({ cwd: sandboxDir, fileName: 'preview' }); - if (template.expected.framework === '@storybook/react-vite') { + if ( + template.expected.framework === '@storybook/react-vite' && + !template.skipTasks.includes('vitest-integration') + ) { // add CSF4 style config previewConfig.setImport(['defineConfig'], '@storybook/react/preview'); // and all of the addons/previewAnnotations that are needed From e0ef30ecf30b3a2d4f7f06c24ee4e913ffa3c29f Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 8 Jan 2025 12:43:26 +0100 Subject: [PATCH 029/144] temporary attempt at fixing part of the E2E tests --- code/e2e-tests/util.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/code/e2e-tests/util.ts b/code/e2e-tests/util.ts index d9faad22939a..bb017f54e12b 100644 --- a/code/e2e-tests/util.ts +++ b/code/e2e-tests/util.ts @@ -87,6 +87,14 @@ export class SbPage { async waitForStoryLoaded() { try { + // wait for the story to be visitedz + await this.page.waitForURL((url) => url.search.includes(`path`)); + + // TODO: Remove this once docs are actually working + if (this.page.url().includes('/docs/')) { + return; + } + const root = this.previewRoot(); // Wait until there is at least one child (a story element) in the preview iframe await root.locator(':scope > *').first().waitFor({ From 309a435cef0b29601bed2335e6b00e6cb7976a1e Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 8 Jan 2025 14:06:31 +0100 Subject: [PATCH 030/144] Change to entry-preview --- code/.storybook/preview.tsx | 4 ++-- code/addons/essentials/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index 2c3fed68bf20..fd45479f729b 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -25,8 +25,8 @@ import { defineConfig } from '@storybook/react/preview'; // import * as designs from '@storybook/addon-designs/preview'; // import * as test from '@storybook/experimental-addon-test/preview'; import * as a11y from '@storybook/addon-a11y/preview'; -// @ts-expect-error TODO: @kasperpeulen investigate the issue with /preview entrypoint -import * as essentials from '@storybook/addon-essentials/preview-TODO-INVESTIGATE-THIS'; +// @ts-expect-error Must be typed +import * as essentials from '@storybook/addon-essentials/entry-preview'; // @ts-expect-error Must be typed import * as addonThemes from '@storybook/addon-themes/preview'; diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json index 6549b2f6eeb3..7b636abef475 100644 --- a/code/addons/essentials/package.json +++ b/code/addons/essentials/package.json @@ -27,7 +27,7 @@ "import": "./dist/index.mjs", "require": "./dist/index.js" }, - "./preview-TODO-INVESTIGATE-THIS": { + "./entry-preview": { "types": "./dist/preview.d.ts", "import": "./dist/preview.mjs", "require": "./dist/preview.js" From 49353cbab659f361431e74cecb89fa3767cce865 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 8 Jan 2025 14:42:47 +0100 Subject: [PATCH 031/144] Fix addon-essentials --- code/addons/essentials/src/preview.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/code/addons/essentials/src/preview.ts b/code/addons/essentials/src/preview.ts index 5e765d953586..ed7839830d83 100644 --- a/code/addons/essentials/src/preview.ts +++ b/code/addons/essentials/src/preview.ts @@ -1,12 +1,15 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck + /* eslint-disable import/namespace */ import { composeConfigs } from 'storybook/internal/preview-api'; -import * as actions from './actions/preview'; -import * as backgrounds from './backgrounds/preview'; -import * as docs from './docs/preview'; -import * as highlight from './highlight/preview'; -import * as measure from './measure/preview'; -import * as outline from './outline/preview'; -import * as viewport from './viewport/preview'; +import * as actions from '@storybook/addon-actions/preview'; +import * as backgrounds from '@storybook/addon-backgrounds/preview'; +import * as docs from '@storybook/addon-docs/preview'; +import * as highlight from '@storybook/addon-highlight/preview'; +import * as measure from '@storybook/addon-measure/preview'; +import * as outline from '@storybook/addon-outline/preview'; +import * as viewport from '@storybook/addon-viewport/preview'; export default composeConfigs([actions, docs, backgrounds, viewport, measure, outline, highlight]); From ec0c55f8ee5594003fd088cdd0310c4aac4ea7b0 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 8 Jan 2025 15:04:58 +0100 Subject: [PATCH 032/144] Revert "Rename Docs, fix later" This reverts commit 5d05b3cdc6e61289edd0535bb19ea86e74deeb8e. --- .../core/src/components/components/Button/{Docs.TODO => Docs.mdx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename code/core/src/components/components/Button/{Docs.TODO => Docs.mdx} (100%) diff --git a/code/core/src/components/components/Button/Docs.TODO b/code/core/src/components/components/Button/Docs.mdx similarity index 100% rename from code/core/src/components/components/Button/Docs.TODO rename to code/core/src/components/components/Button/Docs.mdx From ef92a43608ca73dea810e37ea060d05794dab76e Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 8 Jan 2025 16:04:12 +0100 Subject: [PATCH 033/144] revert workaround --- code/e2e-tests/util.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/code/e2e-tests/util.ts b/code/e2e-tests/util.ts index bb017f54e12b..bd15bfa1a560 100644 --- a/code/e2e-tests/util.ts +++ b/code/e2e-tests/util.ts @@ -87,14 +87,9 @@ export class SbPage { async waitForStoryLoaded() { try { - // wait for the story to be visitedz + // wait for the story to be visited await this.page.waitForURL((url) => url.search.includes(`path`)); - // TODO: Remove this once docs are actually working - if (this.page.url().includes('/docs/')) { - return; - } - const root = this.previewRoot(); // Wait until there is at least one child (a story element) in the preview iframe await root.locator(':scope > *').first().waitFor({ From 745f1b5a31b4cf6255292075823188bcc0351e00 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 8 Jan 2025 16:36:10 +0100 Subject: [PATCH 034/144] Fix docs and make a start on typesafety --- code/.storybook/main.ts | 6 + .../components/Button/Button.stories.tsx | 240 +++++++++--------- .../src/components/components/Button/Docs.mdx | 8 +- .../preview-web/docs-context/DocsContext.ts | 4 +- code/renderers/react/src/preview.tsx | 27 +- 5 files changed, 148 insertions(+), 137 deletions(-) diff --git a/code/.storybook/main.ts b/code/.storybook/main.ts index 9870741de29e..48109681b6b0 100644 --- a/code/.storybook/main.ts +++ b/code/.storybook/main.ts @@ -160,6 +160,12 @@ const config: StorybookConfig = { // disable sourcemaps in CI to not run out of memory sourcemap: process.env.CI !== 'true', }, + server: { + watch: { + // Something odd happens with tsconfig and nx which causes Storybook to keep reloading, so we ignore them + ignored: ['**/.nx/cache/**', '**/tsconfig.json'], + }, + }, } satisfies typeof viteConfig); }, // logLevel: 'debug', diff --git a/code/core/src/components/components/Button/Button.stories.tsx b/code/core/src/components/components/Button/Button.stories.tsx index 2a88929db216..a8d8e02a4e77 100644 --- a/code/core/src/components/components/Button/Button.stories.tsx +++ b/code/core/src/components/components/Button/Button.stories.tsx @@ -64,123 +64,123 @@ export const Variants = meta.story({ ), }); -// export const Active: Story = { -// args: { -// active: true, -// children: ( -// <> -// -// Button -// -// ), -// }, -// render: (args) => ( -// -// -// -// -// ), -// }; -// -// export const Disabled: Story = { -// args: { -// disabled: true, -// children: 'Disabled Button', -// }, -// }; -// -// export const WithHref: Story = { -// render: () => ( -// -// -// -// -// ), -// }; -// -// export const Animated: Story = { -// args: { -// variant: 'outline', -// }, -// render: (args) => ( -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// ), -// }; +export const Active = meta.story({ + args: { + active: true, + children: ( + <> + + Button + + ), + }, + render: (args) => ( + + + + + ), +}); + +export const Disabled = meta.story({ + args: { + disabled: true, + children: 'Disabled Button', + }, +}); + +export const WithHref = meta.story({ + render: () => ( + + + + + ), +}); + +export const Animated = meta.story({ + args: { + variant: 'outline', + }, + render: (args) => ( + + + + + + + + + + + + + + + + + + ), +}); diff --git a/code/core/src/components/components/Button/Docs.mdx b/code/core/src/components/components/Button/Docs.mdx index 3872c9a3fab0..83dbb3c95a2a 100644 --- a/code/core/src/components/components/Button/Docs.mdx +++ b/code/core/src/components/components/Button/Docs.mdx @@ -28,10 +28,10 @@ import { FaceHappyIcon, HeartIcon } from '@storybook/icons' // Using the onClick event handler -// Using the asChild prop to render a custom child - + // Using the asChild prop to render a custom child + `} /> diff --git a/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts b/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts index 6e39643859db..0f26f4f04edc 100644 --- a/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts +++ b/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts @@ -162,7 +162,9 @@ export class DocsContext implements DocsContextProps return { type: 'meta', csfFile } as TResolvedExport; } - const story = this.exportToStory.get(moduleExportOrType); + const story = this.exportToStory.get( + 'isCSFFactory' in moduleExportOrType ? moduleExportOrType.annotations : moduleExportOrType + ); if (story) { return { type: 'story', story } as TResolvedExport; diff --git a/code/renderers/react/src/preview.tsx b/code/renderers/react/src/preview.tsx index ee89b30b1f9d..3e18660abe6f 100644 --- a/code/renderers/react/src/preview.tsx +++ b/code/renderers/react/src/preview.tsx @@ -1,4 +1,4 @@ -import type { ComponentType } from 'react'; +import type { ComponentProps, ComponentType } from 'react'; import { composeConfigs } from 'storybook/internal/preview-api'; import type { @@ -10,6 +10,8 @@ import type { StoryAnnotations, } from 'storybook/internal/types'; +import type { SetOptional } from 'type-fest'; + import * as reactAnnotations from './entry-preview'; import * as reactDocsAnnotations from './entry-preview-docs'; import type { ReactRenderer } from './types'; @@ -33,36 +35,37 @@ class PreviewConfig { this.annotations = composeConfigs([...(addons ?? []), rest]); } - readonly meta = , TMetaArgs extends Args>( + readonly meta = < + TComponent extends ComponentType, + TMetaArgs extends Partial>, + >( meta: ComponentAnnotations & { component: TComponent; args: TMetaArgs } ) => { - return new Meta(meta as ComponentAnnotations, this); + return new Meta, TMetaArgs>(meta, this); }; readonly isCSFFactoryPreview = true; } -class Meta { +class Meta { readonly annotations: ComponentAnnotations; readonly config: PreviewConfig; - constructor( - annotations: ComponentAnnotations, - config: PreviewConfig - ) { + constructor(annotations: ComponentAnnotations, config: PreviewConfig) { this.annotations = annotations; this.config = config; } - readonly story = (story: StoryAnnotations) => - new Story(story, this, this.config); + readonly story = ( + story: StoryAnnotations> + ) => new Story(story as any, this, this.config); } -class Story { +class Story { constructor( public annotations: StoryAnnotations, - public meta: Meta, + public meta: Meta, public config: PreviewConfig ) {} From f59916dac2d6a1848b3e4565f496a7a2c355820a Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Thu, 9 Jan 2025 11:43:44 +0100 Subject: [PATCH 035/144] use correct setup file in csf4 sandbox --- scripts/tasks/sandbox-parts.ts | 71 +++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index b0d576a9edbe..2834a2ac97a0 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -447,33 +447,50 @@ export async function setupVitest(details: TemplateDetails, options: PassedOptio : template.expected.renderer; const viteConfigPath = template.name.includes('JavaScript') ? 'vite.config.js' : 'vite.config.ts'; - await writeFile( - join(sandboxDir, '.storybook/vitest.setup.ts'), - dedent`import { beforeAll } from 'vitest' - import { setProjectAnnotations } from '${storybookPackage}' - import * as rendererDocsAnnotations from '${template.expected.renderer}/dist/entry-preview-docs.mjs' - import * as addonA11yAnnotations from '@storybook/addon-a11y/preview' - import * as addonActionsAnnotations from '@storybook/addon-actions/preview' - import * as addonTestAnnotations from '@storybook/experimental-addon-test/preview' - import '../src/stories/components' - import * as coreAnnotations from '../template-stories/core/preview' - import * as toolbarAnnotations from '../template-stories/addons/toolbars/preview' - import * as projectAnnotations from './preview' - ${isVue ? 'import * as vueAnnotations from "../src/stories/renderers/vue3/preview.js"' : ''} - - const annotations = setProjectAnnotations([ - ${isVue ? 'vueAnnotations,' : ''} - rendererDocsAnnotations, - coreAnnotations, - toolbarAnnotations, - addonActionsAnnotations, - addonTestAnnotations, - addonA11yAnnotations, - projectAnnotations, - ]) - - beforeAll(annotations.beforeAll)` - ); + const setupFilePath = join(sandboxDir, '.storybook/vitest.setup.ts'); + + const shouldUseCsf4 = template.expected.framework === '@storybook/react-vite'; + if (shouldUseCsf4) { + await writeFile( + setupFilePath, + dedent`import { beforeAll } from 'vitest' + import { setProjectAnnotations } from '${storybookPackage}' + import * as projectAnnotations from './preview' + + // setProjectAnnotations still kept to support non-CSF4 story tests + const annotations = setProjectAnnotations(projectAnnotations.config.annotations) + beforeAll(annotations.beforeAll) + ` + ); + } else { + await writeFile( + setupFilePath, + dedent`import { beforeAll } from 'vitest' + import { setProjectAnnotations } from '${storybookPackage}' + import * as rendererDocsAnnotations from '${template.expected.renderer}/dist/entry-preview-docs.mjs' + import * as addonA11yAnnotations from '@storybook/addon-a11y/preview' + import * as addonActionsAnnotations from '@storybook/addon-actions/preview' + import * as addonTestAnnotations from '@storybook/experimental-addon-test/preview' + import '../src/stories/components' + import * as coreAnnotations from '../template-stories/core/preview' + import * as toolbarAnnotations from '../template-stories/addons/toolbars/preview' + import * as projectAnnotations from './preview' + ${isVue ? 'import * as vueAnnotations from "../src/stories/renderers/vue3/preview.js"' : ''} + + const annotations = setProjectAnnotations([ + ${isVue ? 'vueAnnotations,' : ''} + rendererDocsAnnotations, + coreAnnotations, + toolbarAnnotations, + addonActionsAnnotations, + addonTestAnnotations, + addonA11yAnnotations, + projectAnnotations, + ]) + + beforeAll(annotations.beforeAll)` + ); + } await writeFile( join(sandboxDir, 'vitest.workspace.ts'), From eb50508a3acb31fe134b59645ec777f934e2a6da Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Thu, 9 Jan 2025 12:04:03 +0100 Subject: [PATCH 036/144] fix essentials annotations --- scripts/tasks/sandbox-parts.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 2834a2ac97a0..b507bb1c10c2 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -847,6 +847,10 @@ export const extendPreview: Task['run'] = async ({ template, sandboxDir }) => { previewConfig.setImport(['defineConfig'], '@storybook/react/preview'); // and all of the addons/previewAnnotations that are needed previewConfig.setImport(null, '../src/stories/components'); + previewConfig.setImport( + { namespace: 'addonEssentialsAnnotations' }, + '@storybook/addon-essentials/entry-preview' + ); previewConfig.setImport({ namespace: 'addonA11yAnnotations' }, '@storybook/addon-a11y/preview'); previewConfig.setImport( { namespace: 'addonActionsAnnotations' }, @@ -873,6 +877,7 @@ export const extendPreview: Task['run'] = async ({ template, sandboxDir }) => { t.objectProperty( t.identifier('addons'), t.arrayExpression([ + t.identifier('addonEssentialsAnnotations'), t.identifier('addonA11yAnnotations'), t.identifier('addonActionsAnnotations'), t.identifier('addonTestAnnotations'), From ddbe8b946364de7694097a7b2f5bccd726408d85 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Thu, 9 Jan 2025 12:33:35 +0100 Subject: [PATCH 037/144] add mdx example with csf4 --- code/frameworks/react-vite/template/stories/csf4.mdx | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 code/frameworks/react-vite/template/stories/csf4.mdx diff --git a/code/frameworks/react-vite/template/stories/csf4.mdx b/code/frameworks/react-vite/template/stories/csf4.mdx new file mode 100644 index 000000000000..48c16cabb07e --- /dev/null +++ b/code/frameworks/react-vite/template/stories/csf4.mdx @@ -0,0 +1,8 @@ +import * as StoriesModule from './csf4.stories' +import { Meta, Stories } from '@storybook/blocks' + + + +# CSF4 in MDX + + \ No newline at end of file From 4127d874c1f07e345c54132d347c7464644eceba Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Thu, 9 Jan 2025 12:34:48 +0100 Subject: [PATCH 038/144] skip svelte test for now --- code/e2e-tests/addon-controls.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/e2e-tests/addon-controls.spec.ts b/code/e2e-tests/addon-controls.spec.ts index 5ae41a18b2d4..8a4050c52867 100644 --- a/code/e2e-tests/addon-controls.spec.ts +++ b/code/e2e-tests/addon-controls.spec.ts @@ -9,6 +9,10 @@ const templateName = process.env.STORYBOOK_TEMPLATE_NAME || ''; test.describe('addon-controls', () => { test('should change component when changing controls', async ({ page }) => { test.skip(templateName.includes('react-native-web'), 'React Native CSS behaves differently'); + test.skip( + templateName.includes('svelte-vite/default-ts'), + 'TODO: this is skipped until https://github.com/storybookjs/storybook/issues/30223 is resolved' + ); await page.goto(storybookUrl); const sbPage = new SbPage(page, expect); From 740c149c85347cdeb8773b9a400ab09e997b09f9 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 10 Jan 2025 07:22:14 +0100 Subject: [PATCH 039/144] Revert "skip svelte test for now" This reverts commit 4127d874c1f07e345c54132d347c7464644eceba. --- code/e2e-tests/addon-controls.spec.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/code/e2e-tests/addon-controls.spec.ts b/code/e2e-tests/addon-controls.spec.ts index 8a4050c52867..5ae41a18b2d4 100644 --- a/code/e2e-tests/addon-controls.spec.ts +++ b/code/e2e-tests/addon-controls.spec.ts @@ -9,10 +9,6 @@ const templateName = process.env.STORYBOOK_TEMPLATE_NAME || ''; test.describe('addon-controls', () => { test('should change component when changing controls', async ({ page }) => { test.skip(templateName.includes('react-native-web'), 'React Native CSS behaves differently'); - test.skip( - templateName.includes('svelte-vite/default-ts'), - 'TODO: this is skipped until https://github.com/storybookjs/storybook/issues/30223 is resolved' - ); await page.goto(storybookUrl); const sbPage = new SbPage(page, expect); From 5349a7dcf1fb034acb15eba4d8d82cfdd4ddae09 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 6 Jan 2025 13:04:20 +0100 Subject: [PATCH 040/144] Codemod: Add CSF3 to CSF4 migration --- code/lib/codemod/package.json | 8 +- .../transforms/__tests__/csf-3-to-4.test.ts | 234 ++++++++++++++++++ code/lib/codemod/src/transforms/csf-3-to-4.ts | 176 +++++++++++++ 3 files changed, 415 insertions(+), 3 deletions(-) create mode 100644 code/lib/codemod/src/transforms/__tests__/csf-3-to-4.test.ts create mode 100644 code/lib/codemod/src/transforms/csf-3-to-4.ts diff --git a/code/lib/codemod/package.json b/code/lib/codemod/package.json index 99f6ebf2f4cf..9f10a73cb353 100644 --- a/code/lib/codemod/package.json +++ b/code/lib/codemod/package.json @@ -28,6 +28,7 @@ }, "./dist/transforms/add-component-parameters.js": "./dist/transforms/add-component-parameters.js", "./dist/transforms/csf-2-to-3.js": "./dist/transforms/csf-2-to-3.js", + "./dist/transforms/csf-3-to-4.js": "./dist/transforms/csf-3-to-4.js", "./dist/transforms/csf-hoist-story-annotations.js": "./dist/transforms/csf-hoist-story-annotations.js", "./dist/transforms/find-implicit-spies.js": "./dist/transforms/find-implicit-spies.js", "./dist/transforms/move-builtin-addons.js": "./dist/transforms/move-builtin-addons.js", @@ -90,14 +91,15 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/transforms/add-component-parameters.js", + "./src/transforms/storiesof-to-csf.js", + "./src/transforms/mdx-to-csf.ts", "./src/transforms/csf-2-to-3.ts", + "./src/transforms/csf-3-to-4.ts", "./src/transforms/csf-hoist-story-annotations.js", "./src/transforms/find-implicit-spies.ts", - "./src/transforms/mdx-to-csf.ts", + "./src/transforms/add-component-parameters.js", "./src/transforms/migrate-to-test-package.ts", "./src/transforms/move-builtin-addons.js", - "./src/transforms/storiesof-to-csf.js", "./src/transforms/update-addon-info.js", "./src/transforms/update-organisation-name.js", "./src/transforms/upgrade-deprecated-types.ts", diff --git a/code/lib/codemod/src/transforms/__tests__/csf-3-to-4.test.ts b/code/lib/codemod/src/transforms/__tests__/csf-3-to-4.test.ts new file mode 100644 index 000000000000..8cf66dff1ea2 --- /dev/null +++ b/code/lib/codemod/src/transforms/__tests__/csf-3-to-4.test.ts @@ -0,0 +1,234 @@ +import { describe, expect, it } from 'vitest'; + +import { dedent } from 'ts-dedent'; + +import _transform from '../csf-3-to-4'; + +expect.addSnapshotSerializer({ + serialize: (val: any) => (typeof val === 'string' ? val : val.toString()), + test: () => true, +}); + +const transform = async (source: string) => + (await _transform({ source, path: 'Component.stories.tsx' })).trim(); + +describe('csf-3-to-4', () => { + describe('javascript', () => { + it('should wrap const declared meta', async () => { + await expect( + transform(dedent` + const meta = { title: 'Component' }; + export default meta; + `) + ).resolves.toMatchInlineSnapshot(` + import { config } from "#.storybook/preview"; + const meta = config.meta({ + title: 'Component' + }); + `); + }); + + it('should transform and wrap inline default exported meta', async () => { + await expect( + transform(dedent` + export default { title: 'Component' }; + `) + ).resolves.toMatchInlineSnapshot(` + import { config } from "#.storybook/preview"; + const meta = config.meta({ + title: 'Component' + }); + `); + }); + + it('should rename meta object to meta if it has a different name', async () => { + await expect( + transform(dedent` + const componentMeta = { title: 'Component' }; + export default componentMeta; + `) + ).resolves.toMatchInlineSnapshot(` + import { config } from "#.storybook/preview"; + const meta = config.meta({ + title: 'Component' + }); + `); + }); + + it('should wrap stories in a meta.story method', async () => { + await expect( + transform(dedent` + const componentMeta = { title: 'Component' }; + export default componentMeta; + export const A = { + args: { primary: true }, + render: (args) => + }; + `) + ).resolves.toMatchInlineSnapshot(` + import { config } from "#.storybook/preview"; + const meta = config.meta({ + title: 'Component' + }); + export const A = meta.story({ + args: { + primary: true + }, + render: args => + }); + `); + }); + + it('should respect existing config imports', async () => { + await expect( + transform(dedent` + import { decorators } from "#.storybook/preview"; + const componentMeta = { title: 'Component' }; + export default componentMeta; + export const A = { + args: { primary: true }, + render: (args) => + }; + `) + ).resolves.toMatchInlineSnapshot(` + import { decorators, config } from "#.storybook/preview"; + const meta = config.meta({ + title: 'Component' + }); + export const A = meta.story({ + args: { + primary: true + }, + render: args => + }); + `); + }); + }); + + describe('typescript', () => { + const metaSatisfies = dedent` + import { Meta, StoryObj as CSF3 } from '@storybook/react'; + import { ComponentProps } from './Component'; + + const meta = { title: 'Component', component: Component } satisfies Meta + export default meta; + + export const A: CSF3 = { + args: { primary: true } + }; + `; + it('meta satisfies syntax', async () => { + await expect(transform(metaSatisfies)).resolves.toMatchInlineSnapshot(` + import { config } from "#.storybook/preview"; + import { Meta, StoryObj as CSF3 } from '@storybook/react'; + import { ComponentProps } from './Component'; + const meta = config.meta({ + title: 'Component', + component: Component + }); + export const A = meta.story({ + args: { + primary: true + } + }); + `); + }); + + const metaAs = dedent` + import { Meta, StoryObj as CSF3 } from '@storybook/react'; + import { ComponentProps } from './Component'; + + const meta = { title: 'Component', component: Component } as Meta + export default meta; + + export const A: CSF3 = { + args: { primary: true } + }; + `; + it('meta as syntax', async () => { + await expect(transform(metaAs)).resolves.toMatchInlineSnapshot(` + import { config } from "#.storybook/preview"; + import { Meta, StoryObj as CSF3 } from '@storybook/react'; + import { ComponentProps } from './Component'; + const meta = config.meta({ + title: 'Component', + component: Component + }); + export const A = meta.story({ + args: { + primary: true + } + }); + `); + }); + + const storySatisfies = dedent` + import { Meta, StoryObj as CSF3 } from '@storybook/react'; + import { ComponentProps } from './Component'; + + const meta = { title: 'Component', component: Component } as Meta + export default meta; + + export const A = { + args: { primary: true } + } satisfies CSF3; + `; + it('story satisfies syntax', async () => { + await expect(transform(storySatisfies)).resolves.toMatchInlineSnapshot(` + import { config } from "#.storybook/preview"; + import { Meta, StoryObj as CSF3 } from '@storybook/react'; + import { ComponentProps } from './Component'; + const meta = config.meta({ + title: 'Component', + component: Component + }); + export const A = meta.story({ + args: { + primary: true + } + }); + `); + }); + + const storyAs = dedent` + import { Meta, StoryObj as CSF3 } from '@storybook/react'; + import { ComponentProps } from './Component'; + + const meta = { title: 'Component', component: Component } as Meta + export default meta; + + export const A = { + args: { primary: true } + } as CSF3; + `; + it('story as syntax', async () => { + await expect(transform(storyAs)).resolves.toMatchInlineSnapshot(` + import { config } from "#.storybook/preview"; + import { Meta, StoryObj as CSF3 } from '@storybook/react'; + import { ComponentProps } from './Component'; + const meta = config.meta({ + title: 'Component', + component: Component + }); + export const A = meta.story({ + args: { + primary: true + } + }); + `); + }); + + it('should yield the same result to all syntaxes', async () => { + const allSnippets = await Promise.all([ + transform(metaSatisfies), + transform(metaAs), + transform(storySatisfies), + transform(storyAs), + ]); + + allSnippets.forEach((result) => { + expect(result).toEqual(allSnippets[0]); + }); + }); + }); +}); diff --git a/code/lib/codemod/src/transforms/csf-3-to-4.ts b/code/lib/codemod/src/transforms/csf-3-to-4.ts new file mode 100644 index 000000000000..fd80c4ecdbd4 --- /dev/null +++ b/code/lib/codemod/src/transforms/csf-3-to-4.ts @@ -0,0 +1,176 @@ +/* eslint-disable no-underscore-dangle */ +import { isValidPreviewPath, loadCsf } from '@storybook/core/csf-tools'; + +import type { BabelFile } from '@babel/core'; +import * as babel from '@babel/core'; +import { + isIdentifier, + isImportDeclaration, + isImportSpecifier, + isObjectExpression, + isTSAsExpression, + isTSSatisfiesExpression, + isVariableDeclaration, +} from '@babel/types'; +import type { FileInfo } from 'jscodeshift'; + +export default async function transform(info: FileInfo) { + const csf = loadCsf(info.source, { makeTitle: (title) => title }); + const fileNode = csf._ast; + // @ts-expect-error File is not yet exposed, see https://github.com/babel/babel/issues/11350#issuecomment-644118606 + const file: BabelFile = new babel.File( + { filename: info.path }, + { code: info.source, ast: fileNode } + ); + + const metaVariableName = 'meta'; + + /** + * Add the preview import if it doesn't exist yet: + * + * `import { config } from '#.storybook/preview'`; + */ + const programNode = fileNode.program; + let foundConfigImport = false; + + programNode.body.forEach((node) => { + if (isImportDeclaration(node) && isValidPreviewPath(node.source.value)) { + const hasConfigSpecifier = node.specifiers.some( + (specifier) => + isImportSpecifier(specifier) && isIdentifier(specifier.imported, { name: 'config' }) + ); + + if (!hasConfigSpecifier) { + node.specifiers.push( + babel.types.importSpecifier( + babel.types.identifier('config'), + babel.types.identifier('config') + ) + ); + } + + foundConfigImport = true; + } + }); + + if (!foundConfigImport) { + const configImport = babel.types.importDeclaration( + [ + babel.types.importSpecifier( + babel.types.identifier('config'), + babel.types.identifier('config') + ), + ], + babel.types.stringLiteral('#.storybook/preview') + ); + programNode.body.unshift(configImport); + } + + file.path.traverse({ + // Meta export + ExportDefaultDeclaration: (path) => { + const declaration = path.node.declaration; + + /** + * Transform inline default export: `export default { title: 'A' };` + * + * Into a meta call: `const meta = config.meta({ title: 'A' });` + */ + if (isObjectExpression(declaration)) { + const metaVariable = babel.types.variableDeclaration('const', [ + babel.types.variableDeclarator( + babel.types.identifier(metaVariableName), + babel.types.callExpression( + babel.types.memberExpression( + babel.types.identifier('config'), + babel.types.identifier('meta') + ), + [declaration] + ) + ), + ]); + + path.replaceWith(metaVariable); + } else if (isIdentifier(declaration)) { + /** + * Transform const declared metas: + * + * `const meta = {}; export default meta;` + * + * Into a meta call: + * + * `const meta = config.meta({ title: 'A' });` + */ + const binding = path.scope.getBinding(declaration.name); + if (binding && binding.path.isVariableDeclarator()) { + const originalName = declaration.name; + + // Always rename the meta variable to 'meta' + binding.path.node.id = babel.types.identifier(metaVariableName); + + let init = binding.path.node.init; + if (isTSSatisfiesExpression(init) || isTSAsExpression(init)) { + init = init.expression; + } + if (isObjectExpression(init)) { + binding.path.node.init = babel.types.callExpression( + babel.types.memberExpression( + babel.types.identifier('config'), + babel.types.identifier('meta') + ), + [init] + ); + } + + // Update all references to the original name + path.scope.rename(originalName, metaVariableName); + } + + // Remove the default export, it's not needed anymore + path.remove(); + } + }, + // Story export + ExportNamedDeclaration: (path) => { + const declaration = path.node.declaration; + + if (!declaration || !isVariableDeclaration(declaration)) { + return; + } + + declaration.declarations.forEach((decl) => { + const id = decl.id; + let init = decl.init; + + if (isIdentifier(id) && init) { + if (isTSSatisfiesExpression(init) || isTSAsExpression(init)) { + init = init.expression; + } + + if (isObjectExpression(init)) { + const typeAnnotation = id.typeAnnotation; + // Remove type annotation as it's now inferred + if (typeAnnotation) { + id.typeAnnotation = null; + } + + // Wrap the object in `meta.story()` + decl.init = babel.types.callExpression( + babel.types.memberExpression( + babel.types.identifier(metaVariableName), + babel.types.identifier('story') + ), + [init] + ); + } + } + }); + }, + }); + + // Generate the transformed code + const { code } = babel.transformFromAstSync(fileNode, info.source, { + parserOpts: { sourceType: 'module' }, + }); + return code; +} From f4e3a62efaec6e038fd9d408235fe58e1be8bb40 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 10 Jan 2025 09:01:59 +0100 Subject: [PATCH 041/144] only apply meta related changes if there's a meta to change --- code/lib/codemod/src/transforms/csf-3-to-4.ts | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/code/lib/codemod/src/transforms/csf-3-to-4.ts b/code/lib/codemod/src/transforms/csf-3-to-4.ts index fd80c4ecdbd4..7efe6a430c06 100644 --- a/code/lib/codemod/src/transforms/csf-3-to-4.ts +++ b/code/lib/codemod/src/transforms/csf-3-to-4.ts @@ -53,22 +53,12 @@ export default async function transform(info: FileInfo) { } }); - if (!foundConfigImport) { - const configImport = babel.types.importDeclaration( - [ - babel.types.importSpecifier( - babel.types.identifier('config'), - babel.types.identifier('config') - ), - ], - babel.types.stringLiteral('#.storybook/preview') - ); - programNode.body.unshift(configImport); - } + let hasMeta = false; file.path.traverse({ // Meta export ExportDefaultDeclaration: (path) => { + hasMeta = true; const declaration = path.node.declaration; /** @@ -134,7 +124,7 @@ export default async function transform(info: FileInfo) { ExportNamedDeclaration: (path) => { const declaration = path.node.declaration; - if (!declaration || !isVariableDeclaration(declaration)) { + if (!declaration || !isVariableDeclaration(declaration) || !hasMeta) { return; } @@ -168,6 +158,19 @@ export default async function transform(info: FileInfo) { }, }); + if (hasMeta && !foundConfigImport) { + const configImport = babel.types.importDeclaration( + [ + babel.types.importSpecifier( + babel.types.identifier('config'), + babel.types.identifier('config') + ), + ], + babel.types.stringLiteral('#.storybook/preview') + ); + programNode.body.unshift(configImport); + } + // Generate the transformed code const { code } = babel.transformFromAstSync(fileNode, info.source, { parserOpts: { sourceType: 'module' }, From 0c7bace86568eced113bb6c9425d0ae090f53c49 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 10 Jan 2025 09:03:26 +0100 Subject: [PATCH 042/144] in sandbox command, delete a sandbox directory if it already exists --- code/lib/cli-storybook/src/sandbox.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/lib/cli-storybook/src/sandbox.ts b/code/lib/cli-storybook/src/sandbox.ts index 3b43a05af5d5..cd52184ac834 100644 --- a/code/lib/cli-storybook/src/sandbox.ts +++ b/code/lib/cli-storybook/src/sandbox.ts @@ -1,5 +1,5 @@ import { existsSync } from 'node:fs'; -import { readdir } from 'node:fs/promises'; +import { readdir, rm } from 'node:fs/promises'; import { isAbsolute, join } from 'node:path'; import type { PackageManagerName } from 'storybook/internal/common'; @@ -167,6 +167,7 @@ export const sandbox = async ({ const outputDirectoryName = outputDirectory || templateId; if (selectedDirectory && existsSync(`${selectedDirectory}`)) { logger.info(`⚠️ ${selectedDirectory} already exists! Overwriting...`); + await rm(selectedDirectory, { recursive: true, force: true }); } if (!selectedDirectory) { From 23bcf14b11f4b21638c3d3d44f8002c92570bdfe Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 10 Jan 2025 09:06:41 +0100 Subject: [PATCH 043/144] add codemod migrations to sandbox creation --- scripts/tasks/sandbox-parts.ts | 15 +++++++++++++++ scripts/tasks/sandbox.ts | 3 +++ scripts/utils/cli-step.ts | 9 +++++++++ scripts/utils/options.ts | 2 +- 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index b507bb1c10c2..2a9bae9a118c 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -901,6 +901,21 @@ export const extendPreview: Task['run'] = async ({ template, sandboxDir }) => { await writeConfig(previewConfig); }; +export const runMigrations: Task['run'] = async ({ sandboxDir, template }, { dryRun, debug }) => { + if ( + template.expected.framework === '@storybook/react-vite' && + !template.skipTasks.includes('vitest-integration') + ) { + await executeCLIStep(steps.migrate, { + cwd: sandboxDir, + argument: 'csf-3-to-4', + optionValues: { glob: 'src/stories/*.stories.*' }, + dryRun, + debug, + }); + } +}; + export async function setImportMap(cwd: string) { const packageJson = await readJson(join(cwd, 'package.json')); diff --git a/scripts/tasks/sandbox.ts b/scripts/tasks/sandbox.ts index cbf8856e0373..ef79e3d4c32f 100644 --- a/scripts/tasks/sandbox.ts +++ b/scripts/tasks/sandbox.ts @@ -64,6 +64,7 @@ export const sandbox: Task = { addExtraDependencies, setImportMap, setupVitest, + runMigrations, } = await import('./sandbox-parts'); const extraDeps = [ @@ -147,6 +148,8 @@ export const sandbox: Task = { await setImportMap(details.sandboxDir); + await runMigrations(details, options); + logger.info(`✅ Storybook sandbox created at ${details.sandboxDir}`); }, }; diff --git a/scripts/utils/cli-step.ts b/scripts/utils/cli-step.ts index ce57896aeddf..b685eb7b16ee 100644 --- a/scripts/utils/cli-step.ts +++ b/scripts/utils/cli-step.ts @@ -73,6 +73,15 @@ export const steps = { icon: '🖥 ', options: createOptions({}), }, + migrate: { + command: 'migrate', + hasArgument: true, + description: 'Run codemods', + icon: '🚀', + options: createOptions({ + glob: { type: 'string' }, + }), + }, }; export async function executeCLIStep( diff --git a/scripts/utils/options.ts b/scripts/utils/options.ts index f3caf64e28b2..c4c18cf16351 100644 --- a/scripts/utils/options.ts +++ b/scripts/utils/options.ts @@ -296,7 +296,7 @@ function getFlag( if (option.type === 'string') { if (value) { - return `--${longFlag(key, option)} ${value}`; + return `--${longFlag(key, option)} '${value}'`; } return ''; } From 76b1b6c724dc19453424011e2ab5ed5623344265 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 10 Jan 2025 09:40:23 +0100 Subject: [PATCH 044/144] fix tests --- scripts/utils/options.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/utils/options.test.ts b/scripts/utils/options.test.ts index 2cf5dfecf7f4..bbe9b5e59a8b 100644 --- a/scripts/utils/options.test.ts +++ b/scripts/utils/options.test.ts @@ -150,7 +150,7 @@ describe('getCommand', () => { }); it('works with string options', () => { - expect(getCommand('node foo', { third }, { third: 'one' })).toBe('node foo --third one'); + expect(getCommand('node foo', { third }, { third: 'one' })).toBe(`node foo --third 'one'`); }); it('works with multiple string options', () => { @@ -162,7 +162,7 @@ describe('getCommand', () => { // This is for convenience it('works with partial options', () => { expect(getCommand('node foo', allOptions, { third: 'one' })).toBe( - 'node foo --no-second --third one' + `node foo --no-second --third 'one'` ); }); @@ -174,6 +174,6 @@ describe('getCommand', () => { third: 'one', fifth: ['a', 'b'], }) - ).toBe('node foo --first --no-second --third one --fifth a --fifth b'); + ).toBe(`node foo --first --no-second --third 'one' --fifth a --fifth b`); }); }); From 0ae364323f2caa8b766e7220dad82c50239f49af Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 10 Jan 2025 12:19:01 +0100 Subject: [PATCH 045/144] Fix controls --- code/core/src/preview-api/index.ts | 1 + code/renderers/react/src/preview.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/code/core/src/preview-api/index.ts b/code/core/src/preview-api/index.ts index d067acad89cc..1b46b91ecd75 100644 --- a/code/core/src/preview-api/index.ts +++ b/code/core/src/preview-api/index.ts @@ -53,6 +53,7 @@ export { userOrAutoTitleFromSpecifier, userOrAutoTitle, sortStoriesV7, + normalizeProjectAnnotations, } from './store'; export { createPlaywrightTest } from './modules/store/csf/portable-stories'; diff --git a/code/renderers/react/src/preview.tsx b/code/renderers/react/src/preview.tsx index 3e18660abe6f..55489ef5bdab 100644 --- a/code/renderers/react/src/preview.tsx +++ b/code/renderers/react/src/preview.tsx @@ -1,6 +1,7 @@ import type { ComponentProps, ComponentType } from 'react'; import { composeConfigs } from 'storybook/internal/preview-api'; +import { normalizeProjectAnnotations } from 'storybook/internal/preview-api'; import type { Args, ComponentAnnotations, @@ -32,7 +33,7 @@ class PreviewConfig { constructor(data: PreviewConfigData) { const { addons, ...rest } = data; - this.annotations = composeConfigs([...(addons ?? []), rest]); + this.annotations = normalizeProjectAnnotations(composeConfigs([...(addons ?? []), rest])); } readonly meta = < From 53864bdbf7141e59e2e905b8bd9af658a580f9a1 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 10 Jan 2025 14:49:35 +0100 Subject: [PATCH 046/144] use canary of eslint-plugin which supports csf4 meta --- .../components/Button/Button.stories.tsx | 1 - .../template/stories/csf4.stories.tsx | 1 - code/package.json | 2 +- .../src/__test__/Button.csf4.stories.tsx | 1 - code/yarn.lock | 167 +++++++++--------- scripts/package.json | 2 +- scripts/yarn.lock | 107 +++++++++-- 7 files changed, 177 insertions(+), 104 deletions(-) diff --git a/code/core/src/components/components/Button/Button.stories.tsx b/code/core/src/components/components/Button/Button.stories.tsx index a8d8e02a4e77..c2724412aa18 100644 --- a/code/core/src/components/components/Button/Button.stories.tsx +++ b/code/core/src/components/components/Button/Button.stories.tsx @@ -6,7 +6,6 @@ import { FaceHappyIcon } from '@storybook/icons'; import { config } from '../../../../../.storybook/preview'; import { Button } from './Button'; -// eslint-disable-next-line storybook/default-exports const meta = config.meta({ id: 'button-component', title: 'Button', diff --git a/code/frameworks/react-vite/template/stories/csf4.stories.tsx b/code/frameworks/react-vite/template/stories/csf4.stories.tsx index 7ae4ee5b095c..012c356881c6 100644 --- a/code/frameworks/react-vite/template/stories/csf4.stories.tsx +++ b/code/frameworks/react-vite/template/stories/csf4.stories.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { config } from '#.storybook/preview'; -// eslint-disable-next-line storybook/default-exports const meta = config.meta({ component: globalThis.Components.Button, args: { diff --git a/code/package.json b/code/package.json index 095188241d45..cb4709b703d4 100644 --- a/code/package.json +++ b/code/package.json @@ -194,7 +194,7 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-local-rules": "portal:../scripts/eslint-plugin-local-rules", "eslint-plugin-playwright": "^1.6.2", - "eslint-plugin-storybook": "^0.8.0", + "eslint-plugin-storybook": "0.11.3--canary.187.1af857a.0", "github-release-from-changelog": "^2.1.1", "glob": "^10.0.0", "happy-dom": "^14.12.0", diff --git a/code/renderers/react/src/__test__/Button.csf4.stories.tsx b/code/renderers/react/src/__test__/Button.csf4.stories.tsx index 358961add9a5..70ff0caa3344 100644 --- a/code/renderers/react/src/__test__/Button.csf4.stories.tsx +++ b/code/renderers/react/src/__test__/Button.csf4.stories.tsx @@ -8,7 +8,6 @@ import { action } from '@storybook/addon-actions'; import { defineConfig } from '../preview'; import { Button } from './Button'; -// eslint-disable-next-line storybook/default-exports const config = defineConfig({ args: { children: 'TODO: THIS IS NOT WORKING YET' } }); const meta = config.meta({ diff --git a/code/yarn.lock b/code/yarn.lock index 13e798756fd5..a7a55bff3a1c 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6345,12 +6345,12 @@ __metadata: languageName: node linkType: hard -"@storybook/csf@npm:^0.0.1": - version: 0.0.1 - resolution: "@storybook/csf@npm:0.0.1" +"@storybook/csf@npm:^0.1.11": + version: 0.1.13 + resolution: "@storybook/csf@npm:0.1.13" dependencies: - lodash: "npm:^4.17.15" - checksum: 10c0/7b0f75763415f9147692a460b44417ee56ea9639433716a1fd4d1df4c8b0221cbc71b8da0fbed4dcecb3ccd6c7ed64be39f5c255c713539a6088a1d6488aaa24 + type-fest: "npm:^2.19.0" + checksum: 10c0/7c57b531ac95ca45239f498d419483d675e58cd8d549e0bac623519cc1ef4f3c9c6b75ec3873aa51cc2872728012db5dd5e1f2c2d8085014241eb4b896480996 languageName: node linkType: hard @@ -7134,7 +7134,7 @@ __metadata: eslint-plugin-import: "npm:^2.29.1" eslint-plugin-local-rules: "portal:../scripts/eslint-plugin-local-rules" eslint-plugin-playwright: "npm:^1.6.2" - eslint-plugin-storybook: "npm:^0.8.0" + eslint-plugin-storybook: "npm:0.11.3--canary.187.1af857a.0" github-release-from-changelog: "npm:^2.1.1" glob: "npm:^10.0.0" happy-dom: "npm:^14.12.0" @@ -8482,7 +8482,7 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:^7, @types/semver@npm:^7.3.12, @types/semver@npm:^7.3.4, @types/semver@npm:^7.5.0, @types/semver@npm:^7.5.6, @types/semver@npm:^7.5.8": +"@types/semver@npm:^7, @types/semver@npm:^7.3.4, @types/semver@npm:^7.5.0, @types/semver@npm:^7.5.6, @types/semver@npm:^7.5.8": version: 7.5.8 resolution: "@types/semver@npm:7.5.8" checksum: 10c0/8663ff927234d1c5fcc04b33062cb2b9fcfbe0f5f351ed26c4d1e1581657deebd506b41ff7fdf89e787e3d33ce05854bc01686379b89e9c49b564c4cfa988efa @@ -8780,16 +8780,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/scope-manager@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/visitor-keys": "npm:5.62.0" - checksum: 10c0/861253235576c1c5c1772d23cdce1418c2da2618a479a7de4f6114a12a7ca853011a1e530525d0931c355a8fd237b9cd828fac560f85f9623e24054fd024726f - languageName: node - linkType: hard - "@typescript-eslint/scope-manager@npm:6.18.1": version: 6.18.1 resolution: "@typescript-eslint/scope-manager@npm:6.18.1" @@ -8820,6 +8810,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:8.19.1": + version: 8.19.1 + resolution: "@typescript-eslint/scope-manager@npm:8.19.1" + dependencies: + "@typescript-eslint/types": "npm:8.19.1" + "@typescript-eslint/visitor-keys": "npm:8.19.1" + checksum: 10c0/7dca0c28ad27a0c7e26499e0f584f98efdcf34087f46aadc661b36c310484b90655e83818bafd249b5a28c7094a69c54d553f6cd403869bf134f95a9148733f5 + languageName: node + linkType: hard + "@typescript-eslint/type-utils@npm:6.21.0": version: 6.21.0 resolution: "@typescript-eslint/type-utils@npm:6.21.0" @@ -8854,13 +8854,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/types@npm:5.62.0" - checksum: 10c0/7febd3a7f0701c0b927e094f02e82d8ee2cada2b186fcb938bc2b94ff6fbad88237afc304cbaf33e82797078bbbb1baf91475f6400912f8b64c89be79bfa4ddf - languageName: node - linkType: hard - "@typescript-eslint/types@npm:6.18.1": version: 6.18.1 resolution: "@typescript-eslint/types@npm:6.18.1" @@ -8882,21 +8875,10 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/visitor-keys": "npm:5.62.0" - debug: "npm:^4.3.4" - globby: "npm:^11.1.0" - is-glob: "npm:^4.0.3" - semver: "npm:^7.3.7" - tsutils: "npm:^3.21.0" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/d7984a3e9d56897b2481940ec803cb8e7ead03df8d9cfd9797350be82ff765dfcf3cfec04e7355e1779e948da8f02bc5e11719d07a596eb1cb995c48a95e38cf +"@typescript-eslint/types@npm:8.19.1": + version: 8.19.1 + resolution: "@typescript-eslint/types@npm:8.19.1" + checksum: 10c0/e907bf096d5ed7a812a1e537a98dd881ab5d2d47e072225bfffaa218c1433115a148b27a15744db8374b46dac721617c6d13a1da255fdeb369cf193416533f6e languageName: node linkType: hard @@ -8957,6 +8939,24 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/typescript-estree@npm:8.19.1": + version: 8.19.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.19.1" + dependencies: + "@typescript-eslint/types": "npm:8.19.1" + "@typescript-eslint/visitor-keys": "npm:8.19.1" + debug: "npm:^4.3.4" + fast-glob: "npm:^3.3.2" + is-glob: "npm:^4.0.3" + minimatch: "npm:^9.0.4" + semver: "npm:^7.6.0" + ts-api-utils: "npm:^2.0.0" + peerDependencies: + typescript: ">=4.8.4 <5.8.0" + checksum: 10c0/549d9d565a58a25fc8397a555506f2e8d29a740f5b6ed9105479e22de5aab89d9d535959034a8e9d4115adb435de09ee6987d28e8922052eea577842ddce1a7a + languageName: node + linkType: hard + "@typescript-eslint/utils@npm:6.21.0": version: 6.21.0 resolution: "@typescript-eslint/utils@npm:6.21.0" @@ -8988,31 +8988,18 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:^5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/utils@npm:5.62.0" +"@typescript-eslint/utils@npm:^8.8.1": + version: 8.19.1 + resolution: "@typescript-eslint/utils@npm:8.19.1" dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@types/json-schema": "npm:^7.0.9" - "@types/semver": "npm:^7.3.12" - "@typescript-eslint/scope-manager": "npm:5.62.0" - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/typescript-estree": "npm:5.62.0" - eslint-scope: "npm:^5.1.1" - semver: "npm:^7.3.7" + "@eslint-community/eslint-utils": "npm:^4.4.0" + "@typescript-eslint/scope-manager": "npm:8.19.1" + "@typescript-eslint/types": "npm:8.19.1" + "@typescript-eslint/typescript-estree": "npm:8.19.1" peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10c0/f09b7d9952e4a205eb1ced31d7684dd55cee40bf8c2d78e923aa8a255318d97279825733902742c09d8690f37a50243f4c4d383ab16bd7aefaf9c4b438f785e1 - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - eslint-visitor-keys: "npm:^3.3.0" - checksum: 10c0/7c3b8e4148e9b94d9b7162a596a1260d7a3efc4e65199693b8025c71c4652b8042501c0bc9f57654c1e2943c26da98c0f77884a746c6ae81389fcb0b513d995d + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.8.0" + checksum: 10c0/f7d2fe9a2bd8cb3ae6fafe5e465882a6784b2acf81d43d194c579381b92651c2ffc0fca69d2a35eee119f539622752a0e9ec063aaec7576d5d2bfe68b441980d languageName: node linkType: hard @@ -9046,6 +9033,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:8.19.1": + version: 8.19.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.19.1" + dependencies: + "@typescript-eslint/types": "npm:8.19.1" + eslint-visitor-keys: "npm:^4.2.0" + checksum: 10c0/117537450a099f51f3f0d39186f248ae370bdc1b7f6975dbdbffcfc89e6e1aa47c1870db790d4f778a48f2c1f6cd9c269b63867c12afaa424367c63dabee8fd0 + languageName: node + linkType: hard + "@ungap/structured-clone@npm:^1.0.0, @ungap/structured-clone@npm:^1.2.0": version: 1.2.0 resolution: "@ungap/structured-clone@npm:1.2.0" @@ -14940,21 +14937,20 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-storybook@npm:^0.8.0": - version: 0.8.0 - resolution: "eslint-plugin-storybook@npm:0.8.0" +"eslint-plugin-storybook@npm:0.11.3--canary.187.1af857a.0": + version: 0.11.3--canary.187.1af857a.0 + resolution: "eslint-plugin-storybook@npm:0.11.3--canary.187.1af857a.0" dependencies: - "@storybook/csf": "npm:^0.0.1" - "@typescript-eslint/utils": "npm:^5.62.0" - requireindex: "npm:^1.2.0" + "@storybook/csf": "npm:^0.1.11" + "@typescript-eslint/utils": "npm:^8.8.1" ts-dedent: "npm:^2.2.0" peerDependencies: - eslint: ">=6" - checksum: 10c0/c76f6decdd4c826cd6a8bb613085e0cde804f4648093a0464a39867cc0ba4e1d34be15ff91eed827730da5efbbf55ae5e71af648bb0b461946d5e41384669ab8 + eslint: ">=8" + checksum: 10c0/1d9de1e8478fda4c0054271baf4444fa8c401c58bfb9752f5160dc83110ceff9dd0c36321a4473142e6a88cb4bda4cdbe3945cf91dc26990026a526747b2707b languageName: node linkType: hard -"eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": +"eslint-scope@npm:5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" dependencies: @@ -14999,6 +14995,13 @@ __metadata: languageName: node linkType: hard +"eslint-visitor-keys@npm:^4.2.0": + version: 4.2.0 + resolution: "eslint-visitor-keys@npm:4.2.0" + checksum: 10c0/2ed81c663b147ca6f578312919483eb040295bbab759e5a371953456c636c5b49a559883e2677112453728d66293c0a4c90ab11cab3428cf02a0236d2e738269 + languageName: node + linkType: hard + "eslint@npm:8.4.1": version: 8.4.1 resolution: "eslint@npm:8.4.1" @@ -27881,6 +27884,15 @@ __metadata: languageName: node linkType: hard +"ts-api-utils@npm:^2.0.0": + version: 2.0.0 + resolution: "ts-api-utils@npm:2.0.0" + peerDependencies: + typescript: ">=4.8.4" + checksum: 10c0/6165e29a5b75bd0218e3cb0f9ee31aa893dbd819c2e46dbb086c841121eb0436ed47c2c18a20cb3463d74fd1fb5af62e2604ba5971cc48e5b38ebbdc56746dfc + languageName: node + linkType: hard + "ts-dedent@npm:^2.0.0, ts-dedent@npm:^2.2.0": version: 2.2.0 resolution: "ts-dedent@npm:2.2.0" @@ -28021,24 +28033,13 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^1.13.0, tslib@npm:^1.8.1, tslib@npm:^1.9.3": +"tslib@npm:^1.13.0, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 languageName: node linkType: hard -"tsutils@npm:^3.21.0": - version: 3.21.0 - resolution: "tsutils@npm:3.21.0" - dependencies: - tslib: "npm:^1.8.1" - peerDependencies: - typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - checksum: 10c0/02f19e458ec78ead8fffbf711f834ad8ecd2cc6ade4ec0320790713dccc0a412b99e7fd907c4cda2a1dc602c75db6f12e0108e87a5afad4b2f9e90a24cabd5a2 - languageName: node - linkType: hard - "tty-browserify@npm:^0.0.1": version: 0.0.1 resolution: "tty-browserify@npm:0.0.1" diff --git a/scripts/package.json b/scripts/package.json index b02d6abc9bba..cb1a60a2cb7b 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -123,7 +123,7 @@ "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react": "^7.34.2", "eslint-plugin-react-hooks": "^4.6.2", - "eslint-plugin-storybook": "^0.8.0", + "eslint-plugin-storybook": "0.11.3--canary.187.1af857a.0", "execa": "^6.1.0", "fast-folder-size": "^2.2.0", "fast-glob": "^3.3.2", diff --git a/scripts/yarn.lock b/scripts/yarn.lock index e7bac1502539..ce14f97d71f9 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -1351,12 +1351,12 @@ __metadata: languageName: node linkType: hard -"@storybook/csf@npm:^0.0.1": - version: 0.0.1 - resolution: "@storybook/csf@npm:0.0.1" +"@storybook/csf@npm:^0.1.11": + version: 0.1.13 + resolution: "@storybook/csf@npm:0.1.13" dependencies: - lodash: "npm:^4.17.15" - checksum: 10c0/7b0f75763415f9147692a460b44417ee56ea9639433716a1fd4d1df4c8b0221cbc71b8da0fbed4dcecb3ccd6c7ed64be39f5c255c713539a6088a1d6488aaa24 + type-fest: "npm:^2.19.0" + checksum: 10c0/7c57b531ac95ca45239f498d419483d675e58cd8d549e0bac623519cc1ef4f3c9c6b75ec3873aa51cc2872728012db5dd5e1f2c2d8085014241eb4b896480996 languageName: node linkType: hard @@ -1465,7 +1465,7 @@ __metadata: eslint-plugin-prettier: "npm:^5.1.3" eslint-plugin-react: "npm:^7.34.2" eslint-plugin-react-hooks: "npm:^4.6.2" - eslint-plugin-storybook: "npm:^0.8.0" + eslint-plugin-storybook: "npm:0.11.3--canary.187.1af857a.0" execa: "npm:^6.1.0" fast-folder-size: "npm:^2.2.0" fast-glob: "npm:^3.3.2" @@ -2208,6 +2208,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:8.19.1": + version: 8.19.1 + resolution: "@typescript-eslint/scope-manager@npm:8.19.1" + dependencies: + "@typescript-eslint/types": "npm:8.19.1" + "@typescript-eslint/visitor-keys": "npm:8.19.1" + checksum: 10c0/7dca0c28ad27a0c7e26499e0f584f98efdcf34087f46aadc661b36c310484b90655e83818bafd249b5a28c7094a69c54d553f6cd403869bf134f95a9148733f5 + languageName: node + linkType: hard + "@typescript-eslint/type-utils@npm:6.18.1": version: 6.18.1 resolution: "@typescript-eslint/type-utils@npm:6.18.1" @@ -2263,6 +2273,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:8.19.1": + version: 8.19.1 + resolution: "@typescript-eslint/types@npm:8.19.1" + checksum: 10c0/e907bf096d5ed7a812a1e537a98dd881ab5d2d47e072225bfffaa218c1433115a148b27a15744db8374b46dac721617c6d13a1da255fdeb369cf193416533f6e + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" @@ -2319,7 +2336,25 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.62.0, @typescript-eslint/utils@npm:^5.62.0": +"@typescript-eslint/typescript-estree@npm:8.19.1": + version: 8.19.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.19.1" + dependencies: + "@typescript-eslint/types": "npm:8.19.1" + "@typescript-eslint/visitor-keys": "npm:8.19.1" + debug: "npm:^4.3.4" + fast-glob: "npm:^3.3.2" + is-glob: "npm:^4.0.3" + minimatch: "npm:^9.0.4" + semver: "npm:^7.6.0" + ts-api-utils: "npm:^2.0.0" + peerDependencies: + typescript: ">=4.8.4 <5.8.0" + checksum: 10c0/549d9d565a58a25fc8397a555506f2e8d29a740f5b6ed9105479e22de5aab89d9d535959034a8e9d4115adb435de09ee6987d28e8922052eea577842ddce1a7a + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/utils@npm:5.62.0" dependencies: @@ -2368,6 +2403,21 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:^8.8.1": + version: 8.19.1 + resolution: "@typescript-eslint/utils@npm:8.19.1" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.4.0" + "@typescript-eslint/scope-manager": "npm:8.19.1" + "@typescript-eslint/types": "npm:8.19.1" + "@typescript-eslint/typescript-estree": "npm:8.19.1" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.8.0" + checksum: 10c0/f7d2fe9a2bd8cb3ae6fafe5e465882a6784b2acf81d43d194c579381b92651c2ffc0fca69d2a35eee119f539622752a0e9ec063aaec7576d5d2bfe68b441980d + languageName: node + linkType: hard + "@typescript-eslint/visitor-keys@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" @@ -2398,6 +2448,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:8.19.1": + version: 8.19.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.19.1" + dependencies: + "@typescript-eslint/types": "npm:8.19.1" + eslint-visitor-keys: "npm:^4.2.0" + checksum: 10c0/117537450a099f51f3f0d39186f248ae370bdc1b7f6975dbdbffcfc89e6e1aa47c1870db790d4f778a48f2c1f6cd9c269b63867c12afaa424367c63dabee8fd0 + languageName: node + linkType: hard + "@ungap/structured-clone@npm:^1.0.0, @ungap/structured-clone@npm:^1.2.0": version: 1.2.0 resolution: "@ungap/structured-clone@npm:1.2.0" @@ -5535,17 +5595,16 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-storybook@npm:^0.8.0": - version: 0.8.0 - resolution: "eslint-plugin-storybook@npm:0.8.0" +"eslint-plugin-storybook@npm:0.11.3--canary.187.1af857a.0": + version: 0.11.3--canary.187.1af857a.0 + resolution: "eslint-plugin-storybook@npm:0.11.3--canary.187.1af857a.0" dependencies: - "@storybook/csf": "npm:^0.0.1" - "@typescript-eslint/utils": "npm:^5.62.0" - requireindex: "npm:^1.2.0" + "@storybook/csf": "npm:^0.1.11" + "@typescript-eslint/utils": "npm:^8.8.1" ts-dedent: "npm:^2.2.0" peerDependencies: - eslint: ">=6" - checksum: 10c0/c76f6decdd4c826cd6a8bb613085e0cde804f4648093a0464a39867cc0ba4e1d34be15ff91eed827730da5efbbf55ae5e71af648bb0b461946d5e41384669ab8 + eslint: ">=8" + checksum: 10c0/1d9de1e8478fda4c0054271baf4444fa8c401c58bfb9752f5160dc83110ceff9dd0c36321a4473142e6a88cb4bda4cdbe3945cf91dc26990026a526747b2707b languageName: node linkType: hard @@ -5576,6 +5635,13 @@ __metadata: languageName: node linkType: hard +"eslint-visitor-keys@npm:^4.2.0": + version: 4.2.0 + resolution: "eslint-visitor-keys@npm:4.2.0" + checksum: 10c0/2ed81c663b147ca6f578312919483eb040295bbab759e5a371953456c636c5b49a559883e2677112453728d66293c0a4c90ab11cab3428cf02a0236d2e738269 + languageName: node + linkType: hard + "eslint@npm:^8.57.0": version: 8.57.0 resolution: "eslint@npm:8.57.0" @@ -8521,7 +8587,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4, lodash@npm:4.17.21, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.21": +"lodash@npm:4, lodash@npm:4.17.21, lodash@npm:^4.17.14, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c @@ -13506,6 +13572,15 @@ __metadata: languageName: node linkType: hard +"ts-api-utils@npm:^2.0.0": + version: 2.0.0 + resolution: "ts-api-utils@npm:2.0.0" + peerDependencies: + typescript: ">=4.8.4" + checksum: 10c0/6165e29a5b75bd0218e3cb0f9ee31aa893dbd819c2e46dbb086c841121eb0436ed47c2c18a20cb3463d74fd1fb5af62e2604ba5971cc48e5b38ebbdc56746dfc + languageName: node + linkType: hard + "ts-dedent@npm:^2.2.0": version: 2.2.0 resolution: "ts-dedent@npm:2.2.0" From 6d08dd1c05dffe25efde36bc9e92d5b9d3a524da Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 10 Jan 2025 15:27:03 +0100 Subject: [PATCH 047/144] fix lint --- .../docs/template/stories/docs2/resolved-react.stories.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/addons/docs/template/stories/docs2/resolved-react.stories.ts b/code/addons/docs/template/stories/docs2/resolved-react.stories.ts index 00fed804cebb..75e3d480ca98 100644 --- a/code/addons/docs/template/stories/docs2/resolved-react.stories.ts +++ b/code/addons/docs/template/stories/docs2/resolved-react.stories.ts @@ -62,15 +62,15 @@ export const Story = { const actualReactDomVersion = (await canvas.findByTestId('react-dom')).textContent; const actualReactDomServerVersion = (await canvas.findByTestId('react-dom-server')).textContent; - step('Expect React packages to all resolve to the same version', () => { + step('Expect React packages to all resolve to the same version', async () => { // react-dom has a bug in its production build, reporting version 18.2.0-next-9e3b772b8-20220608 even though version 18.2.0 is installed. - expect(actualReactDomVersion!.startsWith(actualReactVersion!)).toBeTruthy(); + await expect(actualReactDomVersion!.startsWith(actualReactVersion!)).toBeTruthy(); if (parameters.renderer === 'preact') { // the preact/compat alias doesn't have a version export in react-dom/server return; } - expect(actualReactDomServerVersion).toBe(actualReactVersion); + await expect(actualReactDomServerVersion).toBe(actualReactVersion); }); }, }; From dd7fd8435d00cdd66b62da568aa708d4d047fe77 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 10 Jan 2025 16:22:01 +0100 Subject: [PATCH 048/144] add CSF4 support in save from controls feature closes https://github.com/storybookjs/storybook/issues/30244 --- .../duplicate-story-with-new-name.test.ts | 33 ++++++++++++++ .../duplicate-story-with-new-name.ts | 11 ++++- .../mocks/csf-variances.stories.tsx | 4 +- .../mocks/csf4-variances.stories.tsx | 14 ++++++ .../update-args-in-csf-file.test.ts | 45 ++++++++++++++++++- .../save-story/update-args-in-csf-file.ts | 8 +++- 6 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx diff --git a/code/core/src/core-server/utils/save-story/duplicate-story-with-new-name.test.ts b/code/core/src/core-server/utils/save-story/duplicate-story-with-new-name.test.ts index 549c74337ca9..ce781a7e8447 100644 --- a/code/core/src/core-server/utils/save-story/duplicate-story-with-new-name.test.ts +++ b/code/core/src/core-server/utils/save-story/duplicate-story-with-new-name.test.ts @@ -15,6 +15,7 @@ const makeTitle = (userTitle: string) => userTitle; const FILES = { csfVariances: join(__dirname, 'mocks/csf-variances.stories.tsx'), + csf4Variances: join(__dirname, 'mocks/csf4-variances.stories.tsx'), unsupportedCsfVariances: join(__dirname, 'mocks/unsupported-csf-variances.stories.tsx'), typescriptConstructs: join(__dirname, 'mocks/typescript-constructs.stories.tsx'), }; @@ -77,6 +78,38 @@ describe('success', () => { + " `); }); + test('CSF4 Variances', async () => { + const before = await format(await readFile(FILES.csf4Variances, 'utf-8'), { + parser: 'typescript', + }); + const CSF = await readCsf(FILES.csf4Variances, { makeTitle }); + + const parsed = CSF.parse(); + const names = Object.keys(parsed._stories); + + names.forEach((name) => { + duplicateStoryWithNewName(parsed, name, name + 'Duplicated'); + }); + + const after = await format(printCsf(parsed).code, { + parser: 'typescript', + }); + + // check if the code was updated at all + expect(after).not.toBe(before); + + // check if the code was updated correctly + expect(getDiff(before, after)).toMatchInlineSnapshot(` + " ... + foo: "bar", + }, + }); + + + export const EmptyDuplicated = meta.story({}); + + export const WithArgsDuplicated = meta.story({}); + + " + `); + }); test('Unsupported CSF Variances', async () => { const CSF = await readCsf(FILES.unsupportedCsfVariances, { makeTitle }); diff --git a/code/core/src/core-server/utils/save-story/duplicate-story-with-new-name.ts b/code/core/src/core-server/utils/save-story/duplicate-story-with-new-name.ts index fe85e2cb6aa0..cf20ccfcaeb1 100644 --- a/code/core/src/core-server/utils/save-story/duplicate-story-with-new-name.ts +++ b/code/core/src/core-server/utils/save-story/duplicate-story-with-new-name.ts @@ -37,8 +37,17 @@ export const duplicateStoryWithNewName = (csfFile: In, storyName: string, newSto noScope: true, }); + const isCsf4Story = + t.isCallExpression(cloned.init) && + t.isMemberExpression(cloned.init.callee) && + t.isIdentifier(cloned.init.callee.property) && + cloned.init.callee.property.name === 'story'; + // detect CSF2 and throw - if (t.isArrowFunctionExpression(cloned.init) || t.isCallExpression(cloned.init)) { + if ( + !isCsf4Story && + (t.isArrowFunctionExpression(cloned.init) || t.isCallExpression(cloned.init)) + ) { throw new SaveStoryError(`Creating a new story based on a CSF2 story is not supported`); } diff --git a/code/core/src/core-server/utils/save-story/mocks/csf-variances.stories.tsx b/code/core/src/core-server/utils/save-story/mocks/csf-variances.stories.tsx index 35081f422ae3..674605e2ac4e 100644 --- a/code/core/src/core-server/utils/save-story/mocks/csf-variances.stories.tsx +++ b/code/core/src/core-server/utils/save-story/mocks/csf-variances.stories.tsx @@ -49,7 +49,7 @@ export const RenderExistingArgs = { render: (args) => , } satisfies Story; -// The order of both the properties of the story and the order or args should be preserved +// The order of both the properties of the story and the order of args should be preserved export const OrderedArgs = { args: { bordered: true, @@ -59,7 +59,7 @@ export const OrderedArgs = { render: (args) => , } satisfies Story; -// The order of both the properties of the story and the order or args should be preserved +// The order of both the properties of the story and the order of args should be preserved export const HasPlayFunction = { args: { bordered: true, diff --git a/code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx b/code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx new file mode 100644 index 000000000000..48bbafcaa22c --- /dev/null +++ b/code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx @@ -0,0 +1,14 @@ +import { config } from '#.storybook/preview'; + +const meta = config.meta({ + title: 'MyComponent', + args: { + initial: 'foo', + }, +}); +export const Empty = meta.story({}); +export const WithArgs = meta.story({ + args: { + foo: 'bar', + }, +}); diff --git a/code/core/src/core-server/utils/save-story/update-args-in-csf-file.test.ts b/code/core/src/core-server/utils/save-story/update-args-in-csf-file.test.ts index 43bc049fc58b..0b32bc267c56 100644 --- a/code/core/src/core-server/utils/save-story/update-args-in-csf-file.test.ts +++ b/code/core/src/core-server/utils/save-story/update-args-in-csf-file.test.ts @@ -16,6 +16,7 @@ const makeTitle = (userTitle: string) => userTitle; const FILES = { typescriptConstructs: join(__dirname, 'mocks/typescript-constructs.stories.tsx'), csfVariances: join(__dirname, 'mocks/csf-variances.stories.tsx'), + csf4Variances: join(__dirname, 'mocks/csf4-variances.stories.tsx'), unsupportedCsfVariances: join(__dirname, 'mocks/unsupported-csf-variances.stories.tsx'), exportVariances: join(__dirname, 'mocks/export-variances.stories.tsx'), dataVariances: join(__dirname, 'mocks/data-variances.stories.tsx'), @@ -204,7 +205,7 @@ describe('success', () => { render: (args) => , } satisfies Story; - // The order of both the properties of the story and the order or args should be preserved + // The order of both the properties of the story and the order of args should be preserved export const OrderedArgs = { args: { bordered: true, @@ -233,6 +234,48 @@ describe('success', () => { ..." `); }); + test.only('CSF4 Variances', async () => { + const newArgs = { bordered: true, initial: 'test1' }; + + const before = await format(await readFile(FILES.csf4Variances, 'utf-8'), { + parser: 'typescript', + }); + const CSF = await readCsf(FILES.csf4Variances, { makeTitle }); + + const parsed = CSF.parse(); + const names = Object.keys(parsed._stories); + const nodes = names.map((name) => CSF.getStoryExport(name)); + + nodes.forEach((node) => { + updateArgsInCsfFile(node, newArgs); + }); + + const after = await format(printCsf(parsed).code, { + parser: 'typescript', + }); + + // check if the code was updated at all + expect(after).not.toBe(before); + + // check if the code was updated correctly + // TODO, the comment is not preserved!!! + expect(getDiff(before, after)).toMatchInlineSnapshot(` + " ... + initial: "foo", + }, + }); + + - export const Empty = meta.story({}); + - + + export const Empty = meta.story({ + + args: { + + bordered: true, + + initial: "test1", + + }, + + }); + + " + `); + }); test('Export Variances', async () => { const newArgs = { bordered: true, initial: 'test1' }; diff --git a/code/core/src/core-server/utils/save-story/update-args-in-csf-file.ts b/code/core/src/core-server/utils/save-story/update-args-in-csf-file.ts index 7e1f30e7e961..d391d9c6a296 100644 --- a/code/core/src/core-server/utils/save-story/update-args-in-csf-file.ts +++ b/code/core/src/core-server/utils/save-story/update-args-in-csf-file.ts @@ -11,8 +11,14 @@ export const updateArgsInCsfFile = async (node: t.Node, input: Record Date: Fri, 10 Jan 2025 17:07:59 +0100 Subject: [PATCH 049/144] remove .only in test --- .../utils/save-story/update-args-in-csf-file.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/core-server/utils/save-story/update-args-in-csf-file.test.ts b/code/core/src/core-server/utils/save-story/update-args-in-csf-file.test.ts index 0b32bc267c56..c3a9b16d903c 100644 --- a/code/core/src/core-server/utils/save-story/update-args-in-csf-file.test.ts +++ b/code/core/src/core-server/utils/save-story/update-args-in-csf-file.test.ts @@ -234,7 +234,7 @@ describe('success', () => { ..." `); }); - test.only('CSF4 Variances', async () => { + test('CSF4 Variances', async () => { const newArgs = { bordered: true, initial: 'test1' }; const before = await format(await readFile(FILES.csf4Variances, 'utf-8'), { From 19f4b0dcf6c0126cfcbb8934468e69ecc21d120a Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 10 Jan 2025 17:09:19 +0100 Subject: [PATCH 050/144] update snapshots --- .../utils/save-story/update-args-in-csf-file.test.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/code/core/src/core-server/utils/save-story/update-args-in-csf-file.test.ts b/code/core/src/core-server/utils/save-story/update-args-in-csf-file.test.ts index c3a9b16d903c..78a9393b04eb 100644 --- a/code/core/src/core-server/utils/save-story/update-args-in-csf-file.test.ts +++ b/code/core/src/core-server/utils/save-story/update-args-in-csf-file.test.ts @@ -273,7 +273,17 @@ describe('success', () => { + initial: "test1", + }, + }); - + " + + + export const WithArgs = meta.story({ + args: { + foo: "bar", + + + bordered: true, + + initial: "test1", + + + }, + }); + " `); }); test('Export Variances', async () => { From f151fef10c3a2ffddb63e85d9e8123b07ba96f94 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 17 Jan 2025 13:02:08 +0100 Subject: [PATCH 051/144] fix types and tests --- .../mocks/csf4-variances.stories.tsx | 1 + .../src/__test__/Button.csf4.stories.tsx | 21 ++++++++++++++----- .../portable-stories-factory.test.tsx.snap | 4 +++- .../portable-stories-factory.test.tsx | 18 +++++++++++----- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx b/code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx index 48bbafcaa22c..fd9003947612 100644 --- a/code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx +++ b/code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx @@ -1,3 +1,4 @@ +// @ts-expect-error this is just a mock file import { config } from '#.storybook/preview'; const meta = config.meta({ diff --git a/code/renderers/react/src/__test__/Button.csf4.stories.tsx b/code/renderers/react/src/__test__/Button.csf4.stories.tsx index 70ff0caa3344..6a57e1d5a909 100644 --- a/code/renderers/react/src/__test__/Button.csf4.stories.tsx +++ b/code/renderers/react/src/__test__/Button.csf4.stories.tsx @@ -17,7 +17,9 @@ const meta = config.meta({ argTypes: { backgroundColor: { control: 'color' }, }, - args: {}, + args: { + children: 'Children coming from meta args', + }, }); export const CSF2Secondary = meta.story({ @@ -61,7 +63,7 @@ export const CSF2StoryWithLocale = meta.story({ }); export const CSF2StoryWithParamsAndDecorator = meta.story({ - render: (args: any) => { + render: (args) => { return diff --git a/code/renderers/react/src/__test__/portable-stories-factory.test.tsx b/code/renderers/react/src/__test__/portable-stories-factory.test.tsx index a63b4fb029a1..e74f94886f23 100644 --- a/code/renderers/react/src/__test__/portable-stories-factory.test.tsx +++ b/code/renderers/react/src/__test__/portable-stories-factory.test.tsx @@ -28,8 +28,9 @@ const HooksStory = composeStory( const projectAnnotations = setProjectAnnotations([]); // example with composeStories, returns an object with all stories composed with args/decorators -const { CSF3Primary, LoaderStory, MountInPlayFunction, MountInPlayFunctionThrow } = - composeStories(ButtonStories); +// @ts-expect-error TODO: add a way to provide custom args/argTypes +// eslint-disable-next-line prettier/prettier +const { CSF3Primary, LoaderStory, MountInPlayFunction, MountInPlayFunctionThrow } = composeStories(ButtonStories); const { ThrowsError } = composeStories(ComponentWithErrorStories); beforeAll(async () => { @@ -83,8 +84,8 @@ describe('renders', () => { expect(screen.getByTestId('loaded-data').textContent).toEqual('loaded data'); }); - it('should throw an error in play function', () => { - expect(() => MountInPlayFunctionThrow.run()).rejects.toThrowError('Error thrown in play'); + it('should throw an error in play function', async () => { + await expect(() => MountInPlayFunctionThrow.run()).rejects.toThrowError('Error thrown in play'); }); it('should call and compose loaders data', async () => { @@ -136,6 +137,8 @@ describe('projectAnnotations', () => { ButtonStories.CSF3Primary.meta.annotations, addonActionsPreview as ProjectAnnotations ); + + // @ts-expect-error TODO: add a way to provide custom args/argTypes expect(Story.args.someActionArg).toHaveProperty('isAction', true); }); }); @@ -189,7 +192,9 @@ describe('CSF3', () => { const input = screen.getByTestId('input') as HTMLInputElement; expect(input.value).toEqual('Hello world!'); } finally { - document.body.removeChild(divElement); + if (divElement) { + document.body.removeChild(divElement); + } } }); @@ -237,7 +242,9 @@ describe('ComposeStories types', () => { }); }); +// @ts-expect-error TODO: fix the types for this const testCases = Object.values(composeStories(ButtonStories)).map( + // @ts-expect-error TODO: fix the types for this (Story) => [Story.storyName, Story] as [string, typeof Story] ); it.each(testCases)('Renders %s story', async (_storyName, Story) => { @@ -245,6 +252,7 @@ it.each(testCases)('Renders %s story', async (_storyName, Story) => { return; } + // @ts-expect-error TODO: fix the types for this await Story.run(); expect(document.body).toMatchSnapshot(); }); From 5fe2c120eb9c0c31667879e1b47a501251137bc9 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 17 Jan 2025 13:14:27 +0100 Subject: [PATCH 052/144] fix test --- code/renderers/react/src/csf-factories.test.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/code/renderers/react/src/csf-factories.test.tsx b/code/renderers/react/src/csf-factories.test.tsx index 4b9862963410..12ea20a79ce2 100644 --- a/code/renderers/react/src/csf-factories.test.tsx +++ b/code/renderers/react/src/csf-factories.test.tsx @@ -1,4 +1,4 @@ -import { test } from 'vitest'; +import { expect, test } from 'vitest'; import { Button } from './__test__/Button'; import { defineConfig } from './preview'; @@ -15,6 +15,10 @@ test('csf factories', () => { const meta = config.meta({ component: Button, args: { primary: true } }); const MyStory = meta.story({ - args: {}, + args: { + children: 'Hello world', + }, }); + + expect(MyStory.annotations.args?.children).toBe('Hello world'); }); From f1a2c535f5d773e1e2d69792e2e91af9e7908793 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 13 Jan 2025 14:48:20 +0100 Subject: [PATCH 053/144] Csf Tools: Support CSF4 in enrichCsf for source extraction --- code/core/src/csf-tools/enrichCsf.test.ts | 45 +++++++++++++++++++++++ code/core/src/csf-tools/enrichCsf.ts | 11 +++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/code/core/src/csf-tools/enrichCsf.test.ts b/code/core/src/csf-tools/enrichCsf.test.ts index e8c0ce6250eb..1f7786f9e6f9 100644 --- a/code/core/src/csf-tools/enrichCsf.test.ts +++ b/code/core/src/csf-tools/enrichCsf.test.ts @@ -149,6 +149,51 @@ describe('enrichCsf', () => { }; `); }); + it('csf4', () => { + expect( + enrich( + dedent` + // compiled code + import {config} from "/.storybook/preview.ts"; + const meta = config.meta({ + args: { + label: "Hello world!" + } + }); + export const Story = meta.story({}); + `, + dedent` + // original code + import {config} from "#.storybook/preview.ts"; + const meta = config.meta({ + args: { + label: "Hello world!" + } + }); + export const Story = meta.story({}); + ` + ) + ).toMatchInlineSnapshot(` + // compiled code + import { config } from "/.storybook/preview.ts"; + const meta = config.meta({ + args: { + label: "Hello world!" + } + }); + export const Story = meta.story({}); + Story.annotations.parameters = { + ...Story.annotations.parameters, + docs: { + ...Story.annotations.parameters?.docs, + source: { + originalSource: "meta.story({})", + ...Story.annotations.parameters?.docs?.source + } + } + }; + `); + }); it('multiple stories', () => { expect( enrich( diff --git a/code/core/src/csf-tools/enrichCsf.ts b/code/core/src/csf-tools/enrichCsf.ts index aa4a205e6bf3..1413249090df 100644 --- a/code/core/src/csf-tools/enrichCsf.ts +++ b/code/core/src/csf-tools/enrichCsf.ts @@ -15,11 +15,20 @@ export const enrichCsfStory = ( options?: EnrichCsfOptions ) => { const storyExport = csfSource.getStoryExport(key); + const isCsfFactory = + t.isCallExpression(storyExport) && + t.isMemberExpression(storyExport.callee) && + t.isIdentifier(storyExport.callee.object) && + storyExport.callee.object.name === 'meta'; const source = !options?.disableSource && extractSource(storyExport); const description = !options?.disableDescription && extractDescription(csfSource._storyStatements[key]); const parameters = []; - const originalParameters = t.memberExpression(t.identifier(key), t.identifier('parameters')); + // in csf1/2/3 we use Story.parameters, in csf4 we use Story.annotations.parameters + const baseStoryObject = isCsfFactory + ? t.memberExpression(t.identifier(key), t.identifier('annotations')) + : t.identifier(key); + const originalParameters = t.memberExpression(baseStoryObject, t.identifier('parameters')); parameters.push(t.spreadElement(originalParameters)); const optionalDocs = t.optionalMemberExpression( originalParameters, From 6c27056c9c881c7e681661f42cef5b3884088fa4 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 13 Jan 2025 15:12:32 +0100 Subject: [PATCH 054/144] Apply suggestions from code review Co-authored-by: Michael Shilman --- code/core/src/csf-tools/enrichCsf.test.ts | 2 +- code/core/src/csf-tools/enrichCsf.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/core/src/csf-tools/enrichCsf.test.ts b/code/core/src/csf-tools/enrichCsf.test.ts index 1f7786f9e6f9..e46c908bd658 100644 --- a/code/core/src/csf-tools/enrichCsf.test.ts +++ b/code/core/src/csf-tools/enrichCsf.test.ts @@ -149,7 +149,7 @@ describe('enrichCsf', () => { }; `); }); - it('csf4', () => { + it('csf factories', () => { expect( enrich( dedent` diff --git a/code/core/src/csf-tools/enrichCsf.ts b/code/core/src/csf-tools/enrichCsf.ts index 1413249090df..11b1a239c7ae 100644 --- a/code/core/src/csf-tools/enrichCsf.ts +++ b/code/core/src/csf-tools/enrichCsf.ts @@ -24,7 +24,7 @@ export const enrichCsfStory = ( const description = !options?.disableDescription && extractDescription(csfSource._storyStatements[key]); const parameters = []; - // in csf1/2/3 we use Story.parameters, in csf4 we use Story.annotations.parameters + // in csf 1/2/3 use Story.parameters; CSF factories use Story.annotations.parameters const baseStoryObject = isCsfFactory ? t.memberExpression(t.identifier(key), t.identifier('annotations')) : t.identifier(key); From 659930e1c7a5c50e764525884b3b0606f7602331 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 15 Jan 2025 13:58:14 +0100 Subject: [PATCH 055/144] Use csf-tools in csf factory codemod --- code/core/src/csf-tools/CsfFile.ts | 12 +- .../transforms/__tests__/csf-3-to-4.test.ts | 105 +++---- code/lib/codemod/src/transforms/csf-3-to-4.ts | 274 ++++++++++-------- 3 files changed, 212 insertions(+), 179 deletions(-) diff --git a/code/core/src/csf-tools/CsfFile.ts b/code/core/src/csf-tools/CsfFile.ts index d7ed7fe8eeb9..cfbc9b08f957 100644 --- a/code/core/src/csf-tools/CsfFile.ts +++ b/code/core/src/csf-tools/CsfFile.ts @@ -4,6 +4,7 @@ import { readFile, writeFile } from 'node:fs/promises'; import { BabelFileClass, type GeneratorOptions, + type NodePath, type RecastOptions, babelParse, generate, @@ -255,10 +256,14 @@ export class CsfFile { _storyExports: Record = {}; + _storyPaths: Record> = {}; + _metaStatement: t.Statement | undefined; _metaNode: t.Expression | undefined; + _metaPath: NodePath | undefined; + _metaVariableName: string | undefined; _metaIsFactory: boolean | undefined; @@ -466,10 +471,13 @@ export class CsfFile { self._options.fileName ); } + + self._metaPath = path; }, }, ExportNamedDeclaration: { - enter({ node, parent }) { + enter(path) { + const { node, parent } = path; let declarations; if (t.isVariableDeclaration(node.declaration)) { declarations = node.declaration.declarations.filter((d) => t.isVariableDeclarator(d)); @@ -487,6 +495,7 @@ export class CsfFile { return; } self._storyExports[exportName] = decl; + self._storyPaths[exportName] = path; self._storyStatements[exportName] = node; let name = storyNameFromExport(exportName); if (self._storyAnnotations[exportName]) { @@ -611,6 +620,7 @@ export class CsfFile { } else { self._storyAnnotations[exportName] = {}; self._storyStatements[exportName] = decl; + self._storyPaths[exportName] = path; self._stories[exportName] = { id: 'FIXME', name: exportName, diff --git a/code/lib/codemod/src/transforms/__tests__/csf-3-to-4.test.ts b/code/lib/codemod/src/transforms/__tests__/csf-3-to-4.test.ts index 8cf66dff1ea2..7dcef73c2e53 100644 --- a/code/lib/codemod/src/transforms/__tests__/csf-3-to-4.test.ts +++ b/code/lib/codemod/src/transforms/__tests__/csf-3-to-4.test.ts @@ -21,10 +21,9 @@ describe('csf-3-to-4', () => { export default meta; `) ).resolves.toMatchInlineSnapshot(` - import { config } from "#.storybook/preview"; - const meta = config.meta({ - title: 'Component' - }); + import { config } from '#.storybook/preview'; + + const meta = config.meta({ title: 'Component' }); `); }); @@ -34,9 +33,10 @@ describe('csf-3-to-4', () => { export default { title: 'Component' }; `) ).resolves.toMatchInlineSnapshot(` - import { config } from "#.storybook/preview"; + import { config } from '#.storybook/preview'; + const meta = config.meta({ - title: 'Component' + title: 'Component', }); `); }); @@ -48,10 +48,9 @@ describe('csf-3-to-4', () => { export default componentMeta; `) ).resolves.toMatchInlineSnapshot(` - import { config } from "#.storybook/preview"; - const meta = config.meta({ - title: 'Component' - }); + import { config } from '#.storybook/preview'; + + const meta = config.meta({ title: 'Component' }); `); }); @@ -66,15 +65,12 @@ describe('csf-3-to-4', () => { }; `) ).resolves.toMatchInlineSnapshot(` - import { config } from "#.storybook/preview"; - const meta = config.meta({ - title: 'Component' - }); + import { config } from '#.storybook/preview'; + + const meta = config.meta({ title: 'Component' }); export const A = meta.story({ - args: { - primary: true - }, - render: args => + args: { primary: true }, + render: (args) => , }); `); }); @@ -91,15 +87,12 @@ describe('csf-3-to-4', () => { }; `) ).resolves.toMatchInlineSnapshot(` - import { decorators, config } from "#.storybook/preview"; - const meta = config.meta({ - title: 'Component' - }); + import { config, decorators } from '#.storybook/preview'; + + const meta = config.meta({ title: 'Component' }); export const A = meta.story({ - args: { - primary: true - }, - render: args => + args: { primary: true }, + render: (args) => , }); `); }); @@ -119,17 +112,14 @@ describe('csf-3-to-4', () => { `; it('meta satisfies syntax', async () => { await expect(transform(metaSatisfies)).resolves.toMatchInlineSnapshot(` - import { config } from "#.storybook/preview"; - import { Meta, StoryObj as CSF3 } from '@storybook/react'; + import { config } from '#.storybook/preview'; + import { ComponentProps } from './Component'; - const meta = config.meta({ - title: 'Component', - component: Component - }); + + const meta = config.meta({ title: 'Component', component: Component }); + export const A = meta.story({ - args: { - primary: true - } + args: { primary: true }, }); `); }); @@ -147,17 +137,14 @@ describe('csf-3-to-4', () => { `; it('meta as syntax', async () => { await expect(transform(metaAs)).resolves.toMatchInlineSnapshot(` - import { config } from "#.storybook/preview"; - import { Meta, StoryObj as CSF3 } from '@storybook/react'; + import { config } from '#.storybook/preview'; + import { ComponentProps } from './Component'; - const meta = config.meta({ - title: 'Component', - component: Component - }); + + const meta = config.meta({ title: 'Component', component: Component }); + export const A = meta.story({ - args: { - primary: true - } + args: { primary: true }, }); `); }); @@ -175,17 +162,14 @@ describe('csf-3-to-4', () => { `; it('story satisfies syntax', async () => { await expect(transform(storySatisfies)).resolves.toMatchInlineSnapshot(` - import { config } from "#.storybook/preview"; - import { Meta, StoryObj as CSF3 } from '@storybook/react'; + import { config } from '#.storybook/preview'; + import { ComponentProps } from './Component'; - const meta = config.meta({ - title: 'Component', - component: Component - }); + + const meta = config.meta({ title: 'Component', component: Component }); + export const A = meta.story({ - args: { - primary: true - } + args: { primary: true }, }); `); }); @@ -203,17 +187,14 @@ describe('csf-3-to-4', () => { `; it('story as syntax', async () => { await expect(transform(storyAs)).resolves.toMatchInlineSnapshot(` - import { config } from "#.storybook/preview"; - import { Meta, StoryObj as CSF3 } from '@storybook/react'; + import { config } from '#.storybook/preview'; + import { ComponentProps } from './Component'; - const meta = config.meta({ - title: 'Component', - component: Component - }); + + const meta = config.meta({ title: 'Component', component: Component }); + export const A = meta.story({ - args: { - primary: true - } + args: { primary: true }, }); `); }); diff --git a/code/lib/codemod/src/transforms/csf-3-to-4.ts b/code/lib/codemod/src/transforms/csf-3-to-4.ts index 7efe6a430c06..9d8856f25150 100644 --- a/code/lib/codemod/src/transforms/csf-3-to-4.ts +++ b/code/lib/codemod/src/transforms/csf-3-to-4.ts @@ -1,27 +1,24 @@ /* eslint-disable no-underscore-dangle */ -import { isValidPreviewPath, loadCsf } from '@storybook/core/csf-tools'; +import { types as t, traverse } from '@storybook/core/babel'; + +import { isValidPreviewPath, loadCsf, printCsf } from '@storybook/core/csf-tools'; -import type { BabelFile } from '@babel/core'; import * as babel from '@babel/core'; -import { - isIdentifier, - isImportDeclaration, - isImportSpecifier, - isObjectExpression, - isTSAsExpression, - isTSSatisfiesExpression, - isVariableDeclaration, -} from '@babel/types'; import type { FileInfo } from 'jscodeshift'; +import prettier from 'prettier'; + +const logger = console; export default async function transform(info: FileInfo) { const csf = loadCsf(info.source, { makeTitle: (title) => title }); const fileNode = csf._ast; - // @ts-expect-error File is not yet exposed, see https://github.com/babel/babel/issues/11350#issuecomment-644118606 - const file: BabelFile = new babel.File( - { filename: info.path }, - { code: info.source, ast: fileNode } - ); + + try { + csf.parse(); + } catch (err) { + logger.log(`Error ${err}, skipping`); + return info.source; + } const metaVariableName = 'meta'; @@ -34,10 +31,10 @@ export default async function transform(info: FileInfo) { let foundConfigImport = false; programNode.body.forEach((node) => { - if (isImportDeclaration(node) && isValidPreviewPath(node.source.value)) { + if (t.isImportDeclaration(node) && isValidPreviewPath(node.source.value)) { const hasConfigSpecifier = node.specifiers.some( (specifier) => - isImportSpecifier(specifier) && isIdentifier(specifier.imported, { name: 'config' }) + t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported, { name: 'config' }) ); if (!hasConfigSpecifier) { @@ -53,110 +50,93 @@ export default async function transform(info: FileInfo) { } }); - let hasMeta = false; + const hasMeta = !!csf._meta; - file.path.traverse({ - // Meta export - ExportDefaultDeclaration: (path) => { - hasMeta = true; - const declaration = path.node.declaration; + Object.entries(csf._storyExports).forEach(([key, decl]) => { + const id = decl.id; + const declarator = decl as babel.types.VariableDeclarator; + let init = t.isVariableDeclarator(declarator) ? declarator.init : undefined; - /** - * Transform inline default export: `export default { title: 'A' };` - * - * Into a meta call: `const meta = config.meta({ title: 'A' });` - */ - if (isObjectExpression(declaration)) { - const metaVariable = babel.types.variableDeclaration('const', [ - babel.types.variableDeclarator( - babel.types.identifier(metaVariableName), - babel.types.callExpression( - babel.types.memberExpression( - babel.types.identifier('config'), - babel.types.identifier('meta') - ), - [declaration] - ) - ), - ]); - - path.replaceWith(metaVariable); - } else if (isIdentifier(declaration)) { - /** - * Transform const declared metas: - * - * `const meta = {}; export default meta;` - * - * Into a meta call: - * - * `const meta = config.meta({ title: 'A' });` - */ - const binding = path.scope.getBinding(declaration.name); - if (binding && binding.path.isVariableDeclarator()) { - const originalName = declaration.name; - - // Always rename the meta variable to 'meta' - binding.path.node.id = babel.types.identifier(metaVariableName); - - let init = binding.path.node.init; - if (isTSSatisfiesExpression(init) || isTSAsExpression(init)) { - init = init.expression; - } - if (isObjectExpression(init)) { - binding.path.node.init = babel.types.callExpression( - babel.types.memberExpression( - babel.types.identifier('config'), - babel.types.identifier('meta') - ), - [init] - ); - } + if (t.isIdentifier(id) && init) { + if (t.isTSSatisfiesExpression(init) || t.isTSAsExpression(init)) { + init = init.expression; + } - // Update all references to the original name - path.scope.rename(originalName, metaVariableName); + if (t.isObjectExpression(init)) { + const typeAnnotation = id.typeAnnotation; + // Remove type annotation as it's now inferred + if (typeAnnotation) { + id.typeAnnotation = null; } - // Remove the default export, it's not needed anymore - path.remove(); - } - }, - // Story export - ExportNamedDeclaration: (path) => { - const declaration = path.node.declaration; - - if (!declaration || !isVariableDeclaration(declaration) || !hasMeta) { - return; + // Wrap the object in `meta.story()` + declarator.init = babel.types.callExpression( + babel.types.memberExpression( + babel.types.identifier(metaVariableName), + babel.types.identifier('story') + ), + [init] + ); } + } + }); - declaration.declarations.forEach((decl) => { - const id = decl.id; - let init = decl.init; + // modify meta + if (csf._metaPath) { + const declaration = csf._metaPath.node.declaration; + if (t.isObjectExpression(declaration)) { + const metaVariable = babel.types.variableDeclaration('const', [ + babel.types.variableDeclarator( + babel.types.identifier(metaVariableName), + babel.types.callExpression( + babel.types.memberExpression( + babel.types.identifier('config'), + babel.types.identifier('meta') + ), + [declaration] + ) + ), + ]); + csf._metaPath.replaceWith(metaVariable); + } else if (t.isIdentifier(declaration)) { + /** + * Transform const declared metas: + * + * `const meta = {}; export default meta;` + * + * Into a meta call: + * + * `const meta = config.meta({ title: 'A' });` + */ + const binding = csf._metaPath.scope.getBinding(declaration.name); + if (binding && binding.path.isVariableDeclarator()) { + const originalName = declaration.name; - if (isIdentifier(id) && init) { - if (isTSSatisfiesExpression(init) || isTSAsExpression(init)) { - init = init.expression; - } + // Always rename the meta variable to 'meta' + binding.path.node.id = babel.types.identifier(metaVariableName); - if (isObjectExpression(init)) { - const typeAnnotation = id.typeAnnotation; - // Remove type annotation as it's now inferred - if (typeAnnotation) { - id.typeAnnotation = null; - } - - // Wrap the object in `meta.story()` - decl.init = babel.types.callExpression( - babel.types.memberExpression( - babel.types.identifier(metaVariableName), - babel.types.identifier('story') - ), - [init] - ); - } + let init = binding.path.node.init; + if (t.isTSSatisfiesExpression(init) || t.isTSAsExpression(init)) { + init = init.expression; } - }); - }, - }); + if (t.isObjectExpression(init)) { + binding.path.node.init = babel.types.callExpression( + babel.types.memberExpression( + babel.types.identifier('config'), + babel.types.identifier('meta') + ), + [init] + ); + } + + // Update all references to the original name + csf._metaPath.scope.rename(originalName, metaVariableName); + } + + // Remove the default export, it's not needed anymore + csf._metaPath.remove(); + } + } if (hasMeta && !foundConfigImport) { const configImport = babel.types.importDeclaration( @@ -171,9 +151,71 @@ export default async function transform(info: FileInfo) { programNode.body.unshift(configImport); } - // Generate the transformed code - const { code } = babel.transformFromAstSync(fileNode, info.source, { - parserOpts: { sourceType: 'module' }, + function isSpecifierUsed(name: string) { + let isUsed = false; + + // Traverse the AST and check for usage of the name + traverse(programNode, { + Identifier(path) { + if (path.node.name === name) { + isUsed = true; + // Stop traversal early if we've found a match + path.stop(); + } + }, + }); + + return isUsed; + } + + // Remove type imports – now inferred – from @storybook/* packages + const disallowlist = [ + 'Story', + 'StoryFn', + 'StoryObj', + 'Meta', + 'MetaObj', + 'ComponentStory', + 'ComponentMeta', + ]; + + programNode.body = programNode.body.filter((node) => { + if (t.isImportDeclaration(node)) { + const { source, specifiers } = node; + + if (source.value.startsWith('@storybook/')) { + const allowedSpecifiers = specifiers.filter((specifier) => { + if (t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported)) { + return !disallowlist.includes(specifier.imported.name); + } + // Retain non-specifier imports (e.g., namespace imports) + return true; + }); + + // Remove the entire import if no specifiers are left + if (allowedSpecifiers.length > 0) { + node.specifiers = allowedSpecifiers; + return true; + } + + // Remove the import if no specifiers remain + return false; + } + } + + // Retain all other nodes + return true; }); - return code; + + let output = printCsf(csf).code; + + try { + output = await prettier.format(output, { + ...(await prettier.resolveConfig(info.path)), + filepath: info.path, + }); + } catch (e) { + logger.log(`Failed applying prettier to ${info.path}.`); + } + return output; } From 7db0050290d8b8291c40f1f0d662c2eaea92133f Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 14 Jan 2025 15:01:37 +0100 Subject: [PATCH 056/144] Add defineConfig helper for main.js --- code/core/src/common/defineConfig.ts | 5 +++++ code/core/src/common/index.ts | 1 + code/frameworks/angular/src/types.ts | 5 +++++ code/frameworks/ember/src/types.ts | 5 +++++ code/frameworks/experimental-nextjs-vite/src/types.ts | 5 +++++ code/frameworks/html-vite/src/types.ts | 5 +++++ code/frameworks/html-webpack5/src/types.ts | 5 +++++ code/frameworks/nextjs/src/types.ts | 5 +++++ code/frameworks/preact-vite/src/types.ts | 5 +++++ code/frameworks/preact-webpack5/src/types.ts | 5 +++++ code/frameworks/react-native-web-vite/src/types.ts | 5 +++++ code/frameworks/react-vite/src/types.ts | 5 +++++ code/frameworks/react-webpack5/src/types.ts | 5 +++++ code/frameworks/server-webpack5/src/types.ts | 5 +++++ code/frameworks/svelte-vite/src/types.ts | 5 +++++ code/frameworks/svelte-webpack5/src/types.ts | 5 +++++ code/frameworks/sveltekit/src/types.ts | 5 +++++ code/frameworks/vue3-vite/src/types.ts | 5 +++++ code/frameworks/vue3-webpack5/src/types.ts | 5 +++++ code/frameworks/web-components-vite/src/types.ts | 5 +++++ code/frameworks/web-components-webpack5/src/types.ts | 5 +++++ 21 files changed, 101 insertions(+) create mode 100644 code/core/src/common/defineConfig.ts diff --git a/code/core/src/common/defineConfig.ts b/code/core/src/common/defineConfig.ts new file mode 100644 index 000000000000..08006b683343 --- /dev/null +++ b/code/core/src/common/defineConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types/modules/core-common'; + +export function defineConfig(config: TConfig): TConfig { + return config; +} diff --git a/code/core/src/common/index.ts b/code/core/src/common/index.ts index 6166e285ab05..dcec4e025c53 100644 --- a/code/core/src/common/index.ts +++ b/code/core/src/common/index.ts @@ -42,6 +42,7 @@ export * from './utils/formatter'; export * from './utils/get-story-id'; export * from './utils/posix'; export * from './js-package-manager'; +export * from './defineConfig'; export { versions }; diff --git a/code/frameworks/angular/src/types.ts b/code/frameworks/angular/src/types.ts index a30e2174efd6..1ba889dc90ff 100644 --- a/code/frameworks/angular/src/types.ts +++ b/code/frameworks/angular/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import { CompatibleString } from 'storybook/internal/types'; import { @@ -48,3 +49,7 @@ export interface AngularOptions { enableIvy?: boolean; enableNgcc?: boolean; } + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/ember/src/types.ts b/code/frameworks/ember/src/types.ts index ee6ef8e0fe5b..236840db2f10 100644 --- a/code/frameworks/ember/src/types.ts +++ b/code/frameworks/ember/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -48,3 +49,7 @@ declare global { // eslint-disable-next-line no-underscore-dangle, @typescript-eslint/naming-convention, no-var var __EMBER_GENERATED_DOC_JSON__: any; } + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/experimental-nextjs-vite/src/types.ts b/code/frameworks/experimental-nextjs-vite/src/types.ts index 0221787dccb6..6c04f243b6fc 100644 --- a/code/frameworks/experimental-nextjs-vite/src/types.ts +++ b/code/frameworks/experimental-nextjs-vite/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { BuilderOptions } from '@storybook/builder-vite'; @@ -32,3 +33,7 @@ type StorybookConfigFramework = { /** The interface for Storybook configuration in `main.ts` files. */ export type StorybookConfig = Omit & StorybookConfigFramework; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/html-vite/src/types.ts b/code/frameworks/html-vite/src/types.ts index 34d1e116e8d7..107ae09ac607 100644 --- a/code/frameworks/html-vite/src/types.ts +++ b/code/frameworks/html-vite/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -36,3 +37,7 @@ export type StorybookConfig = Omit< > & StorybookConfigVite & StorybookConfigFramework; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/html-webpack5/src/types.ts b/code/frameworks/html-webpack5/src/types.ts index d29860b85762..b53f57f9cc66 100644 --- a/code/frameworks/html-webpack5/src/types.ts +++ b/code/frameworks/html-webpack5/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -43,3 +44,7 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/nextjs/src/types.ts b/code/frameworks/nextjs/src/types.ts index 42148ef8517b..f2a83028fe58 100644 --- a/code/frameworks/nextjs/src/types.ts +++ b/code/frameworks/nextjs/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -48,3 +49,7 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/preact-vite/src/types.ts b/code/frameworks/preact-vite/src/types.ts index 503f7cd5d327..317de3d9732b 100644 --- a/code/frameworks/preact-vite/src/types.ts +++ b/code/frameworks/preact-vite/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -36,3 +37,7 @@ export type StorybookConfig = Omit< > & StorybookConfigVite & StorybookConfigFramework; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/preact-webpack5/src/types.ts b/code/frameworks/preact-webpack5/src/types.ts index a09f8e3695c1..678294d9ec8f 100644 --- a/code/frameworks/preact-webpack5/src/types.ts +++ b/code/frameworks/preact-webpack5/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -43,3 +44,7 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/react-native-web-vite/src/types.ts b/code/frameworks/react-native-web-vite/src/types.ts index 5558722e03d7..7436accebc84 100644 --- a/code/frameworks/react-native-web-vite/src/types.ts +++ b/code/frameworks/react-native-web-vite/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -30,3 +31,7 @@ export type StorybookConfig = Omit & { options: FrameworkOptions; }; }; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/react-vite/src/types.ts b/code/frameworks/react-vite/src/types.ts index 58cba705bf56..be84bca936ca 100644 --- a/code/frameworks/react-vite/src/types.ts +++ b/code/frameworks/react-vite/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -63,3 +64,7 @@ export type StorybookConfig = Omit< StorybookConfigFramework & { typescript?: Partial; }; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/react-webpack5/src/types.ts b/code/frameworks/react-webpack5/src/types.ts index 6366ca3cd857..006a08cdf667 100644 --- a/code/frameworks/react-webpack5/src/types.ts +++ b/code/frameworks/react-webpack5/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -44,3 +45,7 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/server-webpack5/src/types.ts b/code/frameworks/server-webpack5/src/types.ts index 1040a0fea8bb..fd7c5d91d960 100644 --- a/code/frameworks/server-webpack5/src/types.ts +++ b/code/frameworks/server-webpack5/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -43,3 +44,7 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/svelte-vite/src/types.ts b/code/frameworks/svelte-vite/src/types.ts index d6efce7feaef..908b77101109 100644 --- a/code/frameworks/svelte-vite/src/types.ts +++ b/code/frameworks/svelte-vite/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -36,3 +37,7 @@ export type StorybookConfig = Omit< > & StorybookConfigVite & StorybookConfigFramework; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/svelte-webpack5/src/types.ts b/code/frameworks/svelte-webpack5/src/types.ts index 2770043e12dd..62381933e4ed 100644 --- a/code/frameworks/svelte-webpack5/src/types.ts +++ b/code/frameworks/svelte-webpack5/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -44,3 +45,7 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/sveltekit/src/types.ts b/code/frameworks/sveltekit/src/types.ts index 975a184f4545..6aef9a709fd3 100644 --- a/code/frameworks/sveltekit/src/types.ts +++ b/code/frameworks/sveltekit/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -63,3 +64,7 @@ export type SvelteKitParameters = Partial<{ enhance: typeof enhance; }; }>; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/vue3-vite/src/types.ts b/code/frameworks/vue3-vite/src/types.ts index d02b791f294b..6ac74e77f10f 100644 --- a/code/frameworks/vue3-vite/src/types.ts +++ b/code/frameworks/vue3-vite/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -99,3 +100,7 @@ export type VueDocgenInfoEntry< ? VueDocgenInfo<'vue-component-meta'>[Exclude] : VueDocgenInfo<'vue-docgen-api'>[Exclude] >; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/vue3-webpack5/src/types.ts b/code/frameworks/vue3-webpack5/src/types.ts index 9d6aa2467884..bd1b1eac450c 100644 --- a/code/frameworks/vue3-webpack5/src/types.ts +++ b/code/frameworks/vue3-webpack5/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -43,3 +44,7 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/web-components-vite/src/types.ts b/code/frameworks/web-components-vite/src/types.ts index cb8328dc9770..8954bf3221df 100644 --- a/code/frameworks/web-components-vite/src/types.ts +++ b/code/frameworks/web-components-vite/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -36,3 +37,7 @@ export type StorybookConfig = Omit< > & StorybookConfigVite & StorybookConfigFramework; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/web-components-webpack5/src/types.ts b/code/frameworks/web-components-webpack5/src/types.ts index 0cd693b1fd4a..f6d1c9552392 100644 --- a/code/frameworks/web-components-webpack5/src/types.ts +++ b/code/frameworks/web-components-webpack5/src/types.ts @@ -1,3 +1,4 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -43,3 +44,7 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} From ebe6bf9a7a0ee05712550147a6f888bc24e25198 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 14 Jan 2025 16:51:43 +0100 Subject: [PATCH 057/144] move defineConfig to its own import path --- code/frameworks/angular/package.json | 15 +++++++++++++++ .../angular/src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/angular/src/types.ts | 5 ----- code/frameworks/ember/package.json | 15 +++++++++++++++ .../ember/src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/ember/src/types.ts | 5 ----- .../experimental-nextjs-vite/package.json | 7 +++++++ .../src/csf-factory/defineMainConfig.ts | 7 +++++++ .../experimental-nextjs-vite/src/types.ts | 5 ----- code/frameworks/html-vite/package.json | 9 ++++++++- .../html-vite/src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/html-vite/src/types.ts | 5 ----- code/frameworks/html-webpack5/package.json | 9 ++++++++- .../src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/html-webpack5/src/types.ts | 5 ----- code/frameworks/nextjs/package.json | 7 +++++++ .../nextjs/src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/nextjs/src/types.ts | 5 ----- code/frameworks/preact-vite/package.json | 6 ++++++ .../src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/preact-vite/src/types.ts | 5 ----- code/frameworks/preact-webpack5/package.json | 9 ++++++++- .../src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/preact-webpack5/src/types.ts | 5 ----- .../frameworks/react-native-web-vite/package.json | 9 ++++++++- .../src/csf-factory/defineMainConfig.ts | 7 +++++++ .../frameworks/react-native-web-vite/src/types.ts | 5 ----- code/frameworks/react-vite/package.json | 9 ++++++++- .../src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/react-vite/src/types.ts | 5 ----- code/frameworks/react-webpack5/package.json | 9 ++++++++- .../src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/react-webpack5/src/types.ts | 5 ----- code/frameworks/server-webpack5/package.json | 9 ++++++++- .../src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/server-webpack5/src/types.ts | 5 ----- code/frameworks/svelte-vite/package.json | 9 ++++++++- .../src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/svelte-vite/src/types.ts | 5 ----- code/frameworks/svelte-webpack5/package.json | 9 ++++++++- .../src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/svelte-webpack5/src/types.ts | 5 ----- code/frameworks/sveltekit/package.json | 9 ++++++++- .../sveltekit/src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/sveltekit/src/types.ts | 5 ----- code/frameworks/vue3-vite/package.json | 9 ++++++++- .../vue3-vite/src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/vue3-vite/src/types.ts | 5 ----- code/frameworks/vue3-webpack5/package.json | 9 ++++++++- .../src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/vue3-webpack5/src/types.ts | 5 ----- code/frameworks/web-components-vite/package.json | 7 +++++++ .../src/csf-factory/defineMainConfig.ts | 7 +++++++ code/frameworks/web-components-vite/src/types.ts | 5 ----- .../web-components-webpack5/package.json | 9 ++++++++- .../src/csf-factory/defineMainConfig.ts | 7 +++++++ .../web-components-webpack5/src/types.ts | 5 ----- 57 files changed, 294 insertions(+), 108 deletions(-) create mode 100644 code/frameworks/angular/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/ember/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/experimental-nextjs-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/html-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/html-webpack5/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/nextjs/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/preact-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/preact-webpack5/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/react-native-web-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/react-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/react-webpack5/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/server-webpack5/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/svelte-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/svelte-webpack5/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/sveltekit/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/vue3-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/vue3-webpack5/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/web-components-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/web-components-webpack5/src/csf-factory/defineMainConfig.ts diff --git a/code/frameworks/angular/package.json b/code/frameworks/angular/package.json index 946679a7a129..9623dd81ceb2 100644 --- a/code/frameworks/angular/package.json +++ b/code/frameworks/angular/package.json @@ -20,6 +20,21 @@ "url": "https://opencollective.com/storybook" }, "license": "MIT", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "node": "./dist/index.js", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.js", + "require": "./dist/csf-factory/defineMainConfig.js" + }, + "./package.json": "./package.json" + }, "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", diff --git a/code/frameworks/angular/src/csf-factory/defineMainConfig.ts b/code/frameworks/angular/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..3092f2e9d68d --- /dev/null +++ b/code/frameworks/angular/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/angular/src/types.ts b/code/frameworks/angular/src/types.ts index 1ba889dc90ff..a30e2174efd6 100644 --- a/code/frameworks/angular/src/types.ts +++ b/code/frameworks/angular/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import { CompatibleString } from 'storybook/internal/types'; import { @@ -49,7 +48,3 @@ export interface AngularOptions { enableIvy?: boolean; enableNgcc?: boolean; } - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/ember/package.json b/code/frameworks/ember/package.json index 9e308f6b5f6d..f9f61961c054 100644 --- a/code/frameworks/ember/package.json +++ b/code/frameworks/ember/package.json @@ -16,6 +16,21 @@ "url": "https://opencollective.com/storybook" }, "license": "MIT", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "node": "./dist/index.js", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.js", + "require": "./dist/csf-factory/defineMainConfig.js" + }, + "./package.json": "./package.json" + }, "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", diff --git a/code/frameworks/ember/src/csf-factory/defineMainConfig.ts b/code/frameworks/ember/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/ember/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/ember/src/types.ts b/code/frameworks/ember/src/types.ts index 236840db2f10..ee6ef8e0fe5b 100644 --- a/code/frameworks/ember/src/types.ts +++ b/code/frameworks/ember/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -49,7 +48,3 @@ declare global { // eslint-disable-next-line no-underscore-dangle, @typescript-eslint/naming-convention, no-var var __EMBER_GENERATED_DOC_JSON__: any; } - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/experimental-nextjs-vite/package.json b/code/frameworks/experimental-nextjs-vite/package.json index b5205345ed72..7604bf925799 100644 --- a/code/frameworks/experimental-nextjs-vite/package.json +++ b/code/frameworks/experimental-nextjs-vite/package.json @@ -58,6 +58,12 @@ "import": "./dist/vite-plugin/index.mjs", "require": "./dist/vite-plugin/index.js" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -134,6 +140,7 @@ "./src/index.ts", "./src/vite-plugin/index.ts", "./src/preset.ts", + "./src/csf-factory/defineMainConfig.ts", "./src/preview.tsx", "./src/export-mocks/cache/index.ts", "./src/export-mocks/headers/index.ts", diff --git a/code/frameworks/experimental-nextjs-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/experimental-nextjs-vite/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/experimental-nextjs-vite/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/experimental-nextjs-vite/src/types.ts b/code/frameworks/experimental-nextjs-vite/src/types.ts index 6c04f243b6fc..0221787dccb6 100644 --- a/code/frameworks/experimental-nextjs-vite/src/types.ts +++ b/code/frameworks/experimental-nextjs-vite/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { BuilderOptions } from '@storybook/builder-vite'; @@ -33,7 +32,3 @@ type StorybookConfigFramework = { /** The interface for Storybook configuration in `main.ts` files. */ export type StorybookConfig = Omit & StorybookConfigFramework; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/html-vite/package.json b/code/frameworks/html-vite/package.json index 804987dac1c7..89745dfd9b99 100644 --- a/code/frameworks/html-vite/package.json +++ b/code/frameworks/html-vite/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -67,7 +73,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/csf-factory/defineMainConfig.ts" ], "platform": "node" }, diff --git a/code/frameworks/html-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/html-vite/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/html-vite/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/html-vite/src/types.ts b/code/frameworks/html-vite/src/types.ts index 107ae09ac607..34d1e116e8d7 100644 --- a/code/frameworks/html-vite/src/types.ts +++ b/code/frameworks/html-vite/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -37,7 +36,3 @@ export type StorybookConfig = Omit< > & StorybookConfigVite & StorybookConfigFramework; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/html-webpack5/package.json b/code/frameworks/html-webpack5/package.json index 03cd0ce7cfc2..0cbb3b253a46 100644 --- a/code/frameworks/html-webpack5/package.json +++ b/code/frameworks/html-webpack5/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -68,7 +74,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/csf-factory/defineMainConfig.ts" ], "platform": "node" }, diff --git a/code/frameworks/html-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/html-webpack5/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/html-webpack5/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/html-webpack5/src/types.ts b/code/frameworks/html-webpack5/src/types.ts index b53f57f9cc66..d29860b85762 100644 --- a/code/frameworks/html-webpack5/src/types.ts +++ b/code/frameworks/html-webpack5/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -44,7 +43,3 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index a440c6b17a17..41dea2891772 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -87,6 +87,12 @@ "import": "./dist/export-mocks/router/index.mjs", "require": "./dist/export-mocks/router/index.js" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -209,6 +215,7 @@ "./src/image-context.ts", "./src/index.ts", "./src/preset.ts", + "./src/csf-factory/defineMainConfig.ts", "./src/preview.tsx", "./src/export-mocks/index.ts", "./src/export-mocks/cache/index.ts", diff --git a/code/frameworks/nextjs/src/csf-factory/defineMainConfig.ts b/code/frameworks/nextjs/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/nextjs/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/nextjs/src/types.ts b/code/frameworks/nextjs/src/types.ts index f2a83028fe58..42148ef8517b 100644 --- a/code/frameworks/nextjs/src/types.ts +++ b/code/frameworks/nextjs/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -49,7 +48,3 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/preact-vite/package.json b/code/frameworks/preact-vite/package.json index 5b146f3a9bbf..0a2679104192 100644 --- a/code/frameworks/preact-vite/package.json +++ b/code/frameworks/preact-vite/package.json @@ -29,6 +29,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", diff --git a/code/frameworks/preact-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/preact-vite/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/preact-vite/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/preact-vite/src/types.ts b/code/frameworks/preact-vite/src/types.ts index 317de3d9732b..503f7cd5d327 100644 --- a/code/frameworks/preact-vite/src/types.ts +++ b/code/frameworks/preact-vite/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -37,7 +36,3 @@ export type StorybookConfig = Omit< > & StorybookConfigVite & StorybookConfigFramework; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/preact-webpack5/package.json b/code/frameworks/preact-webpack5/package.json index 5f0290ffcebc..5064cc3eea6f 100644 --- a/code/frameworks/preact-webpack5/package.json +++ b/code/frameworks/preact-webpack5/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -69,7 +75,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/csf-factory/defineMainConfig.ts" ], "platform": "node" }, diff --git a/code/frameworks/preact-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/preact-webpack5/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/preact-webpack5/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/preact-webpack5/src/types.ts b/code/frameworks/preact-webpack5/src/types.ts index 678294d9ec8f..a09f8e3695c1 100644 --- a/code/frameworks/preact-webpack5/src/types.ts +++ b/code/frameworks/preact-webpack5/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -44,7 +43,3 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/react-native-web-vite/package.json b/code/frameworks/react-native-web-vite/package.json index da31ba3bca39..c8079ec2a22b 100644 --- a/code/frameworks/react-native-web-vite/package.json +++ b/code/frameworks/react-native-web-vite/package.json @@ -35,6 +35,12 @@ "import": "./dist/vite-plugin.mjs", "require": "./dist/vite-plugin.js" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -86,7 +92,8 @@ "entries": [ "./src/index.ts", "./src/preset.ts", - "./src/vite-plugin.ts" + "./src/vite-plugin.ts", + "./src/csf-factory/defineMainConfig.ts" ], "platform": "node" }, diff --git a/code/frameworks/react-native-web-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/react-native-web-vite/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/react-native-web-vite/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/react-native-web-vite/src/types.ts b/code/frameworks/react-native-web-vite/src/types.ts index 7436accebc84..5558722e03d7 100644 --- a/code/frameworks/react-native-web-vite/src/types.ts +++ b/code/frameworks/react-native-web-vite/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -31,7 +30,3 @@ export type StorybookConfig = Omit & { options: FrameworkOptions; }; }; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/react-vite/package.json b/code/frameworks/react-vite/package.json index d871585cf42c..2b8a1696d12d 100644 --- a/code/frameworks/react-vite/package.json +++ b/code/frameworks/react-vite/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -93,7 +99,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/csf-factory/defineMainConfig.ts" ], "platform": "node" }, diff --git a/code/frameworks/react-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/react-vite/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/react-vite/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/react-vite/src/types.ts b/code/frameworks/react-vite/src/types.ts index be84bca936ca..58cba705bf56 100644 --- a/code/frameworks/react-vite/src/types.ts +++ b/code/frameworks/react-vite/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -64,7 +63,3 @@ export type StorybookConfig = Omit< StorybookConfigFramework & { typescript?: Partial; }; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/react-webpack5/package.json b/code/frameworks/react-webpack5/package.json index c6c05892a81f..0450c35a9999 100644 --- a/code/frameworks/react-webpack5/package.json +++ b/code/frameworks/react-webpack5/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -74,7 +80,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/csf-factory/defineMainConfig.ts" ], "platform": "node" }, diff --git a/code/frameworks/react-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/react-webpack5/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/react-webpack5/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/react-webpack5/src/types.ts b/code/frameworks/react-webpack5/src/types.ts index 006a08cdf667..6366ca3cd857 100644 --- a/code/frameworks/react-webpack5/src/types.ts +++ b/code/frameworks/react-webpack5/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -45,7 +44,3 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/server-webpack5/package.json b/code/frameworks/server-webpack5/package.json index ef6b005495c5..03551f787206 100644 --- a/code/frameworks/server-webpack5/package.json +++ b/code/frameworks/server-webpack5/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -67,7 +73,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/csf-factory/defineMainConfig.ts" ], "platform": "node" }, diff --git a/code/frameworks/server-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/server-webpack5/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/server-webpack5/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/server-webpack5/src/types.ts b/code/frameworks/server-webpack5/src/types.ts index fd7c5d91d960..1040a0fea8bb 100644 --- a/code/frameworks/server-webpack5/src/types.ts +++ b/code/frameworks/server-webpack5/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -44,7 +43,3 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/svelte-vite/package.json b/code/frameworks/svelte-vite/package.json index 5007a4d2ff35..a363bd82c44d 100644 --- a/code/frameworks/svelte-vite/package.json +++ b/code/frameworks/svelte-vite/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -78,7 +84,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/csf-factory/defineMainConfig.ts" ], "platform": "node" }, diff --git a/code/frameworks/svelte-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/svelte-vite/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/svelte-vite/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/svelte-vite/src/types.ts b/code/frameworks/svelte-vite/src/types.ts index 908b77101109..d6efce7feaef 100644 --- a/code/frameworks/svelte-vite/src/types.ts +++ b/code/frameworks/svelte-vite/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -37,7 +36,3 @@ export type StorybookConfig = Omit< > & StorybookConfigVite & StorybookConfigFramework; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/svelte-webpack5/package.json b/code/frameworks/svelte-webpack5/package.json index 78b652a38c07..23251f45ed73 100644 --- a/code/frameworks/svelte-webpack5/package.json +++ b/code/frameworks/svelte-webpack5/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -70,7 +76,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/csf-factory/defineMainConfig.ts" ], "platform": "node" }, diff --git a/code/frameworks/svelte-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/svelte-webpack5/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/svelte-webpack5/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/svelte-webpack5/src/types.ts b/code/frameworks/svelte-webpack5/src/types.ts index 62381933e4ed..2770043e12dd 100644 --- a/code/frameworks/svelte-webpack5/src/types.ts +++ b/code/frameworks/svelte-webpack5/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -45,7 +44,3 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/sveltekit/package.json b/code/frameworks/sveltekit/package.json index 6165990d8b1e..1d2bd99d74cc 100644 --- a/code/frameworks/sveltekit/package.json +++ b/code/frameworks/sveltekit/package.json @@ -41,6 +41,12 @@ "require": "./dist/vite-plugin.js", "import": "./dist/vite-plugin.mjs" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -84,7 +90,8 @@ "./src/index.ts", "./src/preview.ts", "./src/preset.ts", - "./src/vite-plugin.ts" + "./src/vite-plugin.ts", + "./src/csf-factory/defineMainConfig.ts" ], "platform": "node" }, diff --git a/code/frameworks/sveltekit/src/csf-factory/defineMainConfig.ts b/code/frameworks/sveltekit/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/sveltekit/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/sveltekit/src/types.ts b/code/frameworks/sveltekit/src/types.ts index 6aef9a709fd3..975a184f4545 100644 --- a/code/frameworks/sveltekit/src/types.ts +++ b/code/frameworks/sveltekit/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -64,7 +63,3 @@ export type SvelteKitParameters = Partial<{ enhance: typeof enhance; }; }>; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/vue3-vite/package.json b/code/frameworks/vue3-vite/package.json index 8d9b0f427a6a..a71a0a718c1c 100644 --- a/code/frameworks/vue3-vite/package.json +++ b/code/frameworks/vue3-vite/package.json @@ -35,6 +35,12 @@ "require": "./dist/vite-plugin.js", "import": "./dist/vite-plugin.mjs" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -80,7 +86,8 @@ "entries": [ "./src/index.ts", "./src/preset.ts", - "./src/vite-plugin.ts" + "./src/vite-plugin.ts", + "./src/csf-factory/defineMainConfig.ts" ], "platform": "node" }, diff --git a/code/frameworks/vue3-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/vue3-vite/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/vue3-vite/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/vue3-vite/src/types.ts b/code/frameworks/vue3-vite/src/types.ts index 6ac74e77f10f..d02b791f294b 100644 --- a/code/frameworks/vue3-vite/src/types.ts +++ b/code/frameworks/vue3-vite/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -100,7 +99,3 @@ export type VueDocgenInfoEntry< ? VueDocgenInfo<'vue-component-meta'>[Exclude] : VueDocgenInfo<'vue-docgen-api'>[Exclude] >; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/vue3-webpack5/package.json b/code/frameworks/vue3-webpack5/package.json index b08859e0a714..f01b9eff2869 100644 --- a/code/frameworks/vue3-webpack5/package.json +++ b/code/frameworks/vue3-webpack5/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -71,7 +77,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/csf-factory/defineMainConfig.ts" ], "platform": "node" }, diff --git a/code/frameworks/vue3-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/vue3-webpack5/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/vue3-webpack5/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/vue3-webpack5/src/types.ts b/code/frameworks/vue3-webpack5/src/types.ts index bd1b1eac450c..9d6aa2467884 100644 --- a/code/frameworks/vue3-webpack5/src/types.ts +++ b/code/frameworks/vue3-webpack5/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString } from 'storybook/internal/types'; import type { @@ -44,7 +43,3 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/web-components-vite/package.json b/code/frameworks/web-components-vite/package.json index 92a76784a0aa..18b6483a8c1f 100644 --- a/code/frameworks/web-components-vite/package.json +++ b/code/frameworks/web-components-vite/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -66,6 +72,7 @@ }, "bundler": { "entries": [ + "./src/csf-factory/defineMainConfig.ts", "./src/index.ts", "./src/preset.ts" ], diff --git a/code/frameworks/web-components-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/web-components-vite/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/web-components-vite/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/web-components-vite/src/types.ts b/code/frameworks/web-components-vite/src/types.ts index 8954bf3221df..cb8328dc9770 100644 --- a/code/frameworks/web-components-vite/src/types.ts +++ b/code/frameworks/web-components-vite/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -37,7 +36,3 @@ export type StorybookConfig = Omit< > & StorybookConfigVite & StorybookConfigFramework; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/web-components-webpack5/package.json b/code/frameworks/web-components-webpack5/package.json index aa4281ef433e..656279dc71f7 100644 --- a/code/frameworks/web-components-webpack5/package.json +++ b/code/frameworks/web-components-webpack5/package.json @@ -33,6 +33,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./main": { + "types": "./dist/csf-factory/defineMainConfig.d.ts", + "node": "./dist/csf-factory/defineMainConfig.js", + "import": "./dist/csf-factory/defineMainConfig.mjs", + "require": "./dist/csf-factory/defineMainConfig.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -71,7 +77,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/csf-factory/defineMainConfig.ts" ], "platform": "node" }, diff --git a/code/frameworks/web-components-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/web-components-webpack5/src/csf-factory/defineMainConfig.ts new file mode 100644 index 000000000000..5039323552fe --- /dev/null +++ b/code/frameworks/web-components-webpack5/src/csf-factory/defineMainConfig.ts @@ -0,0 +1,7 @@ +import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; + +import type { StorybookConfig } from '../types'; + +export function defineConfig(config: StorybookConfig) { + return commonDefineConfig(config); +} diff --git a/code/frameworks/web-components-webpack5/src/types.ts b/code/frameworks/web-components-webpack5/src/types.ts index f6d1c9552392..0cd693b1fd4a 100644 --- a/code/frameworks/web-components-webpack5/src/types.ts +++ b/code/frameworks/web-components-webpack5/src/types.ts @@ -1,4 +1,3 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { CompatibleString, StorybookConfig as StorybookConfigBase, @@ -44,7 +43,3 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; - -export function defineConfig(config: StorybookConfig) { - return commonDefineConfig(config); -} From df000bb5cf83833e22abeed03185fcbf46470f4b Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 17 Jan 2025 12:34:39 +0100 Subject: [PATCH 058/144] fix internal type import --- code/core/src/common/defineConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/common/defineConfig.ts b/code/core/src/common/defineConfig.ts index 08006b683343..2a16d88aa474 100644 --- a/code/core/src/common/defineConfig.ts +++ b/code/core/src/common/defineConfig.ts @@ -1,4 +1,4 @@ -import type { StorybookConfig } from '../types/modules/core-common'; +import type { StorybookConfig } from '@storybook/core/types'; export function defineConfig(config: TConfig): TConfig { return config; From 5ac85efd5cece91d01db7e9a0f0d25536885a181 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 17 Jan 2025 13:07:42 +0100 Subject: [PATCH 059/144] rename the import and function --- code/frameworks/angular/package.json | 2 +- code/frameworks/angular/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/ember/package.json | 2 +- code/frameworks/ember/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/experimental-nextjs-vite/package.json | 2 +- .../src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/html-vite/package.json | 2 +- code/frameworks/html-vite/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/html-webpack5/package.json | 2 +- .../html-webpack5/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/nextjs/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/preact-vite/package.json | 2 +- code/frameworks/preact-vite/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/preact-webpack5/package.json | 2 +- .../preact-webpack5/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/react-native-web-vite/package.json | 2 +- .../react-native-web-vite/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/react-vite/package.json | 2 +- code/frameworks/react-vite/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/react-webpack5/package.json | 2 +- .../react-webpack5/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/server-webpack5/package.json | 2 +- .../server-webpack5/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/svelte-vite/package.json | 2 +- code/frameworks/svelte-vite/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/svelte-webpack5/package.json | 2 +- .../svelte-webpack5/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/sveltekit/package.json | 2 +- code/frameworks/sveltekit/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/vue3-vite/package.json | 2 +- code/frameworks/vue3-vite/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/vue3-webpack5/package.json | 2 +- .../vue3-webpack5/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/web-components-vite/package.json | 2 +- .../web-components-vite/src/csf-factory/defineMainConfig.ts | 2 +- code/frameworks/web-components-webpack5/package.json | 2 +- .../web-components-webpack5/src/csf-factory/defineMainConfig.ts | 2 +- 37 files changed, 37 insertions(+), 37 deletions(-) diff --git a/code/frameworks/angular/package.json b/code/frameworks/angular/package.json index 9623dd81ceb2..91a2d3da4c11 100644 --- a/code/frameworks/angular/package.json +++ b/code/frameworks/angular/package.json @@ -27,7 +27,7 @@ "import": "./dist/index.mjs", "require": "./dist/index.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.js", diff --git a/code/frameworks/angular/src/csf-factory/defineMainConfig.ts b/code/frameworks/angular/src/csf-factory/defineMainConfig.ts index 3092f2e9d68d..24bc05422ac9 100644 --- a/code/frameworks/angular/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/angular/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/ember/package.json b/code/frameworks/ember/package.json index f9f61961c054..e9f712956e5c 100644 --- a/code/frameworks/ember/package.json +++ b/code/frameworks/ember/package.json @@ -23,7 +23,7 @@ "import": "./dist/index.mjs", "require": "./dist/index.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.js", diff --git a/code/frameworks/ember/src/csf-factory/defineMainConfig.ts b/code/frameworks/ember/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/ember/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/ember/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/experimental-nextjs-vite/package.json b/code/frameworks/experimental-nextjs-vite/package.json index 7604bf925799..4b20e1113092 100644 --- a/code/frameworks/experimental-nextjs-vite/package.json +++ b/code/frameworks/experimental-nextjs-vite/package.json @@ -58,7 +58,7 @@ "import": "./dist/vite-plugin/index.mjs", "require": "./dist/vite-plugin/index.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/experimental-nextjs-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/experimental-nextjs-vite/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/experimental-nextjs-vite/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/experimental-nextjs-vite/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/html-vite/package.json b/code/frameworks/html-vite/package.json index 89745dfd9b99..1ce79c846355 100644 --- a/code/frameworks/html-vite/package.json +++ b/code/frameworks/html-vite/package.json @@ -30,7 +30,7 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/html-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/html-vite/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/html-vite/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/html-vite/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/html-webpack5/package.json b/code/frameworks/html-webpack5/package.json index 0cbb3b253a46..8a7167dd0f54 100644 --- a/code/frameworks/html-webpack5/package.json +++ b/code/frameworks/html-webpack5/package.json @@ -30,7 +30,7 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/html-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/html-webpack5/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/html-webpack5/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/html-webpack5/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/nextjs/src/csf-factory/defineMainConfig.ts b/code/frameworks/nextjs/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/nextjs/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/nextjs/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/preact-vite/package.json b/code/frameworks/preact-vite/package.json index 0a2679104192..e639c1c7e0bb 100644 --- a/code/frameworks/preact-vite/package.json +++ b/code/frameworks/preact-vite/package.json @@ -29,7 +29,7 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/preact-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/preact-vite/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/preact-vite/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/preact-vite/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/preact-webpack5/package.json b/code/frameworks/preact-webpack5/package.json index 5064cc3eea6f..cd7effb465d0 100644 --- a/code/frameworks/preact-webpack5/package.json +++ b/code/frameworks/preact-webpack5/package.json @@ -30,7 +30,7 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/preact-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/preact-webpack5/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/preact-webpack5/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/preact-webpack5/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/react-native-web-vite/package.json b/code/frameworks/react-native-web-vite/package.json index c8079ec2a22b..8f392bc254dd 100644 --- a/code/frameworks/react-native-web-vite/package.json +++ b/code/frameworks/react-native-web-vite/package.json @@ -35,7 +35,7 @@ "import": "./dist/vite-plugin.mjs", "require": "./dist/vite-plugin.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/react-native-web-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/react-native-web-vite/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/react-native-web-vite/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/react-native-web-vite/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/react-vite/package.json b/code/frameworks/react-vite/package.json index 2b8a1696d12d..6228aea36f4c 100644 --- a/code/frameworks/react-vite/package.json +++ b/code/frameworks/react-vite/package.json @@ -30,7 +30,7 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/react-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/react-vite/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/react-vite/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/react-vite/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/react-webpack5/package.json b/code/frameworks/react-webpack5/package.json index 0450c35a9999..8c26cc0072aa 100644 --- a/code/frameworks/react-webpack5/package.json +++ b/code/frameworks/react-webpack5/package.json @@ -30,7 +30,7 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/react-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/react-webpack5/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/react-webpack5/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/react-webpack5/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/server-webpack5/package.json b/code/frameworks/server-webpack5/package.json index 03551f787206..1f61cce000ec 100644 --- a/code/frameworks/server-webpack5/package.json +++ b/code/frameworks/server-webpack5/package.json @@ -30,7 +30,7 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/server-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/server-webpack5/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/server-webpack5/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/server-webpack5/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/svelte-vite/package.json b/code/frameworks/svelte-vite/package.json index a363bd82c44d..b82e3b98b28b 100644 --- a/code/frameworks/svelte-vite/package.json +++ b/code/frameworks/svelte-vite/package.json @@ -30,7 +30,7 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/svelte-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/svelte-vite/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/svelte-vite/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/svelte-vite/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/svelte-webpack5/package.json b/code/frameworks/svelte-webpack5/package.json index 23251f45ed73..f483c25bf7e3 100644 --- a/code/frameworks/svelte-webpack5/package.json +++ b/code/frameworks/svelte-webpack5/package.json @@ -30,7 +30,7 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/svelte-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/svelte-webpack5/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/svelte-webpack5/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/svelte-webpack5/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/sveltekit/package.json b/code/frameworks/sveltekit/package.json index 1d2bd99d74cc..b47ab7a26343 100644 --- a/code/frameworks/sveltekit/package.json +++ b/code/frameworks/sveltekit/package.json @@ -41,7 +41,7 @@ "require": "./dist/vite-plugin.js", "import": "./dist/vite-plugin.mjs" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/sveltekit/src/csf-factory/defineMainConfig.ts b/code/frameworks/sveltekit/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/sveltekit/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/sveltekit/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/vue3-vite/package.json b/code/frameworks/vue3-vite/package.json index a71a0a718c1c..2852d303990b 100644 --- a/code/frameworks/vue3-vite/package.json +++ b/code/frameworks/vue3-vite/package.json @@ -35,7 +35,7 @@ "require": "./dist/vite-plugin.js", "import": "./dist/vite-plugin.mjs" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/vue3-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/vue3-vite/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/vue3-vite/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/vue3-vite/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/vue3-webpack5/package.json b/code/frameworks/vue3-webpack5/package.json index f01b9eff2869..973f16e8a820 100644 --- a/code/frameworks/vue3-webpack5/package.json +++ b/code/frameworks/vue3-webpack5/package.json @@ -30,7 +30,7 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/vue3-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/vue3-webpack5/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/vue3-webpack5/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/vue3-webpack5/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/web-components-vite/package.json b/code/frameworks/web-components-vite/package.json index 18b6483a8c1f..27b17e55155e 100644 --- a/code/frameworks/web-components-vite/package.json +++ b/code/frameworks/web-components-vite/package.json @@ -30,7 +30,7 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/web-components-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/web-components-vite/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/web-components-vite/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/web-components-vite/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } diff --git a/code/frameworks/web-components-webpack5/package.json b/code/frameworks/web-components-webpack5/package.json index 656279dc71f7..2e9b89f3223c 100644 --- a/code/frameworks/web-components-webpack5/package.json +++ b/code/frameworks/web-components-webpack5/package.json @@ -33,7 +33,7 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./main": { + "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", "import": "./dist/csf-factory/defineMainConfig.mjs", diff --git a/code/frameworks/web-components-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/web-components-webpack5/src/csf-factory/defineMainConfig.ts index 5039323552fe..dd999c40b17b 100644 --- a/code/frameworks/web-components-webpack5/src/csf-factory/defineMainConfig.ts +++ b/code/frameworks/web-components-webpack5/src/csf-factory/defineMainConfig.ts @@ -2,6 +2,6 @@ import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; import type { StorybookConfig } from '../types'; -export function defineConfig(config: StorybookConfig) { +export function defineMain(config: StorybookConfig) { return commonDefineConfig(config); } From 778405d379296e72d1bc2d52af3bb6de6e9ee33a Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Thu, 16 Jan 2025 15:00:20 +0100 Subject: [PATCH 060/144] Move csf factory codemod to automigrations --- .../cli-storybook/src/automigrate/codemod.ts | 81 ++++++++++ .../src/automigrate/fixes}/csf-3-to-4.test.ts | 92 ++++++++++- .../src/automigrate/fixes}/csf-3-to-4.ts | 143 +++++++++--------- .../src/automigrate/fixes/index.ts | 7 +- .../cli-storybook/src/automigrate/index.ts | 31 +++- .../cli-storybook/src/automigrate/types.ts | 22 ++- 6 files changed, 291 insertions(+), 85 deletions(-) create mode 100644 code/lib/cli-storybook/src/automigrate/codemod.ts rename code/lib/{codemod/src/transforms/__tests__ => cli-storybook/src/automigrate/fixes}/csf-3-to-4.test.ts (67%) rename code/lib/{codemod/src/transforms => cli-storybook/src/automigrate/fixes}/csf-3-to-4.ts (57%) diff --git a/code/lib/cli-storybook/src/automigrate/codemod.ts b/code/lib/cli-storybook/src/automigrate/codemod.ts new file mode 100644 index 000000000000..9fb9e7c4cb94 --- /dev/null +++ b/code/lib/cli-storybook/src/automigrate/codemod.ts @@ -0,0 +1,81 @@ +import { promises as fs } from 'fs'; +// eslint-disable-next-line depend/ban-dependencies +import { glob } from 'glob'; +import picocolors from 'picocolors'; + +const logger = console; + +export interface FileInfo { + path: string; + source: string; +} + +/** + * Runs a codemod transformation on files matching the specified glob pattern. + * + * The function processes each file matching the glob pattern, applies the transform function, and + * writes the transformed source back to the file if it has changed. + * + * @example + * + * ``` + * await runCodemod('*.stories.tsx', async (fileInfo) => { + * // Transform the file source return + * return fileInfo.source.replace(/foo/g, 'bar'); + * }); + * ``` + */ +export async function runCodemod( + globPattern: string = '**/*.stories.*', + transform: (source: FileInfo) => Promise, + { dryRun = false }: { dryRun?: boolean } = {} +) { + let modifiedCount = 0; + let unmodifiedCount = 0; + let errorCount = 0; + + try { + const files = await glob(globPattern, { + nodir: true, + follow: true, + ignore: ['node_modules/**', 'dist/**', 'storybook-static/**', 'build/**'], + }); + + await Promise.all( + files.map(async (file) => { + try { + const source = await fs.readFile(file, 'utf-8'); + const fileInfo: FileInfo = { path: file, source }; + const transformedSource = await transform(fileInfo); + + if (transformedSource !== source) { + if (!dryRun) { + await fs.writeFile(file, transformedSource, 'utf-8'); + } + modifiedCount++; + } else { + unmodifiedCount++; + } + } catch (fileError) { + logger.error(`Error processing file ${file}:`, fileError); + errorCount++; + } + }) + ); + } catch (error) { + logger.error('Error applying transform:', error); + errorCount++; + } + + logger.log( + `Summary: ${picocolors.green(`${modifiedCount} transformed`)}, ${picocolors.yellow(`${unmodifiedCount} unmodified`)}, ${picocolors.red(`${errorCount} errors`)}` + ); + + if (dryRun) { + logger.log( + picocolors.bold( + `This was a dry run. Run without --dry-run to apply the transformation to ${modifiedCount} files.` + ) + ); + } +} diff --git a/code/lib/codemod/src/transforms/__tests__/csf-3-to-4.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.test.ts similarity index 67% rename from code/lib/codemod/src/transforms/__tests__/csf-3-to-4.test.ts rename to code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.test.ts index 7dcef73c2e53..c6f59ded571f 100644 --- a/code/lib/codemod/src/transforms/__tests__/csf-3-to-4.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'; import { dedent } from 'ts-dedent'; -import _transform from '../csf-3-to-4'; +import { csf4Transform } from './csf-3-to-4'; expect.addSnapshotSerializer({ serialize: (val: any) => (typeof val === 'string' ? val : val.toString()), @@ -10,7 +10,7 @@ expect.addSnapshotSerializer({ }); const transform = async (source: string) => - (await _transform({ source, path: 'Component.stories.tsx' })).trim(); + (await csf4Transform({ source, path: 'Component.stories.tsx' })).trim(); describe('csf-3-to-4', () => { describe('javascript', () => { @@ -96,9 +96,95 @@ describe('csf-3-to-4', () => { }); `); }); + + it('if there is an existing local constant called config, rename storybook config import', async () => { + await expect( + transform(dedent` + const componentMeta = { title: 'Component' }; + export default componentMeta; + const config = {}; + export const A = { + args: { primary: true }, + render: (args) => + }; + `) + ).resolves.toMatchInlineSnapshot(` + import { storybookConfig as config } from "#.storybook/preview"; + const meta = storybookConfig.meta({ title: 'Component' }); + const config = {}; + export const A = meta.story({ + args: { primary: true }, + render: (args) => + }); + `); + }); + + it('converts CSF1 into CSF4 with render', async () => { + await expect( + transform(dedent` + const meta = { title: 'Component' }; + export default meta; + export const CSF1Story = () =>
Hello
; + `) + ).resolves.toMatchInlineSnapshot(` + import { config } from '#.storybook/preview'; + + const meta = config.meta({ title: 'Component' }); + export const CSF1Story = meta.story({ + render: () =>
Hello
, + }); + `); + }); }); describe('typescript', () => { + const inlineMetaSatisfies = dedent` + import { Meta, StoryObj as CSF3 } from '@storybook/react'; + import { ComponentProps } from './Component'; + + export default { title: 'Component', component: Component } satisfies Meta; + + export const A: CSF3 = { + args: { primary: true } + }; + `; + it('meta satisfies syntax', async () => { + await expect(transform(inlineMetaSatisfies)).resolves.toMatchInlineSnapshot(` + import { config } from '#.storybook/preview'; + + import { ComponentProps } from './Component'; + + const meta = config.meta({ title: 'Component', component: Component }); + + export const A = meta.story({ + args: { primary: true }, + }); + `); + }); + + const inlineMetaAs = dedent` + import { Meta, StoryObj as CSF3 } from '@storybook/react'; + import { ComponentProps } from './Component'; + + export default { title: 'Component', component: Component } as Meta; + + export const A: CSF3 = { + args: { primary: true } + }; + `; + it('meta as syntax', async () => { + await expect(transform(inlineMetaAs)).resolves.toMatchInlineSnapshot(` + import { config } from '#.storybook/preview'; + + import { ComponentProps } from './Component'; + + const meta = config.meta({ title: 'Component', component: Component }); + + export const A = meta.story({ + args: { primary: true }, + }); + `); + }); const metaSatisfies = dedent` import { Meta, StoryObj as CSF3 } from '@storybook/react'; import { ComponentProps } from './Component'; @@ -201,6 +287,8 @@ describe('csf-3-to-4', () => { it('should yield the same result to all syntaxes', async () => { const allSnippets = await Promise.all([ + transform(inlineMetaSatisfies), + transform(inlineMetaAs), transform(metaSatisfies), transform(metaAs), transform(storySatisfies), diff --git a/code/lib/codemod/src/transforms/csf-3-to-4.ts b/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.ts similarity index 57% rename from code/lib/codemod/src/transforms/csf-3-to-4.ts rename to code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.ts index 9d8856f25150..0e2dd9755746 100644 --- a/code/lib/codemod/src/transforms/csf-3-to-4.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.ts @@ -1,22 +1,19 @@ /* eslint-disable no-underscore-dangle */ -import { types as t, traverse } from '@storybook/core/babel'; +import { types as t } from 'storybook/internal/babel'; +import { isValidPreviewPath, loadCsf, printCsf } from 'storybook/internal/csf-tools'; -import { isValidPreviewPath, loadCsf, printCsf } from '@storybook/core/csf-tools'; +import prompts from 'prompts'; -import * as babel from '@babel/core'; -import type { FileInfo } from 'jscodeshift'; -import prettier from 'prettier'; - -const logger = console; - -export default async function transform(info: FileInfo) { - const csf = loadCsf(info.source, { makeTitle: (title) => title }); - const fileNode = csf._ast; +import type { FileInfo } from '../codemod'; +import { runCodemod } from '../codemod'; +import type { CommandFix } from '../types'; +export async function csf4Transform(info: FileInfo) { + const csf = loadCsf(info.source, { makeTitle: () => 'FIXME' }); try { csf.parse(); } catch (err) { - logger.log(`Error ${err}, skipping`); + logger.log(`Error when parsing ${info.path}, skipping:\n${err}`); return info.source; } @@ -27,9 +24,23 @@ export default async function transform(info: FileInfo) { * * `import { config } from '#.storybook/preview'`; */ - const programNode = fileNode.program; + const programNode = csf._ast.program; let foundConfigImport = false; + // Check if a root-level constant named 'config' exists + const hasRootLevelConfig = programNode.body.some( + (n) => + t.isVariableDeclaration(n) && + n.declarations.some((declaration) => t.isIdentifier(declaration.id, { name: 'config' })) + ); + + const sbConfigImportName = hasRootLevelConfig ? 'storybookConfig' : 'config'; + + const sbConfigImportSpecifier = t.importSpecifier( + t.identifier('config'), + t.identifier(sbConfigImportName) + ); + programNode.body.forEach((node) => { if (t.isImportDeclaration(node) && isValidPreviewPath(node.source.value)) { const hasConfigSpecifier = node.specifiers.some( @@ -38,12 +49,7 @@ export default async function transform(info: FileInfo) { ); if (!hasConfigSpecifier) { - node.specifiers.push( - babel.types.importSpecifier( - babel.types.identifier('config'), - babel.types.identifier('config') - ) - ); + node.specifiers.push(sbConfigImportSpecifier); } foundConfigImport = true; @@ -54,7 +60,7 @@ export default async function transform(info: FileInfo) { Object.entries(csf._storyExports).forEach(([key, decl]) => { const id = decl.id; - const declarator = decl as babel.types.VariableDeclarator; + const declarator = decl as t.VariableDeclarator; let init = t.isVariableDeclarator(declarator) ? declarator.init : undefined; if (t.isIdentifier(id) && init) { @@ -70,29 +76,37 @@ export default async function transform(info: FileInfo) { } // Wrap the object in `meta.story()` - declarator.init = babel.types.callExpression( - babel.types.memberExpression( - babel.types.identifier(metaVariableName), - babel.types.identifier('story') - ), + declarator.init = t.callExpression( + t.memberExpression(t.identifier(metaVariableName), t.identifier('story')), [init] ); + } else if (t.isArrowFunctionExpression(init)) { + // Transform CSF1 to meta.story({ render: }) + const renderProperty = t.objectProperty(t.identifier('render'), init); + + const objectExpression = t.objectExpression([renderProperty]); + + declarator.init = t.callExpression( + t.memberExpression(t.identifier(metaVariableName), t.identifier('story')), + [objectExpression] + ); } } }); // modify meta if (csf._metaPath) { - const declaration = csf._metaPath.node.declaration; + let declaration = csf._metaPath.node.declaration; + if (t.isTSSatisfiesExpression(declaration) || t.isTSAsExpression(declaration)) { + declaration = declaration.expression; + } + if (t.isObjectExpression(declaration)) { - const metaVariable = babel.types.variableDeclaration('const', [ - babel.types.variableDeclarator( - babel.types.identifier(metaVariableName), - babel.types.callExpression( - babel.types.memberExpression( - babel.types.identifier('config'), - babel.types.identifier('meta') - ), + const metaVariable = t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier(metaVariableName), + t.callExpression( + t.memberExpression(t.identifier(sbConfigImportName), t.identifier('meta')), [declaration] ) ), @@ -113,18 +127,15 @@ export default async function transform(info: FileInfo) { const originalName = declaration.name; // Always rename the meta variable to 'meta' - binding.path.node.id = babel.types.identifier(metaVariableName); + binding.path.node.id = t.identifier(metaVariableName); let init = binding.path.node.init; if (t.isTSSatisfiesExpression(init) || t.isTSAsExpression(init)) { init = init.expression; } if (t.isObjectExpression(init)) { - binding.path.node.init = babel.types.callExpression( - babel.types.memberExpression( - babel.types.identifier('config'), - babel.types.identifier('meta') - ), + binding.path.node.init = t.callExpression( + t.memberExpression(t.identifier(sbConfigImportName), t.identifier('meta')), [init] ); } @@ -139,35 +150,13 @@ export default async function transform(info: FileInfo) { } if (hasMeta && !foundConfigImport) { - const configImport = babel.types.importDeclaration( - [ - babel.types.importSpecifier( - babel.types.identifier('config'), - babel.types.identifier('config') - ), - ], - babel.types.stringLiteral('#.storybook/preview') + const configImport = t.importDeclaration( + [sbConfigImportSpecifier], + t.stringLiteral('#.storybook/preview') ); programNode.body.unshift(configImport); } - function isSpecifierUsed(name: string) { - let isUsed = false; - - // Traverse the AST and check for usage of the name - traverse(programNode, { - Identifier(path) { - if (path.node.name === name) { - isUsed = true; - // Stop traversal early if we've found a match - path.stop(); - } - }, - }); - - return isUsed; - } - // Remove type imports – now inferred – from @storybook/* packages const disallowlist = [ 'Story', @@ -210,12 +199,30 @@ export default async function transform(info: FileInfo) { let output = printCsf(csf).code; try { + // eslint-disable-next-line import/no-extraneous-dependencies + const prettier = await import('prettier'); output = await prettier.format(output, { ...(await prettier.resolveConfig(info.path)), filepath: info.path, }); - } catch (e) { - logger.log(`Failed applying prettier to ${info.path}.`); - } + } catch (e) {} return output; } + +const logger = console; + +export const csf3to4: CommandFix = { + id: 'csf-3-to-4', + promptType: 'command', + async run({ dryRun }) { + logger.log('Please enter the glob for your stories to migrate'); + const { glob: globString } = await prompts({ + type: 'text', + name: 'glob', + message: 'glob', + initial: '**/*.stories.*', + }); + + await runCodemod(globString, csf4Transform, { dryRun }); + }, +}; diff --git a/code/lib/cli-storybook/src/automigrate/fixes/index.ts b/code/lib/cli-storybook/src/automigrate/fixes/index.ts index dfd43100665c..acd7d06183d9 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/index.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/index.ts @@ -1,4 +1,4 @@ -import type { Fix } from '../types'; +import type { CommandFix, Fix } from '../types'; import { addonA11yAddonTest } from './addon-a11y-addon-test'; import { addonPostCSS } from './addon-postcss'; import { addonsAPI } from './addons-api'; @@ -8,6 +8,7 @@ import { autodocsTags } from './autodocs-tags'; import { autodocsTrue } from './autodocs-true'; import { builderVite } from './builder-vite'; import { cra5 } from './cra5'; +import { csf3to4 } from './csf-3-to-4'; import { eslintPlugin } from './eslint-plugin'; import { initialGlobals } from './initial-globals'; import { mdx1to3 } from './mdx-1-to-3'; @@ -70,3 +71,7 @@ export const allFixes: Fix[] = [ ]; export const initFixes: Fix[] = [eslintPlugin]; + +// These are specific fixes that only occur when triggered on command, and are hidden otherwise. +// e.g. npx automigrate csf-3-to-4 +export const commandFixes: CommandFix[] = [csf3to4]; diff --git a/code/lib/cli-storybook/src/automigrate/index.ts b/code/lib/cli-storybook/src/automigrate/index.ts index 9d98d97d7013..a25455e38b1a 100644 --- a/code/lib/cli-storybook/src/automigrate/index.ts +++ b/code/lib/cli-storybook/src/automigrate/index.ts @@ -27,7 +27,7 @@ import type { PreCheckFailure, Prompt, } from './fixes'; -import { FixStatus, allFixes } from './fixes'; +import { FixStatus, allFixes, commandFixes } from './fixes'; import { upgradeStorybookRelatedDependencies } from './fixes/upgrade-storybook-related-dependencies'; import { cleanLog } from './helpers/cleanLog'; import { getMigrationSummary } from './helpers/getMigrationSummary'; @@ -60,7 +60,7 @@ const cleanup = () => { }; const logAvailableMigrations = () => { - const availableFixes = allFixes + const availableFixes = [...allFixes, ...commandFixes] .map((f) => picocolors.yellow(f.id)) .map((x) => `- ${x}`) .join('\n'); @@ -82,10 +82,11 @@ export const doAutomigrate = async (options: AutofixOptionsFromCLI) => { getCoercedStorybookVersion(packageManager), ]); - const { configDir: inferredConfigDir, mainConfig: mainConfigPath } = getStorybookInfo( - packageJson, - options.configDir - ); + const { + configDir: inferredConfigDir, + mainConfig: mainConfigPath, + previewConfig: previewConfigPath, + } = getStorybookInfo(packageJson, options.configDir); const configDir = options.configDir || inferredConfigDir || '.storybook'; if (!storybookVersion) { @@ -102,6 +103,7 @@ export const doAutomigrate = async (options: AutofixOptionsFromCLI) => { storybookVersion, beforeVersion: storybookVersion, mainConfigPath, + previewConfigPath, configDir, isUpgrade: false, isLatest: false, @@ -121,6 +123,7 @@ export const automigrate = async ({ list, configDir, mainConfigPath, + previewConfigPath, storybookVersion, beforeVersion, renderer: rendererPackage, @@ -137,6 +140,22 @@ export const automigrate = async ({ return null; } + // if an on-command migration is triggered, run it and bail + const commandFix = commandFixes.find((f) => f.id === fixId); + if (commandFix) { + logger.info(`🔎 Running migration ${picocolors.magenta(fixId)}..`); + + await commandFix.run({ + mainConfigPath, + previewConfigPath, + packageManager, + dryRun, + result: null, + }); + + return null; + } + const selectedFixes: Fix[] = inputFixes || allFixes.filter((fix) => { diff --git a/code/lib/cli-storybook/src/automigrate/types.ts b/code/lib/cli-storybook/src/automigrate/types.ts index 737d8f9018f7..3c62e6343cab 100644 --- a/code/lib/cli-storybook/src/automigrate/types.ts +++ b/code/lib/cli-storybook/src/automigrate/types.ts @@ -16,6 +16,7 @@ export interface RunOptions { result: ResultType; dryRun?: boolean; mainConfigPath: string; + previewConfigPath?: string; skipInstall?: boolean; } @@ -25,8 +26,9 @@ export interface RunOptions { * - Auto: the fix will be applied automatically * - Manual: the user will be prompted to apply the fix * - Notification: the user will be notified about some changes. A fix isn't required, though + * - Command: the fix will only be applied when specified directly by its id */ -export type Prompt = 'auto' | 'manual' | 'notification'; +export type Prompt = 'auto' | 'manual' | 'notification' | 'command'; type BaseFix = { id: string; @@ -46,17 +48,20 @@ type PromptType = | T | ((result: ResultType) => Promise | Prompt); -export type Fix = ( - | { +export type Fix = + | ({ promptType?: PromptType; run: (options: RunOptions) => Promise; - } - | { + } & BaseFix) + | ({ promptType: PromptType; run?: never; - } -) & - BaseFix; + } & BaseFix); + +export type CommandFix = { + promptType: PromptType; + run: (options: RunOptions) => Promise; +} & Omit, 'versionRange' | 'check' | 'prompt'>; export type FixId = string; @@ -69,6 +74,7 @@ export enum PreCheckFailure { export interface AutofixOptions extends Omit { packageManager: JsPackageManager; mainConfigPath: string; + previewConfigPath?: string; /** The version of Storybook before the migration. */ beforeVersion: string; storybookVersion: string; From e68b8f9975a56220b248d0576548e62499e0fdcd Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Thu, 16 Jan 2025 17:21:21 +0100 Subject: [PATCH 061/144] address review comments --- code/lib/cli-storybook/package.json | 1 + .../cli-storybook/src/automigrate/codemod.ts | 62 ++++++++++++------- .../src/automigrate/fixes/csf-3-to-4.test.ts | 5 +- .../src/automigrate/fixes/csf-3-to-4.ts | 17 ++--- code/yarn.lock | 17 +++++ 5 files changed, 65 insertions(+), 37 deletions(-) diff --git a/code/lib/cli-storybook/package.json b/code/lib/cli-storybook/package.json index 931146d117ba..7a3a97ed9c3f 100644 --- a/code/lib/cli-storybook/package.json +++ b/code/lib/cli-storybook/package.json @@ -55,6 +55,7 @@ "globby": "^14.0.1", "jscodeshift": "^0.15.1", "leven": "^3.1.0", + "p-limit": "^6.2.0", "prompts": "^2.4.0", "semver": "^7.3.7", "storybook": "workspace:*", diff --git a/code/lib/cli-storybook/src/automigrate/codemod.ts b/code/lib/cli-storybook/src/automigrate/codemod.ts index 9fb9e7c4cb94..00e2cb4db0f1 100644 --- a/code/lib/cli-storybook/src/automigrate/codemod.ts +++ b/code/lib/cli-storybook/src/automigrate/codemod.ts @@ -1,10 +1,15 @@ +import os from 'node:os'; + +import { formatFileContent } from 'storybook/internal/common'; + import { promises as fs } from 'fs'; -// eslint-disable-next-line depend/ban-dependencies -import { glob } from 'glob'; import picocolors from 'picocolors'; +import slash from 'slash'; const logger = console; +export const maxConcurrentTasks = Math.max(1, os.cpus().length - 1); + export interface FileInfo { path: string; source: string; @@ -28,39 +33,52 @@ export interface FileInfo { export async function runCodemod( globPattern: string = '**/*.stories.*', transform: (source: FileInfo) => Promise, - { dryRun = false }: { dryRun?: boolean } = {} + { dryRun = false, skipFormatting = false }: { dryRun?: boolean; skipFormatting?: boolean } = {} ) { let modifiedCount = 0; let unmodifiedCount = 0; let errorCount = 0; try { - const files = await glob(globPattern, { - nodir: true, - follow: true, + // Dynamically import these packages because they are pure ESM modules + // eslint-disable-next-line depend/ban-dependencies + const { globby } = await import('globby'); + + const pLimit = (await import('p-limit')).default; + + // glob only supports forward slashes + const files = await globby(slash(globPattern), { + followSymbolicLinks: true, ignore: ['node_modules/**', 'dist/**', 'storybook-static/**', 'build/**'], }); + const limit = pLimit(maxConcurrentTasks); + await Promise.all( - files.map(async (file) => { - try { - const source = await fs.readFile(file, 'utf-8'); - const fileInfo: FileInfo = { path: file, source }; - const transformedSource = await transform(fileInfo); + files.map((file) => + limit(async () => { + try { + const source = await fs.readFile(file, 'utf-8'); + const fileInfo: FileInfo = { path: file, source }; + const transformedSource = await transform(fileInfo); - if (transformedSource !== source) { - if (!dryRun) { - await fs.writeFile(file, transformedSource, 'utf-8'); + if (transformedSource !== source) { + if (!dryRun) { + const fileContent = skipFormatting + ? transformedSource + : await formatFileContent(file, transformedSource); + await fs.writeFile(file, fileContent, 'utf-8'); + } + modifiedCount++; + } else { + unmodifiedCount++; } - modifiedCount++; - } else { - unmodifiedCount++; + } catch (fileError) { + logger.error(`Error processing file ${file}:`, fileError); + errorCount++; } - } catch (fileError) { - logger.error(`Error processing file ${file}:`, fileError); - errorCount++; - } - }) + }) + ) ); } catch (error) { logger.error('Error applying transform:', error); diff --git a/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.test.ts index c6f59ded571f..d6c54e69d967 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.test.ts @@ -109,12 +109,13 @@ describe('csf-3-to-4', () => { }; `) ).resolves.toMatchInlineSnapshot(` - import { storybookConfig as config } from "#.storybook/preview"; + import { config as storybookConfig } from '#.storybook/preview'; + const meta = storybookConfig.meta({ title: 'Component' }); const config = {}; export const A = meta.story({ args: { primary: true }, - render: (args) => + render: (args) => , }); `); }); diff --git a/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.ts b/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.ts index 0e2dd9755746..305d60778dc9 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.ts @@ -1,5 +1,6 @@ /* eslint-disable no-underscore-dangle */ import { types as t } from 'storybook/internal/babel'; +import { formatFileContent } from 'storybook/internal/common'; import { isValidPreviewPath, loadCsf, printCsf } from 'storybook/internal/csf-tools'; import prompts from 'prompts'; @@ -37,8 +38,8 @@ export async function csf4Transform(info: FileInfo) { const sbConfigImportName = hasRootLevelConfig ? 'storybookConfig' : 'config'; const sbConfigImportSpecifier = t.importSpecifier( - t.identifier('config'), - t.identifier(sbConfigImportName) + t.identifier(sbConfigImportName), + t.identifier('config') ); programNode.body.forEach((node) => { @@ -196,17 +197,7 @@ export async function csf4Transform(info: FileInfo) { return true; }); - let output = printCsf(csf).code; - - try { - // eslint-disable-next-line import/no-extraneous-dependencies - const prettier = await import('prettier'); - output = await prettier.format(output, { - ...(await prettier.resolveConfig(info.path)), - filepath: info.path, - }); - } catch (e) {} - return output; + return printCsf(csf).code; } const logger = console; diff --git a/code/yarn.lock b/code/yarn.lock index 2307a03bf243..4fc46e9e2c86 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6128,6 +6128,7 @@ __metadata: globby: "npm:^14.0.1" jscodeshift: "npm:^0.15.1" leven: "npm:^3.1.0" + p-limit: "npm:^6.2.0" picocolors: "npm:^1.1.0" prompts: "npm:^2.4.0" semver: "npm:^7.3.7" @@ -22691,6 +22692,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:^6.2.0": + version: 6.2.0 + resolution: "p-limit@npm:6.2.0" + dependencies: + yocto-queue: "npm:^1.1.1" + checksum: 10c0/448bf55a1776ca1444594d53b3c731e68cdca00d44a6c8df06a2f6e506d5bbd540ebb57b05280f8c8bff992a630ed782a69612473f769a7473495d19e2270166 + languageName: node + linkType: hard + "p-locate@npm:^2.0.0": version: 2.0.0 resolution: "p-locate@npm:2.0.0" @@ -30496,6 +30506,13 @@ __metadata: languageName: node linkType: hard +"yocto-queue@npm:^1.1.1": + version: 1.1.1 + resolution: "yocto-queue@npm:1.1.1" + checksum: 10c0/cb287fe5e6acfa82690acb43c283de34e945c571a78a939774f6eaba7c285bacdf6c90fbc16ce530060863984c906d2b4c6ceb069c94d1e0a06d5f2b458e2a92 + languageName: node + linkType: hard + "yoctocolors-cjs@npm:^2.1.2": version: 2.1.2 resolution: "yoctocolors-cjs@npm:2.1.2" From 1f79546a6c825c8ea05a6aefa9a3ef2350c60646 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 17 Jan 2025 10:13:20 +0100 Subject: [PATCH 062/144] refactor and use automigrate instead of codemod in sandbox --- .../cli-storybook/src/automigrate/codemod.ts | 31 +- .../src/automigrate/fixes/csf-3-to-4.test.ts | 534 +++++++++--------- .../src/automigrate/fixes/csf-3-to-4.ts | 38 +- scripts/tasks/sandbox-parts.ts | 3 +- scripts/utils/cli-step.ts | 7 + 5 files changed, 326 insertions(+), 287 deletions(-) diff --git a/code/lib/cli-storybook/src/automigrate/codemod.ts b/code/lib/cli-storybook/src/automigrate/codemod.ts index 00e2cb4db0f1..1cb937d73398 100644 --- a/code/lib/cli-storybook/src/automigrate/codemod.ts +++ b/code/lib/cli-storybook/src/automigrate/codemod.ts @@ -13,6 +13,7 @@ export const maxConcurrentTasks = Math.max(1, os.cpus().length - 1); export interface FileInfo { path: string; source: string; + [key: string]: any; } /** @@ -32,25 +33,33 @@ export interface FileInfo { */ export async function runCodemod( globPattern: string = '**/*.stories.*', - transform: (source: FileInfo) => Promise, + transform: (source: FileInfo, ...rest: any) => Promise, { dryRun = false, skipFormatting = false }: { dryRun?: boolean; skipFormatting?: boolean } = {} ) { let modifiedCount = 0; let unmodifiedCount = 0; let errorCount = 0; - try { - // Dynamically import these packages because they are pure ESM modules - // eslint-disable-next-line depend/ban-dependencies - const { globby } = await import('globby'); + // Dynamically import these packages because they are pure ESM modules + // eslint-disable-next-line depend/ban-dependencies + const { globby } = await import('globby'); - const pLimit = (await import('p-limit')).default; + // glob only supports forward slashes + const files = await globby(slash(globPattern), { + followSymbolicLinks: true, + ignore: ['node_modules/**', 'dist/**', 'storybook-static/**', 'build/**'], + }); - // glob only supports forward slashes - const files = await globby(slash(globPattern), { - followSymbolicLinks: true, - ignore: ['node_modules/**', 'dist/**', 'storybook-static/**', 'build/**'], - }); + if (!files.length) { + logger.error( + `No files found for glob pattern "${globPattern}".\nPlease try a different pattern.\n` + ); + // eslint-disable-next-line local-rules/no-uncategorized-errors + throw new Error('No files matched'); + } + + try { + const pLimit = (await import('p-limit')).default; const limit = pLimit(maxConcurrentTasks); diff --git a/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.test.ts index d6c54e69d967..6bb4b438a479 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.test.ts @@ -1,5 +1,7 @@ import { describe, expect, it } from 'vitest'; +import { formatFileContent } from '@storybook/core/common'; + import { dedent } from 'ts-dedent'; import { csf4Transform } from './csf-3-to-4'; @@ -9,295 +11,299 @@ expect.addSnapshotSerializer({ test: () => true, }); -const transform = async (source: string) => - (await csf4Transform({ source, path: 'Component.stories.tsx' })).trim(); - describe('csf-3-to-4', () => { - describe('javascript', () => { - it('should wrap const declared meta', async () => { - await expect( - transform(dedent` - const meta = { title: 'Component' }; - export default meta; - `) - ).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; - - const meta = config.meta({ title: 'Component' }); - `); - }); - - it('should transform and wrap inline default exported meta', async () => { - await expect( - transform(dedent` - export default { title: 'Component' }; - `) - ).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; + describe('stories codemod', () => { + const transform = async (source: string) => + formatFileContent( + 'Component.stories.tsx', + await csf4Transform({ source, path: 'Component.stories.tsx' }) + ); + describe('javascript', () => { + it('should wrap const declared meta', async () => { + await expect( + transform(dedent` + const meta = { title: 'Component' }; + export default meta; + `) + ).resolves.toMatchInlineSnapshot(` + import { config } from '#.storybook/preview'; + + const meta = config.meta({ title: 'Component' }); + `); + }); - const meta = config.meta({ - title: 'Component', - }); - `); - }); + it('should transform and wrap inline default exported meta', async () => { + await expect( + transform(dedent` + export default { title: 'Component' }; + `) + ).resolves.toMatchInlineSnapshot(` + import { config } from '#.storybook/preview'; + + const meta = config.meta({ + title: 'Component', + }); + `); + }); - it('should rename meta object to meta if it has a different name', async () => { - await expect( - transform(dedent` - const componentMeta = { title: 'Component' }; - export default componentMeta; - `) - ).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; - - const meta = config.meta({ title: 'Component' }); - `); - }); + it('should rename meta object to meta if it has a different name', async () => { + await expect( + transform(dedent` + const componentMeta = { title: 'Component' }; + export default componentMeta; + `) + ).resolves.toMatchInlineSnapshot(` + import { config } from '#.storybook/preview'; + + const meta = config.meta({ title: 'Component' }); + `); + }); - it('should wrap stories in a meta.story method', async () => { - await expect( - transform(dedent` - const componentMeta = { title: 'Component' }; - export default componentMeta; - export const A = { + it('should wrap stories in a meta.story method', async () => { + await expect( + transform(dedent` + const componentMeta = { title: 'Component' }; + export default componentMeta; + export const A = { + args: { primary: true }, + render: (args) => + }; + `) + ).resolves.toMatchInlineSnapshot(` + import { config } from '#.storybook/preview'; + + const meta = config.meta({ title: 'Component' }); + export const A = meta.story({ args: { primary: true }, - render: (args) => - }; - `) - ).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; - - const meta = config.meta({ title: 'Component' }); - export const A = meta.story({ - args: { primary: true }, - render: (args) => , - }); - `); - }); + render: (args) => , + }); + `); + }); - it('should respect existing config imports', async () => { - await expect( - transform(dedent` - import { decorators } from "#.storybook/preview"; - const componentMeta = { title: 'Component' }; - export default componentMeta; - export const A = { + it('should respect existing config imports', async () => { + await expect( + transform(dedent` + import { decorators } from "#.storybook/preview"; + const componentMeta = { title: 'Component' }; + export default componentMeta; + export const A = { + args: { primary: true }, + render: (args) => + }; + `) + ).resolves.toMatchInlineSnapshot(` + import { config, decorators } from '#.storybook/preview'; + + const meta = config.meta({ title: 'Component' }); + export const A = meta.story({ args: { primary: true }, - render: (args) => - }; - `) - ).resolves.toMatchInlineSnapshot(` - import { config, decorators } from '#.storybook/preview'; - - const meta = config.meta({ title: 'Component' }); - export const A = meta.story({ - args: { primary: true }, - render: (args) => , - }); - `); - }); + render: (args) => , + }); + `); + }); - it('if there is an existing local constant called config, rename storybook config import', async () => { - await expect( - transform(dedent` - const componentMeta = { title: 'Component' }; - export default componentMeta; + it('if there is an existing local constant called config, rename storybook config import', async () => { + await expect( + transform(dedent` + const componentMeta = { title: 'Component' }; + export default componentMeta; + const config = {}; + export const A = { + args: { primary: true }, + render: (args) => + }; + `) + ).resolves.toMatchInlineSnapshot(` + import { config as storybookConfig } from '#.storybook/preview'; + + const meta = storybookConfig.meta({ title: 'Component' }); const config = {}; - export const A = { + export const A = meta.story({ args: { primary: true }, - render: (args) => - }; - `) - ).resolves.toMatchInlineSnapshot(` - import { config as storybookConfig } from '#.storybook/preview'; - - const meta = storybookConfig.meta({ title: 'Component' }); - const config = {}; - export const A = meta.story({ - args: { primary: true }, - render: (args) => , - }); - `); - }); + render: (args) => , + }); + `); + }); - it('converts CSF1 into CSF4 with render', async () => { - await expect( - transform(dedent` - const meta = { title: 'Component' }; - export default meta; - export const CSF1Story = () =>
Hello
; - `) - ).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; - - const meta = config.meta({ title: 'Component' }); - export const CSF1Story = meta.story({ - render: () =>
Hello
, - }); - `); + it('converts CSF1 into CSF4 with render', async () => { + await expect( + transform(dedent` + const meta = { title: 'Component' }; + export default meta; + export const CSF1Story = () =>
Hello
; + `) + ).resolves.toMatchInlineSnapshot(` + import { config } from '#.storybook/preview'; + + const meta = config.meta({ title: 'Component' }); + export const CSF1Story = meta.story({ + render: () =>
Hello
, + }); + `); + }); }); - }); - - describe('typescript', () => { - const inlineMetaSatisfies = dedent` - import { Meta, StoryObj as CSF3 } from '@storybook/react'; - import { ComponentProps } from './Component'; - - export default { title: 'Component', component: Component } satisfies Meta; - - export const A: CSF3 = { - args: { primary: true } - }; - `; - it('meta satisfies syntax', async () => { - await expect(transform(inlineMetaSatisfies)).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; + describe('typescript', () => { + const inlineMetaSatisfies = dedent` + import { Meta, StoryObj as CSF3 } from '@storybook/react'; import { ComponentProps } from './Component'; + + export default { title: 'Component', component: Component } satisfies Meta; + + export const A: CSF3 = { + args: { primary: true } + }; + `; + it('meta satisfies syntax', async () => { + await expect(transform(inlineMetaSatisfies)).resolves.toMatchInlineSnapshot(` + import { config } from '#.storybook/preview'; + + import { ComponentProps } from './Component'; + + const meta = config.meta({ title: 'Component', component: Component }); + + export const A = meta.story({ + args: { primary: true }, + }); + `); + }); - const meta = config.meta({ title: 'Component', component: Component }); - - export const A = meta.story({ - args: { primary: true }, - }); - `); - }); - - const inlineMetaAs = dedent` - import { Meta, StoryObj as CSF3 } from '@storybook/react'; - import { ComponentProps } from './Component'; - - export default { title: 'Component', component: Component } as Meta; - - export const A: CSF3 = { - args: { primary: true } - }; - `; - it('meta as syntax', async () => { - await expect(transform(inlineMetaAs)).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; - + const inlineMetaAs = dedent` + import { Meta, StoryObj as CSF3 } from '@storybook/react'; import { ComponentProps } from './Component'; - - const meta = config.meta({ title: 'Component', component: Component }); - - export const A = meta.story({ - args: { primary: true }, - }); - `); - }); - const metaSatisfies = dedent` - import { Meta, StoryObj as CSF3 } from '@storybook/react'; - import { ComponentProps } from './Component'; - - const meta = { title: 'Component', component: Component } satisfies Meta - export default meta; - - export const A: CSF3 = { - args: { primary: true } - }; - `; - it('meta satisfies syntax', async () => { - await expect(transform(metaSatisfies)).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; - + + export default { title: 'Component', component: Component } as Meta; + + export const A: CSF3 = { + args: { primary: true } + }; + `; + it('meta as syntax', async () => { + await expect(transform(inlineMetaAs)).resolves.toMatchInlineSnapshot(` + import { config } from '#.storybook/preview'; + + import { ComponentProps } from './Component'; + + const meta = config.meta({ title: 'Component', component: Component }); + + export const A = meta.story({ + args: { primary: true }, + }); + `); + }); + const metaSatisfies = dedent` + import { Meta, StoryObj as CSF3 } from '@storybook/react'; import { ComponentProps } from './Component'; + + const meta = { title: 'Component', component: Component } satisfies Meta + export default meta; + + export const A: CSF3 = { + args: { primary: true } + }; + `; + it('meta satisfies syntax', async () => { + await expect(transform(metaSatisfies)).resolves.toMatchInlineSnapshot(` + import { config } from '#.storybook/preview'; + + import { ComponentProps } from './Component'; + + const meta = config.meta({ title: 'Component', component: Component }); + + export const A = meta.story({ + args: { primary: true }, + }); + `); + }); - const meta = config.meta({ title: 'Component', component: Component }); - - export const A = meta.story({ - args: { primary: true }, - }); - `); - }); - - const metaAs = dedent` - import { Meta, StoryObj as CSF3 } from '@storybook/react'; - import { ComponentProps } from './Component'; - - const meta = { title: 'Component', component: Component } as Meta - export default meta; - - export const A: CSF3 = { - args: { primary: true } - }; - `; - it('meta as syntax', async () => { - await expect(transform(metaAs)).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; - + const metaAs = dedent` + import { Meta, StoryObj as CSF3 } from '@storybook/react'; import { ComponentProps } from './Component'; + + const meta = { title: 'Component', component: Component } as Meta + export default meta; + + export const A: CSF3 = { + args: { primary: true } + }; + `; + it('meta as syntax', async () => { + await expect(transform(metaAs)).resolves.toMatchInlineSnapshot(` + import { config } from '#.storybook/preview'; + + import { ComponentProps } from './Component'; + + const meta = config.meta({ title: 'Component', component: Component }); + + export const A = meta.story({ + args: { primary: true }, + }); + `); + }); - const meta = config.meta({ title: 'Component', component: Component }); - - export const A = meta.story({ - args: { primary: true }, - }); - `); - }); - - const storySatisfies = dedent` - import { Meta, StoryObj as CSF3 } from '@storybook/react'; - import { ComponentProps } from './Component'; - - const meta = { title: 'Component', component: Component } as Meta - export default meta; - - export const A = { - args: { primary: true } - } satisfies CSF3; - `; - it('story satisfies syntax', async () => { - await expect(transform(storySatisfies)).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; - + const storySatisfies = dedent` + import { Meta, StoryObj as CSF3 } from '@storybook/react'; import { ComponentProps } from './Component'; + + const meta = { title: 'Component', component: Component } as Meta + export default meta; + + export const A = { + args: { primary: true } + } satisfies CSF3; + `; + it('story satisfies syntax', async () => { + await expect(transform(storySatisfies)).resolves.toMatchInlineSnapshot(` + import { config } from '#.storybook/preview'; + + import { ComponentProps } from './Component'; + + const meta = config.meta({ title: 'Component', component: Component }); + + export const A = meta.story({ + args: { primary: true }, + }); + `); + }); - const meta = config.meta({ title: 'Component', component: Component }); - - export const A = meta.story({ - args: { primary: true }, - }); - `); - }); - - const storyAs = dedent` - import { Meta, StoryObj as CSF3 } from '@storybook/react'; - import { ComponentProps } from './Component'; - - const meta = { title: 'Component', component: Component } as Meta - export default meta; - - export const A = { - args: { primary: true } - } as CSF3; - `; - it('story as syntax', async () => { - await expect(transform(storyAs)).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; - + const storyAs = dedent` + import { Meta, StoryObj as CSF3 } from '@storybook/react'; import { ComponentProps } from './Component'; + + const meta = { title: 'Component', component: Component } as Meta + export default meta; + + export const A = { + args: { primary: true } + } as CSF3; + `; + it('story as syntax', async () => { + await expect(transform(storyAs)).resolves.toMatchInlineSnapshot(` + import { config } from '#.storybook/preview'; + + import { ComponentProps } from './Component'; + + const meta = config.meta({ title: 'Component', component: Component }); + + export const A = meta.story({ + args: { primary: true }, + }); + `); + }); - const meta = config.meta({ title: 'Component', component: Component }); - - export const A = meta.story({ - args: { primary: true }, + it('should yield the same result to all syntaxes', async () => { + const allSnippets = await Promise.all([ + transform(inlineMetaSatisfies), + transform(inlineMetaAs), + transform(metaSatisfies), + transform(metaAs), + transform(storySatisfies), + transform(storyAs), + ]); + + allSnippets.forEach((result) => { + expect(result).toEqual(allSnippets[0]); }); - `); - }); - - it('should yield the same result to all syntaxes', async () => { - const allSnippets = await Promise.all([ - transform(inlineMetaSatisfies), - transform(inlineMetaAs), - transform(metaSatisfies), - transform(metaAs), - transform(storySatisfies), - transform(storyAs), - ]); - - allSnippets.forEach((result) => { - expect(result).toEqual(allSnippets[0]); }); }); }); diff --git a/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.ts b/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.ts index 305d60778dc9..8121c332224c 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.ts @@ -1,6 +1,5 @@ /* eslint-disable no-underscore-dangle */ import { types as t } from 'storybook/internal/babel'; -import { formatFileContent } from 'storybook/internal/common'; import { isValidPreviewPath, loadCsf, printCsf } from 'storybook/internal/csf-tools'; import prompts from 'prompts'; @@ -202,18 +201,37 @@ export async function csf4Transform(info: FileInfo) { const logger = console; +async function runStoriesCodemod(dryRun: boolean | undefined) { + try { + let globString = '**/*.stories.*'; + if (!process.env.IN_STORYBOOK_SANDBOX) { + logger.log('Please enter the glob for your stories to migrate'); + globString = ( + await prompts({ + type: 'text', + name: 'glob', + message: 'glob', + initial: globString, + }) + ).glob; + } + logger.log('Applying codemod on your stories...'); + await runCodemod(globString, csf4Transform, { dryRun }); + } catch (err: any) { + console.log('err message', err.message); + if (err.message === 'No files matched') { + console.log('going to run again'); + await runStoriesCodemod(dryRun); + } else { + throw err; + } + } +} + export const csf3to4: CommandFix = { id: 'csf-3-to-4', promptType: 'command', async run({ dryRun }) { - logger.log('Please enter the glob for your stories to migrate'); - const { glob: globString } = await prompts({ - type: 'text', - name: 'glob', - message: 'glob', - initial: '**/*.stories.*', - }); - - await runCodemod(globString, csf4Transform, { dryRun }); + await runStoriesCodemod(dryRun); }, }; diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 4055d84b7bc7..fe86aeeadd00 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -882,10 +882,9 @@ export const runMigrations: Task['run'] = async ({ sandboxDir, template }, { dry template.expected.framework === '@storybook/react-vite' && !template.skipTasks.includes('vitest-integration') ) { - await executeCLIStep(steps.migrate, { + await executeCLIStep(steps.automigrate, { cwd: sandboxDir, argument: 'csf-3-to-4', - optionValues: { glob: 'src/stories/*.stories.*' }, dryRun, debug, }); diff --git a/scripts/utils/cli-step.ts b/scripts/utils/cli-step.ts index b685eb7b16ee..1432f9b264c7 100644 --- a/scripts/utils/cli-step.ts +++ b/scripts/utils/cli-step.ts @@ -82,6 +82,13 @@ export const steps = { glob: { type: 'string' }, }), }, + automigrate: { + command: 'automigrate', + hasArgument: true, + description: 'Run automigrations', + icon: '🤖', + options: createOptions({}), + }, }; export async function executeCLIStep( From b0972bc374bc85086ed3e2c37cdb033660e1fe67 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 17 Jan 2025 11:07:45 +0100 Subject: [PATCH 063/144] remove entrypoint --- code/lib/codemod/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/lib/codemod/package.json b/code/lib/codemod/package.json index db7317edca1f..81ec7b4b59ee 100644 --- a/code/lib/codemod/package.json +++ b/code/lib/codemod/package.json @@ -28,7 +28,6 @@ }, "./dist/transforms/add-component-parameters.js": "./dist/transforms/add-component-parameters.js", "./dist/transforms/csf-2-to-3.js": "./dist/transforms/csf-2-to-3.js", - "./dist/transforms/csf-3-to-4.js": "./dist/transforms/csf-3-to-4.js", "./dist/transforms/csf-hoist-story-annotations.js": "./dist/transforms/csf-hoist-story-annotations.js", "./dist/transforms/find-implicit-spies.js": "./dist/transforms/find-implicit-spies.js", "./dist/transforms/move-builtin-addons.js": "./dist/transforms/move-builtin-addons.js", @@ -94,7 +93,6 @@ "./src/transforms/storiesof-to-csf.js", "./src/transforms/mdx-to-csf.ts", "./src/transforms/csf-2-to-3.ts", - "./src/transforms/csf-3-to-4.ts", "./src/transforms/csf-hoist-story-annotations.js", "./src/transforms/find-implicit-spies.ts", "./src/transforms/add-component-parameters.js", From 325a554fd790ebd048b2ca547ac290837a54e024 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 17 Jan 2025 11:47:29 +0100 Subject: [PATCH 064/144] only apply codemod to starter stories in sandbox, rename to csf-factories --- .../{csf-3-to-4.test.ts => csf-factories.test.ts} | 6 +++--- .../fixes/{csf-3-to-4.ts => csf-factories.ts} | 10 +++++----- code/lib/cli-storybook/src/automigrate/fixes/index.ts | 6 +++--- scripts/tasks/sandbox-parts.ts | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) rename code/lib/cli-storybook/src/automigrate/fixes/{csf-3-to-4.test.ts => csf-factories.test.ts} (98%) rename code/lib/cli-storybook/src/automigrate/fixes/{csf-3-to-4.ts => csf-factories.ts} (96%) diff --git a/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.test.ts similarity index 98% rename from code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.test.ts rename to code/lib/cli-storybook/src/automigrate/fixes/csf-factories.test.ts index 6bb4b438a479..8584580b1b4d 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.test.ts @@ -4,19 +4,19 @@ import { formatFileContent } from '@storybook/core/common'; import { dedent } from 'ts-dedent'; -import { csf4Transform } from './csf-3-to-4'; +import { storyToCsfFactory } from './csf-factories'; expect.addSnapshotSerializer({ serialize: (val: any) => (typeof val === 'string' ? val : val.toString()), test: () => true, }); -describe('csf-3-to-4', () => { +describe('csf-factories', () => { describe('stories codemod', () => { const transform = async (source: string) => formatFileContent( 'Component.stories.tsx', - await csf4Transform({ source, path: 'Component.stories.tsx' }) + await storyToCsfFactory({ source, path: 'Component.stories.tsx' }) ); describe('javascript', () => { it('should wrap const declared meta', async () => { diff --git a/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.ts b/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.ts similarity index 96% rename from code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.ts rename to code/lib/cli-storybook/src/automigrate/fixes/csf-factories.ts index 8121c332224c..fbf03531ad6c 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/csf-3-to-4.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.ts @@ -8,7 +8,7 @@ import type { FileInfo } from '../codemod'; import { runCodemod } from '../codemod'; import type { CommandFix } from '../types'; -export async function csf4Transform(info: FileInfo) { +export async function storyToCsfFactory(info: FileInfo) { const csf = loadCsf(info.source, { makeTitle: () => 'FIXME' }); try { csf.parse(); @@ -203,7 +203,7 @@ const logger = console; async function runStoriesCodemod(dryRun: boolean | undefined) { try { - let globString = '**/*.stories.*'; + let globString = 'src/stories/*.stories.*'; if (!process.env.IN_STORYBOOK_SANDBOX) { logger.log('Please enter the glob for your stories to migrate'); globString = ( @@ -216,7 +216,7 @@ async function runStoriesCodemod(dryRun: boolean | undefined) { ).glob; } logger.log('Applying codemod on your stories...'); - await runCodemod(globString, csf4Transform, { dryRun }); + await runCodemod(globString, storyToCsfFactory, { dryRun }); } catch (err: any) { console.log('err message', err.message); if (err.message === 'No files matched') { @@ -228,8 +228,8 @@ async function runStoriesCodemod(dryRun: boolean | undefined) { } } -export const csf3to4: CommandFix = { - id: 'csf-3-to-4', +export const csfFactories: CommandFix = { + id: 'csf-factories', promptType: 'command', async run({ dryRun }) { await runStoriesCodemod(dryRun); diff --git a/code/lib/cli-storybook/src/automigrate/fixes/index.ts b/code/lib/cli-storybook/src/automigrate/fixes/index.ts index acd7d06183d9..2bf67cc44704 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/index.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/index.ts @@ -8,7 +8,7 @@ import { autodocsTags } from './autodocs-tags'; import { autodocsTrue } from './autodocs-true'; import { builderVite } from './builder-vite'; import { cra5 } from './cra5'; -import { csf3to4 } from './csf-3-to-4'; +import { csfFactories } from './csf-factories'; import { eslintPlugin } from './eslint-plugin'; import { initialGlobals } from './initial-globals'; import { mdx1to3 } from './mdx-1-to-3'; @@ -73,5 +73,5 @@ export const allFixes: Fix[] = [ export const initFixes: Fix[] = [eslintPlugin]; // These are specific fixes that only occur when triggered on command, and are hidden otherwise. -// e.g. npx automigrate csf-3-to-4 -export const commandFixes: CommandFix[] = [csf3to4]; +// e.g. npx storybook automigrate csf-factories +export const commandFixes: CommandFix[] = [csfFactories]; diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index fe86aeeadd00..2a86db4a73c3 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -884,7 +884,7 @@ export const runMigrations: Task['run'] = async ({ sandboxDir, template }, { dry ) { await executeCLIStep(steps.automigrate, { cwd: sandboxDir, - argument: 'csf-3-to-4', + argument: 'csf-factories', dryRun, debug, }); From 9df03e6a07e9d4bb45ed2c2d4cd18af632f2a393 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 17 Jan 2025 13:40:40 +0100 Subject: [PATCH 065/144] fix angular and ember build --- code/frameworks/angular/package.json | 1 + code/frameworks/ember/package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/code/frameworks/angular/package.json b/code/frameworks/angular/package.json index 91a2d3da4c11..2c71784ede70 100644 --- a/code/frameworks/angular/package.json +++ b/code/frameworks/angular/package.json @@ -27,6 +27,7 @@ "import": "./dist/index.mjs", "require": "./dist/index.js" }, + "./preset": "./preset.js", "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", diff --git a/code/frameworks/ember/package.json b/code/frameworks/ember/package.json index e9f712956e5c..94b6468b39f8 100644 --- a/code/frameworks/ember/package.json +++ b/code/frameworks/ember/package.json @@ -23,6 +23,7 @@ "import": "./dist/index.mjs", "require": "./dist/index.js" }, + "./preset": "./preset.js", "./node": { "types": "./dist/csf-factory/defineMainConfig.d.ts", "node": "./dist/csf-factory/defineMainConfig.js", From d4e369539c0bc49dc004070c97f84624343e3e82 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 17 Jan 2025 18:25:51 +0100 Subject: [PATCH 066/144] ConfigFile: Support pnp wrapped names --- code/core/src/csf-tools/ConfigFile.test.ts | 13 +++++++++++++ code/core/src/csf-tools/ConfigFile.ts | 2 ++ 2 files changed, 15 insertions(+) diff --git a/code/core/src/csf-tools/ConfigFile.test.ts b/code/core/src/csf-tools/ConfigFile.test.ts index 18b2365f25ec..fc250ada5786 100644 --- a/code/core/src/csf-tools/ConfigFile.test.ts +++ b/code/core/src/csf-tools/ConfigFile.test.ts @@ -1017,6 +1017,19 @@ describe('ConfigFile', () => { expect(config.getNameFromPath(['otherField'])).toEqual('foo'); }); + it(`supports pnp wrapped names`, () => { + const source = dedent` + import type { StorybookConfig } from '@storybook/react-webpack5'; + + const config: StorybookConfig = { + framework: getAbsolutePath('foo'), + } + export default config; + `; + const config = loadConfig(source).parse(); + expect(config.getNameFromPath(['framework'])).toEqual('foo'); + }); + it(`returns undefined when accessing a field that does not exist`, () => { const source = dedent` import type { StorybookConfig } from '@storybook/react-webpack5'; diff --git a/code/core/src/csf-tools/ConfigFile.ts b/code/core/src/csf-tools/ConfigFile.ts index 60bea05b56dd..12815504a06b 100644 --- a/code/core/src/csf-tools/ConfigFile.ts +++ b/code/core/src/csf-tools/ConfigFile.ts @@ -499,6 +499,8 @@ export class ConfigFile { value = prop.value.value; } }); + } else if (t.isCallExpression(node)) { + value = this._getPnpWrappedValue(node); } if (!value) { From 664819839cf6e12b354a8e5868f005cce5dfc0aa Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 17 Jan 2025 19:01:51 +0100 Subject: [PATCH 067/144] add codemod for main/preview in factory format --- .../automigrate/fixes/csf-factories.test.ts | 126 ++++++++++- .../src/automigrate/fixes/csf-factories.ts | 199 +++++++++++++++++- .../src/automigrate/helpers/mainConfigFile.ts | 1 + .../cli-storybook/src/automigrate/index.ts | 47 +++-- .../cli-storybook/src/automigrate/types.ts | 6 +- 5 files changed, 357 insertions(+), 22 deletions(-) diff --git a/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.test.ts index 8584580b1b4d..880b1b74b704 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.test.ts @@ -4,7 +4,7 @@ import { formatFileContent } from '@storybook/core/common'; import { dedent } from 'ts-dedent'; -import { storyToCsfFactory } from './csf-factories'; +import { configToCsfFactory, storyToCsfFactory } from './csf-factories'; expect.addSnapshotSerializer({ serialize: (val: any) => (typeof val === 'string' ? val : val.toString()), @@ -12,6 +12,130 @@ expect.addSnapshotSerializer({ }); describe('csf-factories', () => { + describe('main/preview codemod: general parsing functionality', () => { + const transform = async (source: string) => + ( + await configToCsfFactory( + { source, path: 'main.ts' }, + { configType: 'main', frameworkPackage: '@storybook/react-vite' } + ) + ).trim(); + + it('should wrap defineMain call from inline default export', async () => { + await expect( + transform(dedent` + export default { + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: ['@storybook/addon-essentials'], + framework: '@storybook/react-vite', + }; + `) + ).resolves.toMatchInlineSnapshot(` + import { defineMain } from '@storybook/react-vite/node'; + + export default defineMain({ + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: ['@storybook/addon-essentials'], + framework: '@storybook/react-vite', + }); + `); + }); + it('should wrap defineMain call from const declared default export', async () => { + await expect( + transform(dedent` + const config = { + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: ['@storybook/addon-essentials'], + framework: '@storybook/react-vite', + }; + + export default config; + `) + ).resolves.toMatchInlineSnapshot(` + import { defineMain } from '@storybook/react-vite/node'; + + export default defineMain({ + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: ['@storybook/addon-essentials'], + framework: '@storybook/react-vite', + }); + `); + }); + it('should wrap defineMain call from const declared default export and default export mix', async () => { + await expect( + transform(dedent` + export const tags = []; + const config = { + framework: '@storybook/react-vite', + }; + + export default config; + `) + ).resolves.toMatchInlineSnapshot(` + import { defineMain } from '@storybook/react-vite/node'; + + const config = { + framework: '@storybook/react-vite', + tags: [], + }; + + export default config; + `); + }); + it('should wrap defineMain call from named exports format', async () => { + await expect( + transform(dedent` + export const stories = ['../src/**/*.stories.@(js|jsx|ts|tsx)']; + export const addons = ['@storybook/addon-essentials']; + export const framework = '@storybook/react-vite'; + `) + ).resolves.toMatchInlineSnapshot(` + import { defineMain } from '@storybook/react-vite/node'; + + export default defineMain({ + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: ['@storybook/addon-essentials'], + framework: '@storybook/react-vite', + }); + `); + }); + it('should not add additional imports if there is already one', async () => { + const transformed = await transform(dedent` + import { defineMain } from '@storybook/react-vite/node'; + const config = {}; + + export default config; + `); + expect( + transformed.match(/import { defineMain } from '@storybook\/react-vite\/node'/g) + ).toHaveLength(1); + }); + }); + describe('preview specific functionality', () => { + const transform = async (source: string) => + ( + await configToCsfFactory( + { source, path: 'preview.ts' }, + { configType: 'preview', frameworkPackage: '@storybook/react-vite' } + ) + ).trim(); + + it('should contain a named config export', async () => { + await expect( + transform(dedent` + export default { + tags: ['test'], + }; + `) + ).resolves.toMatchInlineSnapshot(` + import { definePreview } from '@storybook/react-vite/browser'; + + export default definePreview({ + tags: ['test'], + }); + `); + }); + }); describe('stories codemod', () => { const transform = async (source: string) => formatFileContent( diff --git a/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.ts b/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.ts index fbf03531ad6c..1a946d1b41fe 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.ts @@ -1,13 +1,190 @@ /* eslint-disable no-underscore-dangle */ import { types as t } from 'storybook/internal/babel'; -import { isValidPreviewPath, loadCsf, printCsf } from 'storybook/internal/csf-tools'; - +import { formatFileContent } from 'storybook/internal/common'; +import { + isValidPreviewPath, + loadConfig, + loadCsf, + printConfig, + printCsf, +} from 'storybook/internal/csf-tools'; + +import picocolors from 'picocolors'; import prompts from 'prompts'; import type { FileInfo } from '../codemod'; import { runCodemod } from '../codemod'; +import { detectRenderer } from '../helpers/detectRenderer'; +import { + getFrameworkPackageName, + getRendererPackageNameFromFramework, +} from '../helpers/mainConfigFile'; import type { CommandFix } from '../types'; +export async function configToCsfFactory( + info: FileInfo, + { configType, frameworkPackage }: { configType?: 'main' | 'preview'; frameworkPackage: string }, + { dryRun = false, skipFormatting = false }: { dryRun?: boolean; skipFormatting?: boolean } = {} +) { + const config = loadConfig(info.source); + try { + config.parse(); + } catch (err) { + logger.log(`Error when parsing ${info.path}, skipping:\n${err}`); + return info.source; + } + + configType = configType || (info.path.includes('preview') ? 'preview' : 'main'); + const methodName = configType === 'main' ? 'defineMain' : 'definePreview'; + + const programNode = config._ast.program; + const hasNamedExports = Object.keys(config._exportDecls).length > 0; + + /** + * Scenario 1: Mixed exports + * + * ``` + * export const tags = []; + * export default { + * parameters: {}, + * }; + * ``` + * + * Transform into: `export default defineMain({ tags: [], parameters: {} })` + */ + if (config._exportsObject && hasNamedExports) { + const exportDecls = config._exportDecls; + + for (const [name, decl] of Object.entries(exportDecls)) { + if (decl.init) { + config._exportsObject.properties.push(t.objectProperty(t.identifier(name), decl.init)); + } + } + + programNode.body = programNode.body.filter((node) => { + if (t.isExportNamedDeclaration(node) && node.declaration) { + if (t.isVariableDeclaration(node.declaration)) { + node.declaration.declarations = node.declaration.declarations.filter( + (decl) => t.isIdentifier(decl.id) && !exportDecls[decl.id.name] + ); + return node.declaration.declarations.length > 0; + } + } + return true; + }); + } else if (config._exportsObject) { + /** + * Scenario 2: Default exports + * + * - Syntax 1: `default export const config = {}; export default config;` + * - Syntax 2: `export default {};` + * + * Transform into: `export default defineMain({})` + */ + const defineConfigCall = t.callExpression(t.identifier(methodName), [config._exportsObject]); + + let exportDefaultNode = null as any as t.ExportDefaultDeclaration; + let declarationNodeIndex = -1; + + programNode.body.forEach((node) => { + // Detect Syntax 1 + if (t.isExportDefaultDeclaration(node) && t.isIdentifier(node.declaration)) { + const declarationName = node.declaration.name; + + declarationNodeIndex = programNode.body.findIndex( + (n) => + t.isVariableDeclaration(n) && + n.declarations.some( + (d) => + t.isIdentifier(d.id) && + d.id.name === declarationName && + t.isObjectExpression(d.init) + ) + ); + + if (declarationNodeIndex !== -1) { + exportDefaultNode = node; + // remove the original declaration as it will become a default export + const declarationNode = programNode.body[declarationNodeIndex]; + if (t.isVariableDeclaration(declarationNode)) { + const id = declarationNode.declarations[0].id; + const variableName = t.isIdentifier(id) && id.name; + + if (variableName) { + programNode.body.splice(declarationNodeIndex, 1); + } + } + } + } else if (t.isExportDefaultDeclaration(node) && t.isObjectExpression(node.declaration)) { + // Detect Syntax 2 + exportDefaultNode = node; + } + }); + + if (exportDefaultNode !== null) { + exportDefaultNode.declaration = defineConfigCall; + } + } else if (hasNamedExports) { + /** + * Scenario 3: Named exports export const foo = {}; export bar = ''; + * + * Transform into: export default defineMain({ foo: {}, bar: '' }); + */ + const exportDecls = config._exportDecls; + const defineConfigProps = []; + + // Collect properties from named exports + for (const [name, decl] of Object.entries(exportDecls)) { + if (decl.init) { + defineConfigProps.push(t.objectProperty(t.identifier(name), decl.init)); + } + } + + // Construct the `define` call + const defineConfigCall = t.callExpression(t.identifier(methodName), [ + t.objectExpression(defineConfigProps), + ]); + + // Remove all related named exports + programNode.body = programNode.body.filter((node) => { + if (t.isExportNamedDeclaration(node) && node.declaration) { + if (t.isVariableDeclaration(node.declaration)) { + node.declaration.declarations = node.declaration.declarations.filter( + (decl) => t.isIdentifier(decl.id) && !exportDecls[decl.id.name] + ); + return node.declaration.declarations.length > 0; + } + } + return true; + }); + + // Add the new export default declaration + programNode.body.push(t.exportDefaultDeclaration(defineConfigCall)); + } + + const configImport = t.importDeclaration( + [t.importSpecifier(t.identifier(methodName), t.identifier(methodName))], + t.stringLiteral(frameworkPackage + `/${configType === 'main' ? 'node' : 'browser'}`) + ); + // only add the import if it doesn't exist yet + if ( + !programNode.body.some( + (node) => t.isImportDeclaration(node) && node.source.value === configImport.source.value + ) + ) { + programNode.body.unshift(configImport); + } + + const output = printConfig(config).code; + + if (dryRun) { + logger.log(`Would write to ${picocolors.yellow(info.path)}:\n${picocolors.green(output)}`); + return info.source; + } + + return skipFormatting ? output : formatFileContent(info.path, output); +} + export async function storyToCsfFactory(info: FileInfo) { const csf = loadCsf(info.source, { makeTitle: () => 'FIXME' }); try { @@ -231,7 +408,23 @@ async function runStoriesCodemod(dryRun: boolean | undefined) { export const csfFactories: CommandFix = { id: 'csf-factories', promptType: 'command', - async run({ dryRun }) { + async run({ dryRun, mainConfig, mainConfigPath, previewConfigPath, packageJson }) { + const frameworkPackageName = getFrameworkPackageName(mainConfig); + + const rendererPackageName = + (await getRendererPackageNameFromFramework(frameworkPackageName as string)) ?? + (await detectRenderer(packageJson)); + + console.log({ rendererPackageName, frameworkPackageName }); + await runStoriesCodemod(dryRun); + logger.log('Applying codemod on your main config...'); + await runCodemod(mainConfigPath, (fileInfo) => + configToCsfFactory(fileInfo, 'main', { dryRun }) + ); + logger.log('Applying codemod on your preview config...'); + await runCodemod(previewConfigPath, (fileInfo) => + configToCsfFactory(fileInfo, 'preview', { dryRun }) + ); }, }; diff --git a/code/lib/cli-storybook/src/automigrate/helpers/mainConfigFile.ts b/code/lib/cli-storybook/src/automigrate/helpers/mainConfigFile.ts index 57aa4bf7ce07..717f92b646b3 100644 --- a/code/lib/cli-storybook/src/automigrate/helpers/mainConfigFile.ts +++ b/code/lib/cli-storybook/src/automigrate/helpers/mainConfigFile.ts @@ -157,6 +157,7 @@ export const getStorybookData = async ({ storybookVersion, mainConfigPath, previewConfigPath, + packageJson, }; }; export type GetStorybookData = typeof getStorybookData; diff --git a/code/lib/cli-storybook/src/automigrate/index.ts b/code/lib/cli-storybook/src/automigrate/index.ts index a25455e38b1a..4dd8e7445895 100644 --- a/code/lib/cli-storybook/src/automigrate/index.ts +++ b/code/lib/cli-storybook/src/automigrate/index.ts @@ -2,13 +2,13 @@ import { createWriteStream } from 'node:fs'; import { rename, rm } from 'node:fs/promises'; import { join } from 'node:path'; +import type { PackageJson } from 'storybook/internal/common'; import { type JsPackageManager, JsPackageManagerFactory, - getCoercedStorybookVersion, - getStorybookInfo, temporaryFile, } from 'storybook/internal/common'; +import type { StorybookConfigRaw } from 'storybook/internal/types'; import boxen from 'boxen'; import picocolors from 'picocolors'; @@ -77,17 +77,17 @@ export const doAutomigrate = async (options: AutofixOptionsFromCLI) => { force: options.packageManager, }); - const [packageJson, storybookVersion] = await Promise.all([ - packageManager.retrievePackageJson(), - getCoercedStorybookVersion(packageManager), - ]); - const { - configDir: inferredConfigDir, - mainConfig: mainConfigPath, - previewConfig: previewConfigPath, - } = getStorybookInfo(packageJson, options.configDir); - const configDir = options.configDir || inferredConfigDir || '.storybook'; + mainConfig, + mainConfigPath, + previewConfigPath, + storybookVersion, + configDir, + packageJson, + } = await getStorybookData({ + configDir: options.configDir, + packageManager, + }); if (!storybookVersion) { throw new Error('Could not determine Storybook version'); @@ -99,10 +99,12 @@ export const doAutomigrate = async (options: AutofixOptionsFromCLI) => { const outcome = await automigrate({ ...options, + packageJson, packageManager, storybookVersion, beforeVersion: storybookVersion, mainConfigPath, + mainConfig, previewConfigPath, configDir, isUpgrade: false, @@ -120,8 +122,10 @@ export const automigrate = async ({ dryRun, yes, packageManager, + packageJson, list, configDir, + mainConfig, mainConfigPath, previewConfigPath, storybookVersion, @@ -149,7 +153,9 @@ export const automigrate = async ({ mainConfigPath, previewConfigPath, packageManager, + packageJson, dryRun, + mainConfig, result: null, }); @@ -185,9 +191,12 @@ export const automigrate = async ({ const { fixResults, fixSummary, preCheckFailure } = await runFixes({ fixes, packageManager, + packageJson, rendererPackage, skipInstall, configDir, + previewConfigPath, + mainConfig, mainConfigPath, storybookVersion, beforeVersion, @@ -233,7 +242,10 @@ export async function runFixes({ skipInstall, configDir, packageManager, + packageJson, + mainConfig, mainConfigPath, + previewConfigPath, storybookVersion, beforeVersion, isUpgrade, @@ -245,7 +257,10 @@ export async function runFixes({ skipInstall?: boolean; configDir: string; packageManager: JsPackageManager; + packageJson: PackageJson; mainConfigPath: string; + previewConfigPath?: string; + mainConfig: StorybookConfigRaw; storybookVersion: string; beforeVersion: string; isUpgrade?: boolean; @@ -262,11 +277,6 @@ export async function runFixes({ let result; try { - const { mainConfig, previewConfigPath } = await getStorybookData({ - configDir, - packageManager, - }); - if ( (isUpgrade && semver.satisfies(beforeVersion, f.versionRange[0], { includePrerelease: true }) && @@ -402,6 +412,9 @@ export async function runFixes({ packageManager, dryRun, mainConfigPath, + previewConfigPath, + packageJson, + mainConfig, skipInstall, }); logger.info(`✅ ran ${picocolors.cyan(f.id)} migration`); diff --git a/code/lib/cli-storybook/src/automigrate/types.ts b/code/lib/cli-storybook/src/automigrate/types.ts index 3c62e6343cab..f4eb22d9e740 100644 --- a/code/lib/cli-storybook/src/automigrate/types.ts +++ b/code/lib/cli-storybook/src/automigrate/types.ts @@ -1,4 +1,4 @@ -import type { JsPackageManager, PackageManagerName } from 'storybook/internal/common'; +import type { JsPackageManager, PackageJson, PackageManagerName } from 'storybook/internal/common'; import type { StorybookConfigRaw } from 'storybook/internal/types'; export interface CheckOptions { @@ -13,10 +13,12 @@ export interface CheckOptions { export interface RunOptions { packageManager: JsPackageManager; + packageJson: PackageJson; result: ResultType; dryRun?: boolean; mainConfigPath: string; previewConfigPath?: string; + mainConfig: StorybookConfigRaw; skipInstall?: boolean; } @@ -73,8 +75,10 @@ export enum PreCheckFailure { export interface AutofixOptions extends Omit { packageManager: JsPackageManager; + packageJson: PackageJson; mainConfigPath: string; previewConfigPath?: string; + mainConfig: StorybookConfigRaw; /** The version of Storybook before the migration. */ beforeVersion: string; storybookVersion: string; From 211fb4b76efcbc52f50147dba64dc6d68e541802 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 20 Jan 2025 13:03:51 +0100 Subject: [PATCH 068/144] add storybook config + addon sync codemod --- code/core/src/csf-tools/ConfigFile.test.ts | 12 + code/core/src/csf-tools/ConfigFile.ts | 15 +- .../src/automigrate/fixes/csf-factories.ts | 430 ------------------ .../src/automigrate/fixes/index.ts | 2 +- .../src/codemod/csf-factories.ts | 75 +++ .../helpers/config-to-csf-factory.test.ts | 174 +++++++ .../codemod/helpers/config-to-csf-factory.ts | 177 +++++++ .../helpers/csf-factories-utils.test.ts | 69 +++ .../codemod/helpers/csf-factories-utils.ts | 101 ++++ .../helpers/get-addon-annotations.test.ts | 25 + .../codemod/helpers/get-addon-annotations.ts | 41 ++ .../helpers/story-to-csf-factory.test.ts} | 282 ++++-------- .../codemod/helpers/story-to-csf-factory.ts | 171 +++++++ scripts/tasks/sandbox-parts.ts | 45 +- scripts/tasks/sandbox.ts | 5 +- 15 files changed, 934 insertions(+), 690 deletions(-) delete mode 100644 code/lib/cli-storybook/src/automigrate/fixes/csf-factories.ts create mode 100644 code/lib/cli-storybook/src/codemod/csf-factories.ts create mode 100644 code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts create mode 100644 code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts create mode 100644 code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts create mode 100644 code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts create mode 100644 code/lib/cli-storybook/src/codemod/helpers/get-addon-annotations.test.ts create mode 100644 code/lib/cli-storybook/src/codemod/helpers/get-addon-annotations.ts rename code/lib/cli-storybook/src/{automigrate/fixes/csf-factories.test.ts => codemod/helpers/story-to-csf-factory.test.ts} (53%) create mode 100644 code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts diff --git a/code/core/src/csf-tools/ConfigFile.test.ts b/code/core/src/csf-tools/ConfigFile.test.ts index fc250ada5786..b4c3895538f8 100644 --- a/code/core/src/csf-tools/ConfigFile.test.ts +++ b/code/core/src/csf-tools/ConfigFile.test.ts @@ -245,6 +245,18 @@ describe('ConfigFile', () => { }); describe('factory config', () => { + it('parses correctly', () => { + const source = dedent` + import { defineConfig } from '@storybook/react-vite/browser'; + + const config = defineConfig({ + framework: 'foo', + }); + export default config; + `; + const config = loadConfig(source).parse(); + expect(config.getNameFromPath(['framework'])).toEqual('foo'); + }); it('found scalar', () => { expect( getField( diff --git a/code/core/src/csf-tools/ConfigFile.ts b/code/core/src/csf-tools/ConfigFile.ts index 12815504a06b..7ea42448dfc6 100644 --- a/code/core/src/csf-tools/ConfigFile.ts +++ b/code/core/src/csf-tools/ConfigFile.ts @@ -25,18 +25,8 @@ const getCsfParsingErrorMessage = ({ foundType: string | undefined; node: any | undefined; }) => { - let nodeInfo = ''; - if (node) { - try { - nodeInfo = JSON.stringify(node); - } catch (e) { - // - } - } - return dedent` CSF Parsing error: Expected '${expectedType}' but found '${foundType}' instead in '${node?.type}'. - ${nodeInfo} `; }; @@ -215,6 +205,11 @@ export class ConfigFile { decl = unwrap(decl); + // csf factory + if (t.isCallExpression(decl) && t.isObjectExpression(decl.arguments[0])) { + decl = decl.arguments[0]; + } + if (t.isObjectExpression(decl)) { self._parseExportsObject(decl); } else { diff --git a/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.ts b/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.ts deleted file mode 100644 index 1a946d1b41fe..000000000000 --- a/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.ts +++ /dev/null @@ -1,430 +0,0 @@ -/* eslint-disable no-underscore-dangle */ -import { types as t } from 'storybook/internal/babel'; -import { formatFileContent } from 'storybook/internal/common'; -import { - isValidPreviewPath, - loadConfig, - loadCsf, - printConfig, - printCsf, -} from 'storybook/internal/csf-tools'; - -import picocolors from 'picocolors'; -import prompts from 'prompts'; - -import type { FileInfo } from '../codemod'; -import { runCodemod } from '../codemod'; -import { detectRenderer } from '../helpers/detectRenderer'; -import { - getFrameworkPackageName, - getRendererPackageNameFromFramework, -} from '../helpers/mainConfigFile'; -import type { CommandFix } from '../types'; - -export async function configToCsfFactory( - info: FileInfo, - { configType, frameworkPackage }: { configType?: 'main' | 'preview'; frameworkPackage: string }, - { dryRun = false, skipFormatting = false }: { dryRun?: boolean; skipFormatting?: boolean } = {} -) { - const config = loadConfig(info.source); - try { - config.parse(); - } catch (err) { - logger.log(`Error when parsing ${info.path}, skipping:\n${err}`); - return info.source; - } - - configType = configType || (info.path.includes('preview') ? 'preview' : 'main'); - const methodName = configType === 'main' ? 'defineMain' : 'definePreview'; - - const programNode = config._ast.program; - const hasNamedExports = Object.keys(config._exportDecls).length > 0; - - /** - * Scenario 1: Mixed exports - * - * ``` - * export const tags = []; - * export default { - * parameters: {}, - * }; - * ``` - * - * Transform into: `export default defineMain({ tags: [], parameters: {} })` - */ - if (config._exportsObject && hasNamedExports) { - const exportDecls = config._exportDecls; - - for (const [name, decl] of Object.entries(exportDecls)) { - if (decl.init) { - config._exportsObject.properties.push(t.objectProperty(t.identifier(name), decl.init)); - } - } - - programNode.body = programNode.body.filter((node) => { - if (t.isExportNamedDeclaration(node) && node.declaration) { - if (t.isVariableDeclaration(node.declaration)) { - node.declaration.declarations = node.declaration.declarations.filter( - (decl) => t.isIdentifier(decl.id) && !exportDecls[decl.id.name] - ); - return node.declaration.declarations.length > 0; - } - } - return true; - }); - } else if (config._exportsObject) { - /** - * Scenario 2: Default exports - * - * - Syntax 1: `default export const config = {}; export default config;` - * - Syntax 2: `export default {};` - * - * Transform into: `export default defineMain({})` - */ - const defineConfigCall = t.callExpression(t.identifier(methodName), [config._exportsObject]); - - let exportDefaultNode = null as any as t.ExportDefaultDeclaration; - let declarationNodeIndex = -1; - - programNode.body.forEach((node) => { - // Detect Syntax 1 - if (t.isExportDefaultDeclaration(node) && t.isIdentifier(node.declaration)) { - const declarationName = node.declaration.name; - - declarationNodeIndex = programNode.body.findIndex( - (n) => - t.isVariableDeclaration(n) && - n.declarations.some( - (d) => - t.isIdentifier(d.id) && - d.id.name === declarationName && - t.isObjectExpression(d.init) - ) - ); - - if (declarationNodeIndex !== -1) { - exportDefaultNode = node; - // remove the original declaration as it will become a default export - const declarationNode = programNode.body[declarationNodeIndex]; - if (t.isVariableDeclaration(declarationNode)) { - const id = declarationNode.declarations[0].id; - const variableName = t.isIdentifier(id) && id.name; - - if (variableName) { - programNode.body.splice(declarationNodeIndex, 1); - } - } - } - } else if (t.isExportDefaultDeclaration(node) && t.isObjectExpression(node.declaration)) { - // Detect Syntax 2 - exportDefaultNode = node; - } - }); - - if (exportDefaultNode !== null) { - exportDefaultNode.declaration = defineConfigCall; - } - } else if (hasNamedExports) { - /** - * Scenario 3: Named exports export const foo = {}; export bar = ''; - * - * Transform into: export default defineMain({ foo: {}, bar: '' }); - */ - const exportDecls = config._exportDecls; - const defineConfigProps = []; - - // Collect properties from named exports - for (const [name, decl] of Object.entries(exportDecls)) { - if (decl.init) { - defineConfigProps.push(t.objectProperty(t.identifier(name), decl.init)); - } - } - - // Construct the `define` call - const defineConfigCall = t.callExpression(t.identifier(methodName), [ - t.objectExpression(defineConfigProps), - ]); - - // Remove all related named exports - programNode.body = programNode.body.filter((node) => { - if (t.isExportNamedDeclaration(node) && node.declaration) { - if (t.isVariableDeclaration(node.declaration)) { - node.declaration.declarations = node.declaration.declarations.filter( - (decl) => t.isIdentifier(decl.id) && !exportDecls[decl.id.name] - ); - return node.declaration.declarations.length > 0; - } - } - return true; - }); - - // Add the new export default declaration - programNode.body.push(t.exportDefaultDeclaration(defineConfigCall)); - } - - const configImport = t.importDeclaration( - [t.importSpecifier(t.identifier(methodName), t.identifier(methodName))], - t.stringLiteral(frameworkPackage + `/${configType === 'main' ? 'node' : 'browser'}`) - ); - // only add the import if it doesn't exist yet - if ( - !programNode.body.some( - (node) => t.isImportDeclaration(node) && node.source.value === configImport.source.value - ) - ) { - programNode.body.unshift(configImport); - } - - const output = printConfig(config).code; - - if (dryRun) { - logger.log(`Would write to ${picocolors.yellow(info.path)}:\n${picocolors.green(output)}`); - return info.source; - } - - return skipFormatting ? output : formatFileContent(info.path, output); -} - -export async function storyToCsfFactory(info: FileInfo) { - const csf = loadCsf(info.source, { makeTitle: () => 'FIXME' }); - try { - csf.parse(); - } catch (err) { - logger.log(`Error when parsing ${info.path}, skipping:\n${err}`); - return info.source; - } - - const metaVariableName = 'meta'; - - /** - * Add the preview import if it doesn't exist yet: - * - * `import { config } from '#.storybook/preview'`; - */ - const programNode = csf._ast.program; - let foundConfigImport = false; - - // Check if a root-level constant named 'config' exists - const hasRootLevelConfig = programNode.body.some( - (n) => - t.isVariableDeclaration(n) && - n.declarations.some((declaration) => t.isIdentifier(declaration.id, { name: 'config' })) - ); - - const sbConfigImportName = hasRootLevelConfig ? 'storybookConfig' : 'config'; - - const sbConfigImportSpecifier = t.importSpecifier( - t.identifier(sbConfigImportName), - t.identifier('config') - ); - - programNode.body.forEach((node) => { - if (t.isImportDeclaration(node) && isValidPreviewPath(node.source.value)) { - const hasConfigSpecifier = node.specifiers.some( - (specifier) => - t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported, { name: 'config' }) - ); - - if (!hasConfigSpecifier) { - node.specifiers.push(sbConfigImportSpecifier); - } - - foundConfigImport = true; - } - }); - - const hasMeta = !!csf._meta; - - Object.entries(csf._storyExports).forEach(([key, decl]) => { - const id = decl.id; - const declarator = decl as t.VariableDeclarator; - let init = t.isVariableDeclarator(declarator) ? declarator.init : undefined; - - if (t.isIdentifier(id) && init) { - if (t.isTSSatisfiesExpression(init) || t.isTSAsExpression(init)) { - init = init.expression; - } - - if (t.isObjectExpression(init)) { - const typeAnnotation = id.typeAnnotation; - // Remove type annotation as it's now inferred - if (typeAnnotation) { - id.typeAnnotation = null; - } - - // Wrap the object in `meta.story()` - declarator.init = t.callExpression( - t.memberExpression(t.identifier(metaVariableName), t.identifier('story')), - [init] - ); - } else if (t.isArrowFunctionExpression(init)) { - // Transform CSF1 to meta.story({ render: }) - const renderProperty = t.objectProperty(t.identifier('render'), init); - - const objectExpression = t.objectExpression([renderProperty]); - - declarator.init = t.callExpression( - t.memberExpression(t.identifier(metaVariableName), t.identifier('story')), - [objectExpression] - ); - } - } - }); - - // modify meta - if (csf._metaPath) { - let declaration = csf._metaPath.node.declaration; - if (t.isTSSatisfiesExpression(declaration) || t.isTSAsExpression(declaration)) { - declaration = declaration.expression; - } - - if (t.isObjectExpression(declaration)) { - const metaVariable = t.variableDeclaration('const', [ - t.variableDeclarator( - t.identifier(metaVariableName), - t.callExpression( - t.memberExpression(t.identifier(sbConfigImportName), t.identifier('meta')), - [declaration] - ) - ), - ]); - csf._metaPath.replaceWith(metaVariable); - } else if (t.isIdentifier(declaration)) { - /** - * Transform const declared metas: - * - * `const meta = {}; export default meta;` - * - * Into a meta call: - * - * `const meta = config.meta({ title: 'A' });` - */ - const binding = csf._metaPath.scope.getBinding(declaration.name); - if (binding && binding.path.isVariableDeclarator()) { - const originalName = declaration.name; - - // Always rename the meta variable to 'meta' - binding.path.node.id = t.identifier(metaVariableName); - - let init = binding.path.node.init; - if (t.isTSSatisfiesExpression(init) || t.isTSAsExpression(init)) { - init = init.expression; - } - if (t.isObjectExpression(init)) { - binding.path.node.init = t.callExpression( - t.memberExpression(t.identifier(sbConfigImportName), t.identifier('meta')), - [init] - ); - } - - // Update all references to the original name - csf._metaPath.scope.rename(originalName, metaVariableName); - } - - // Remove the default export, it's not needed anymore - csf._metaPath.remove(); - } - } - - if (hasMeta && !foundConfigImport) { - const configImport = t.importDeclaration( - [sbConfigImportSpecifier], - t.stringLiteral('#.storybook/preview') - ); - programNode.body.unshift(configImport); - } - - // Remove type imports – now inferred – from @storybook/* packages - const disallowlist = [ - 'Story', - 'StoryFn', - 'StoryObj', - 'Meta', - 'MetaObj', - 'ComponentStory', - 'ComponentMeta', - ]; - - programNode.body = programNode.body.filter((node) => { - if (t.isImportDeclaration(node)) { - const { source, specifiers } = node; - - if (source.value.startsWith('@storybook/')) { - const allowedSpecifiers = specifiers.filter((specifier) => { - if (t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported)) { - return !disallowlist.includes(specifier.imported.name); - } - // Retain non-specifier imports (e.g., namespace imports) - return true; - }); - - // Remove the entire import if no specifiers are left - if (allowedSpecifiers.length > 0) { - node.specifiers = allowedSpecifiers; - return true; - } - - // Remove the import if no specifiers remain - return false; - } - } - - // Retain all other nodes - return true; - }); - - return printCsf(csf).code; -} - -const logger = console; - -async function runStoriesCodemod(dryRun: boolean | undefined) { - try { - let globString = 'src/stories/*.stories.*'; - if (!process.env.IN_STORYBOOK_SANDBOX) { - logger.log('Please enter the glob for your stories to migrate'); - globString = ( - await prompts({ - type: 'text', - name: 'glob', - message: 'glob', - initial: globString, - }) - ).glob; - } - logger.log('Applying codemod on your stories...'); - await runCodemod(globString, storyToCsfFactory, { dryRun }); - } catch (err: any) { - console.log('err message', err.message); - if (err.message === 'No files matched') { - console.log('going to run again'); - await runStoriesCodemod(dryRun); - } else { - throw err; - } - } -} - -export const csfFactories: CommandFix = { - id: 'csf-factories', - promptType: 'command', - async run({ dryRun, mainConfig, mainConfigPath, previewConfigPath, packageJson }) { - const frameworkPackageName = getFrameworkPackageName(mainConfig); - - const rendererPackageName = - (await getRendererPackageNameFromFramework(frameworkPackageName as string)) ?? - (await detectRenderer(packageJson)); - - console.log({ rendererPackageName, frameworkPackageName }); - - await runStoriesCodemod(dryRun); - logger.log('Applying codemod on your main config...'); - await runCodemod(mainConfigPath, (fileInfo) => - configToCsfFactory(fileInfo, 'main', { dryRun }) - ); - logger.log('Applying codemod on your preview config...'); - await runCodemod(previewConfigPath, (fileInfo) => - configToCsfFactory(fileInfo, 'preview', { dryRun }) - ); - }, -}; diff --git a/code/lib/cli-storybook/src/automigrate/fixes/index.ts b/code/lib/cli-storybook/src/automigrate/fixes/index.ts index 2bf67cc44704..c66b0106ce5d 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/index.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/index.ts @@ -1,3 +1,4 @@ +import { csfFactories } from '../../codemod/csf-factories'; import type { CommandFix, Fix } from '../types'; import { addonA11yAddonTest } from './addon-a11y-addon-test'; import { addonPostCSS } from './addon-postcss'; @@ -8,7 +9,6 @@ import { autodocsTags } from './autodocs-tags'; import { autodocsTrue } from './autodocs-true'; import { builderVite } from './builder-vite'; import { cra5 } from './cra5'; -import { csfFactories } from './csf-factories'; import { eslintPlugin } from './eslint-plugin'; import { initialGlobals } from './initial-globals'; import { mdx1to3 } from './mdx-1-to-3'; diff --git a/code/lib/cli-storybook/src/codemod/csf-factories.ts b/code/lib/cli-storybook/src/codemod/csf-factories.ts new file mode 100644 index 000000000000..963aa3649152 --- /dev/null +++ b/code/lib/cli-storybook/src/codemod/csf-factories.ts @@ -0,0 +1,75 @@ +import prompts from 'prompts'; + +import { runCodemod } from '../automigrate/codemod'; +import { getFrameworkPackageName } from '../automigrate/helpers/mainConfigFile'; +import type { CommandFix } from '../automigrate/types'; +import { configToCsfFactory } from './helpers/config-to-csf-factory'; +import { syncStorybookAddons } from './helpers/csf-factories-utils'; +import { storyToCsfFactory } from './helpers/story-to-csf-factory'; + +export const logger = console; + +async function runStoriesCodemod(dryRun: boolean | undefined) { + try { + let globString = 'src/stories/*.stories.*'; + if (!process.env.IN_STORYBOOK_SANDBOX) { + logger.log('Please enter the glob for your stories to migrate'); + globString = ( + await prompts({ + type: 'text', + name: 'glob', + message: 'glob', + initial: globString, + }) + ).glob; + } + await runCodemod(globString, storyToCsfFactory, { dryRun }); + } catch (err: any) { + console.log('err message', err.message); + if (err.message === 'No files matched') { + console.log('going to run again'); + await runStoriesCodemod(dryRun); + } else { + throw err; + } + } +} + +export const csfFactories: CommandFix = { + id: 'csf-factories', + promptType: 'command', + async run({ + dryRun, + mainConfig, + mainConfigPath, + previewConfigPath, + packageJson, + packageManager, + }) { + logger.log(`Adding imports map in ${packageManager.packageJsonPath()}`); + packageJson.imports = { + ...packageJson.imports, + // @ts-expect-error we need to upgrade type-fest + '#*': ['./*', './*.ts', './*.tsx', './*.js', './*.jsx'], + }; + await packageManager.writePackageJson(packageJson); + + logger.log('Applying codemod on your stories...'); + await runStoriesCodemod(dryRun); + + logger.log('Applying codemod on your main config...'); + const frameworkPackage = + getFrameworkPackageName(mainConfig) || '@storybook/your-framework-here'; + await runCodemod(mainConfigPath, (fileInfo) => + configToCsfFactory(fileInfo, { configType: 'main', frameworkPackage }, { dryRun }) + ); + + logger.log('Applying codemod on your preview config...'); + await runCodemod(previewConfigPath, (fileInfo) => + configToCsfFactory(fileInfo, { configType: 'preview', frameworkPackage }, { dryRun }) + ); + + logger.log('Synchronizing addons between main and preview config...'); + await syncStorybookAddons(mainConfig, previewConfigPath!); + }, +}; diff --git a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts new file mode 100644 index 000000000000..847368f71e15 --- /dev/null +++ b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts @@ -0,0 +1,174 @@ +import { describe, expect, it } from 'vitest'; + +import { dedent } from 'ts-dedent'; + +import { configToCsfFactory } from './config-to-csf-factory'; + +expect.addSnapshotSerializer({ + serialize: (val: any) => (typeof val === 'string' ? val : val.toString()), + test: () => true, +}); + +describe('main/preview codemod: general parsing functionality', () => { + const transform = async (source: string) => + ( + await configToCsfFactory( + { source, path: 'main.ts' }, + { configType: 'main', frameworkPackage: '@storybook/react-vite' } + ) + ).trim(); + + it('should wrap defineMain call from inline default export', async () => { + await expect( + transform(dedent` + export default { + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: ['@storybook/addon-essentials'], + framework: '@storybook/react-vite', + }; + `) + ).resolves.toMatchInlineSnapshot(` + import { defineMain } from '@storybook/react-vite/node'; + + export default defineMain({ + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: ['@storybook/addon-essentials'], + framework: '@storybook/react-vite', + }); + `); + }); + it('should wrap defineMain call from const declared default export', async () => { + await expect( + transform(dedent` + const config = { + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: ['@storybook/addon-essentials'], + framework: '@storybook/react-vite', + }; + + export default config; + `) + ).resolves.toMatchInlineSnapshot(` + import { defineMain } from '@storybook/react-vite/node'; + + export default defineMain({ + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: ['@storybook/addon-essentials'], + framework: '@storybook/react-vite', + }); + `); + }); + it('should wrap defineMain call from const declared default export and default export mix', async () => { + await expect( + transform(dedent` + export const tags = []; + const config = { + framework: '@storybook/react-vite', + }; + + export default config; + `) + ).resolves.toMatchInlineSnapshot(` + import { defineMain } from '@storybook/react-vite/node'; + + const config = { + framework: '@storybook/react-vite', + tags: [], + }; + + export default config; + `); + }); + it('should wrap defineMain call from named exports format', async () => { + await expect( + transform(dedent` + export const stories = ['../src/**/*.stories.@(js|jsx|ts|tsx)']; + export const addons = ['@storybook/addon-essentials']; + export const framework = '@storybook/react-vite'; + `) + ).resolves.toMatchInlineSnapshot(` + import { defineMain } from '@storybook/react-vite/node'; + + export default defineMain({ + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: ['@storybook/addon-essentials'], + framework: '@storybook/react-vite', + }); + `); + }); + it('should not add additional imports if there is already one', async () => { + const transformed = await transform(dedent` + import { defineMain } from '@storybook/react-vite/node'; + const config = {}; + + export default config; + `); + expect( + transformed.match(/import { defineMain } from '@storybook\/react-vite\/node'/g) + ).toHaveLength(1); + }); + + it('should remove legacy main config type imports', async () => { + await expect( + transform(dedent` + import type { StorybookConfig } from '@storybook/react' + + const config: StorybookConfig = { + stories: [] + }; + export default config; + `) + ).resolves.toMatchInlineSnapshot(` + import { defineMain } from '@storybook/react-vite/node'; + + export default defineMain({ + stories: [], + }); + `); + }); +}); + +describe('preview specific functionality', () => { + const transform = async (source: string) => + ( + await configToCsfFactory( + { source, path: 'preview.ts' }, + { configType: 'preview', frameworkPackage: '@storybook/react-vite' } + ) + ).trim(); + + it('should contain a named config export', async () => { + await expect( + transform(dedent` + export default { + tags: ['test'], + }; + `) + ).resolves.toMatchInlineSnapshot(` + import { definePreview } from '@storybook/react-vite/browser'; + + export default definePreview({ + tags: ['test'], + }); + `); + }); + + it('should remove legacy preview type imports', async () => { + await expect( + transform(dedent` + import type { Preview } from '@storybook/react' + + const preview: Preview = { + tags: [] + }; + export default preview; + `) + ).resolves.toMatchInlineSnapshot(` + import { definePreview } from '@storybook/react-vite/browser'; + + export default definePreview({ + tags: [], + }); + `); + }); +}); diff --git a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts new file mode 100644 index 000000000000..ea5bf4f5e93c --- /dev/null +++ b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts @@ -0,0 +1,177 @@ +/* eslint-disable no-underscore-dangle */ +import { types as t } from 'storybook/internal/babel'; +import { formatFileContent } from 'storybook/internal/common'; +import { loadConfig, printConfig } from 'storybook/internal/csf-tools'; + +import picocolors from 'picocolors'; + +import type { FileInfo } from '../../automigrate/codemod'; +import { logger } from '../csf-factories'; +import { cleanupTypeImports } from './csf-factories-utils'; + +export async function configToCsfFactory( + info: FileInfo, + { configType, frameworkPackage }: { configType: 'main' | 'preview'; frameworkPackage: string }, + { dryRun = false, skipFormatting = false }: { dryRun?: boolean; skipFormatting?: boolean } = {} +) { + const config = loadConfig(info.source); + try { + config.parse(); + } catch (err) { + logger.log(`Error when parsing ${info.path}, skipping:\n${err}`); + return info.source; + } + + const methodName = configType === 'main' ? 'defineMain' : 'definePreview'; + + const programNode = config._ast.program; + const hasNamedExports = Object.keys(config._exportDecls).length > 0; + + /** + * Scenario 1: Mixed exports + * + * ``` + * export const tags = []; + * export default { + * parameters: {}, + * }; + * ``` + * + * Transform into: `export default defineMain({ tags: [], parameters: {} })` + */ + if (config._exportsObject && hasNamedExports) { + const exportDecls = config._exportDecls; + + for (const [name, decl] of Object.entries(exportDecls)) { + if (decl.init) { + config._exportsObject.properties.push(t.objectProperty(t.identifier(name), decl.init)); + } + } + + programNode.body = programNode.body.filter((node) => { + if (t.isExportNamedDeclaration(node) && node.declaration) { + if (t.isVariableDeclaration(node.declaration)) { + node.declaration.declarations = node.declaration.declarations.filter( + (decl) => t.isIdentifier(decl.id) && !exportDecls[decl.id.name] + ); + return node.declaration.declarations.length > 0; + } + } + return true; + }); + } else if (config._exportsObject) { + /** + * Scenario 2: Default exports + * + * - Syntax 1: `default export const config = {}; export default config;` + * - Syntax 2: `export default {};` + * + * Transform into: `export default defineMain({})` + */ + const defineConfigCall = t.callExpression(t.identifier(methodName), [config._exportsObject]); + + let exportDefaultNode = null as any as t.ExportDefaultDeclaration; + let declarationNodeIndex = -1; + + programNode.body.forEach((node) => { + // Detect Syntax 1 + if (t.isExportDefaultDeclaration(node) && t.isIdentifier(node.declaration)) { + const declarationName = node.declaration.name; + + declarationNodeIndex = programNode.body.findIndex( + (n) => + t.isVariableDeclaration(n) && + n.declarations.some( + (d) => + t.isIdentifier(d.id) && + d.id.name === declarationName && + t.isObjectExpression(d.init) + ) + ); + + if (declarationNodeIndex !== -1) { + exportDefaultNode = node; + // remove the original declaration as it will become a default export + const declarationNode = programNode.body[declarationNodeIndex]; + if (t.isVariableDeclaration(declarationNode)) { + const id = declarationNode.declarations[0].id; + const variableName = t.isIdentifier(id) && id.name; + + if (variableName) { + programNode.body.splice(declarationNodeIndex, 1); + } + } + } + } else if (t.isExportDefaultDeclaration(node) && t.isObjectExpression(node.declaration)) { + // Detect Syntax 2 + exportDefaultNode = node; + } + }); + + if (exportDefaultNode !== null) { + exportDefaultNode.declaration = defineConfigCall; + } + } else if (hasNamedExports) { + /** + * Scenario 3: Named exports export const foo = {}; export bar = ''; + * + * Transform into: export default defineMain({ foo: {}, bar: '' }); + */ + const exportDecls = config._exportDecls; + const defineConfigProps = []; + + // Collect properties from named exports + for (const [name, decl] of Object.entries(exportDecls)) { + if (decl.init) { + defineConfigProps.push(t.objectProperty(t.identifier(name), decl.init)); + } + } + + // Construct the `define` call + const defineConfigCall = t.callExpression(t.identifier(methodName), [ + t.objectExpression(defineConfigProps), + ]); + + // Remove all related named exports + programNode.body = programNode.body.filter((node) => { + if (t.isExportNamedDeclaration(node) && node.declaration) { + if (t.isVariableDeclaration(node.declaration)) { + node.declaration.declarations = node.declaration.declarations.filter( + (decl) => t.isIdentifier(decl.id) && !exportDecls[decl.id.name] + ); + return node.declaration.declarations.length > 0; + } + } + return true; + }); + + // Add the new export default declaration + programNode.body.push(t.exportDefaultDeclaration(defineConfigCall)); + } + + const configImport = t.importDeclaration( + [t.importSpecifier(t.identifier(methodName), t.identifier(methodName))], + t.stringLiteral(frameworkPackage + `/${configType === 'main' ? 'node' : 'browser'}`) + ); + // only add the import if it doesn't exist yet + if ( + !programNode.body.some( + (node) => t.isImportDeclaration(node) && node.source.value === configImport.source.value + ) + ) { + programNode.body.unshift(configImport); + } + + // Remove type imports – now inferred – from @storybook/* packages + const disallowList = ['StorybookConfig', 'Preview']; + programNode.body = cleanupTypeImports(programNode, disallowList); + + const output = printConfig(config).code; + + if (dryRun) { + logger.log(`Would write to ${picocolors.yellow(info.path)}:\n${picocolors.green(output)}`); + return info.source; + } + + return skipFormatting ? output : formatFileContent(info.path, output); +} diff --git a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts new file mode 100644 index 000000000000..27e065c86455 --- /dev/null +++ b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts @@ -0,0 +1,69 @@ +import type { Mock } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; + +import type { StorybookConfigRaw } from '@storybook/types'; + +import { loadConfig, printConfig } from '@storybook/core/csf-tools'; + +import { dedent } from 'ts-dedent'; + +import { getSyncedStorybookAddons } from './csf-factories-utils'; +import { getAddonAnnotations } from './get-addon-annotations'; + +vi.mock('./get-addon-annotations'); + +expect.addSnapshotSerializer({ + serialize: (val: any) => (typeof val === 'string' ? dedent(val) : dedent(val.toString())), + test: () => true, +}); + +describe('getSyncedStorybookAddons', () => { + const mainConfig: StorybookConfigRaw = { + stories: [], + addons: ['custom-addon', '@storybook/addon-a11y'], + }; + it('should sync addons between main and preview', async () => { + const preview = loadConfig(` + import * as myAddonAnnotations from "custom-addon/preview"; + import { definePreview } from "@storybook/react-vite/browser"; + + export default definePreview({ + addons: [myAddonAnnotations], + }); + `).parse(); + + (getAddonAnnotations as Mock).mockImplementation(() => { + return { importName: 'addonA11yAnnotations', importPath: '@storybook/addon-a11y/preview' }; + }); + + const result = await getSyncedStorybookAddons(mainConfig, preview); + expect(printConfig(result).code).toMatchInlineSnapshot(` + import * as addonA11yAnnotations from "@storybook/addon-a11y/preview"; + import * as myAddonAnnotations from "custom-addon/preview"; + import { definePreview } from "@storybook/react-vite/browser"; + + export default definePreview({ + addons: [myAddonAnnotations, addonA11yAnnotations], + }); + `); + }); + it('should not modify the code if all addons are already synced', async () => { + const originalCode = ` + import * as addonA11yAnnotations from "@storybook/addon-a11y/preview"; + import * as myAddonAnnotations from "custom-addon/preview"; + import { definePreview } from "@storybook/react-vite/browser"; + + export default definePreview({ + addons: [myAddonAnnotations, addonA11yAnnotations], + }); + `; + const preview = loadConfig(originalCode).parse(); + + (getAddonAnnotations as Mock).mockImplementation(() => { + return { importName: 'addonA11yAnnotations', importPath: '@storybook/addon-a11y/preview' }; + }); + + const result = await getSyncedStorybookAddons(mainConfig, preview); + expect(printConfig(result).code).toEqual(originalCode); + }); +}); diff --git a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts new file mode 100644 index 000000000000..7a89afabdecf --- /dev/null +++ b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts @@ -0,0 +1,101 @@ +/* eslint-disable no-underscore-dangle */ +import path from 'node:path'; + +import { types as t } from 'storybook/internal/babel'; +import { type ConfigFile, readConfig, writeConfig } from 'storybook/internal/csf-tools'; + +import type { StorybookConfigRaw } from '@storybook/types'; + +import { getAddonNames } from '../../automigrate/helpers/mainConfigFile'; +import { logger } from '../csf-factories'; +import { getAddonAnnotations } from './get-addon-annotations'; + +export function cleanupTypeImports(programNode: t.Program, disallowList: string[]) { + return programNode.body.filter((node) => { + if (t.isImportDeclaration(node)) { + const { source, specifiers } = node; + + if (source.value.startsWith('@storybook/')) { + const allowedSpecifiers = specifiers.filter((specifier) => { + if (t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported)) { + return !disallowList.includes(specifier.imported.name); + } + // Retain non-specifier imports (e.g., namespace imports) + return true; + }); + + // Remove the entire import if no specifiers are left + if (allowedSpecifiers.length > 0) { + node.specifiers = allowedSpecifiers; + return true; + } + + // Remove the import if no specifiers remain + return false; + } + } + + // Retain all other nodes + return true; + }); +} + +export async function syncStorybookAddons( + mainConfig: StorybookConfigRaw, + previewConfigPath: string +) { + const previewConfig = await readConfig(previewConfigPath!); + const modifiedConfig = await getSyncedStorybookAddons(mainConfig, previewConfig); + + await writeConfig(modifiedConfig); +} + +export async function getSyncedStorybookAddons( + mainConfig: StorybookConfigRaw, + previewConfig: ConfigFile +): Promise { + const program = previewConfig._ast.program; + const isCsfFactoryPreview = !!program.body.find((node) => { + return ( + t.isImportDeclaration(node) && + node.source.value.includes('@storybook') && + node.source.value.endsWith('/browser') && + node.specifiers.some((specifier) => { + return ( + t.isImportSpecifier(specifier) && + t.isIdentifier(specifier.imported) && + specifier.imported.name === 'definePreview' + ); + }) + ); + }); + + if (!isCsfFactoryPreview) { + logger.log('Skipping syncStorybookAddons as the preview config is not a csf factory'); + return previewConfig; + } + + const addons = getAddonNames(mainConfig); + + /** + * This goes through all mainConfig.addons, read their package.json and check whether they have an + * exports map called preview, if so add to the array + */ + addons.forEach(async (addon) => { + const annotations = await getAddonAnnotations(addon); + if (annotations) { + previewConfig.setImport({ namespace: annotations.importName }, annotations.importPath); + const existingAddons = previewConfig.getFieldNode(['addons']); + if ( + t.isArrayExpression(existingAddons) && + !existingAddons.elements.some( + (element) => t.isIdentifier(element) && element.name === annotations.importName + ) + ) { + previewConfig.appendNodeToArray(['addons'], t.identifier(annotations.importName)); + } + } + }); + + return previewConfig; +} diff --git a/code/lib/cli-storybook/src/codemod/helpers/get-addon-annotations.test.ts b/code/lib/cli-storybook/src/codemod/helpers/get-addon-annotations.test.ts new file mode 100644 index 000000000000..c523b2cf74f2 --- /dev/null +++ b/code/lib/cli-storybook/src/codemod/helpers/get-addon-annotations.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest'; + +import { getAnnotationsName } from './get-addon-annotations'; + +describe('getAnnotationsName', () => { + it('should handle @storybook namespace and camel case conversion', () => { + expect(getAnnotationsName('@storybook/addon-essentials')).toBe('addonEssentialsAnnotations'); + }); + + it('should handle other namespaces and camel case conversion', () => { + expect(getAnnotationsName('@kudos-components/testing/module')).toBe( + 'kudosComponentsTestingModuleAnnotations' + ); + }); + + it('should handle strings without namespaces', () => { + expect(getAnnotationsName('plain-text/example')).toBe('plainTextExampleAnnotations'); + }); + + it('should handle strings with multiple special characters', () => { + expect(getAnnotationsName('@storybook/multi-part/example-test')).toBe( + 'multiPartExampleTestAnnotations' + ); + }); +}); diff --git a/code/lib/cli-storybook/src/codemod/helpers/get-addon-annotations.ts b/code/lib/cli-storybook/src/codemod/helpers/get-addon-annotations.ts new file mode 100644 index 000000000000..e9d562326915 --- /dev/null +++ b/code/lib/cli-storybook/src/codemod/helpers/get-addon-annotations.ts @@ -0,0 +1,41 @@ +import path from 'node:path'; + +/** + * Get the name of the annotations object for a given addon. + * + * Input: '@storybook/addon-essentials' + * + * Output: 'addonEssentialsAnnotations' + */ +export function getAnnotationsName(addonName: string): string { + // remove @storybook namespace, split by special characters, convert to camelCase + const cleanedUpName = addonName + .replace(/^@storybook\//, '') + .split(/[^a-zA-Z0-9]+/) + .map((word, index) => + index === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() + ) + .join('') + .replace(/^./, (char) => char.toLowerCase()); + + return `${cleanedUpName}Annotations`; +} + +export async function getAddonAnnotations(addon: string) { + try { + const data = { + importPath: `${addon}/preview`, + importName: getAnnotationsName(addon), + }; + // TODO: current workaround needed only for essentials, fix this once we change the preview entry-point for that package + if (addon === '@storybook/addon-essentials') { + data.importPath = '@storybook/addon-essentials/entry-preview'; + } else { + require.resolve(path.join(addon, 'preview')); + } + + return data; + } catch (err) {} + + return null; +} diff --git a/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.test.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts similarity index 53% rename from code/lib/cli-storybook/src/automigrate/fixes/csf-factories.test.ts rename to code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts index 880b1b74b704..3a98dbeece0b 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/csf-factories.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts @@ -4,188 +4,63 @@ import { formatFileContent } from '@storybook/core/common'; import { dedent } from 'ts-dedent'; -import { configToCsfFactory, storyToCsfFactory } from './csf-factories'; +import { storyToCsfFactory } from './story-to-csf-factory'; expect.addSnapshotSerializer({ serialize: (val: any) => (typeof val === 'string' ? val : val.toString()), test: () => true, }); -describe('csf-factories', () => { - describe('main/preview codemod: general parsing functionality', () => { - const transform = async (source: string) => - ( - await configToCsfFactory( - { source, path: 'main.ts' }, - { configType: 'main', frameworkPackage: '@storybook/react-vite' } - ) - ).trim(); - - it('should wrap defineMain call from inline default export', async () => { +describe('stories codemod', () => { + const transform = async (source: string) => + formatFileContent( + 'Component.stories.tsx', + await storyToCsfFactory({ source, path: 'Component.stories.tsx' }) + ); + describe('javascript', () => { + it('should wrap const declared meta', async () => { await expect( transform(dedent` - export default { - stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], - addons: ['@storybook/addon-essentials'], - framework: '@storybook/react-vite', - }; - `) - ).resolves.toMatchInlineSnapshot(` - import { defineMain } from '@storybook/react-vite/node'; - - export default defineMain({ - stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], - addons: ['@storybook/addon-essentials'], - framework: '@storybook/react-vite', - }); - `); - }); - it('should wrap defineMain call from const declared default export', async () => { - await expect( - transform(dedent` - const config = { - stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], - addons: ['@storybook/addon-essentials'], - framework: '@storybook/react-vite', - }; - - export default config; - `) - ).resolves.toMatchInlineSnapshot(` - import { defineMain } from '@storybook/react-vite/node'; - - export default defineMain({ - stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], - addons: ['@storybook/addon-essentials'], - framework: '@storybook/react-vite', - }); - `); - }); - it('should wrap defineMain call from const declared default export and default export mix', async () => { - await expect( - transform(dedent` - export const tags = []; - const config = { - framework: '@storybook/react-vite', - }; - - export default config; - `) - ).resolves.toMatchInlineSnapshot(` - import { defineMain } from '@storybook/react-vite/node'; - - const config = { - framework: '@storybook/react-vite', - tags: [], - }; - - export default config; - `); - }); - it('should wrap defineMain call from named exports format', async () => { - await expect( - transform(dedent` - export const stories = ['../src/**/*.stories.@(js|jsx|ts|tsx)']; - export const addons = ['@storybook/addon-essentials']; - export const framework = '@storybook/react-vite'; - `) - ).resolves.toMatchInlineSnapshot(` - import { defineMain } from '@storybook/react-vite/node'; - - export default defineMain({ - stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], - addons: ['@storybook/addon-essentials'], - framework: '@storybook/react-vite', - }); - `); - }); - it('should not add additional imports if there is already one', async () => { - const transformed = await transform(dedent` - import { defineMain } from '@storybook/react-vite/node'; - const config = {}; - - export default config; - `); - expect( - transformed.match(/import { defineMain } from '@storybook\/react-vite\/node'/g) - ).toHaveLength(1); - }); - }); - describe('preview specific functionality', () => { - const transform = async (source: string) => - ( - await configToCsfFactory( - { source, path: 'preview.ts' }, - { configType: 'preview', frameworkPackage: '@storybook/react-vite' } - ) - ).trim(); - - it('should contain a named config export', async () => { - await expect( - transform(dedent` - export default { - tags: ['test'], - }; - `) - ).resolves.toMatchInlineSnapshot(` - import { definePreview } from '@storybook/react-vite/browser'; - - export default definePreview({ - tags: ['test'], - }); - `); - }); - }); - describe('stories codemod', () => { - const transform = async (source: string) => - formatFileContent( - 'Component.stories.tsx', - await storyToCsfFactory({ source, path: 'Component.stories.tsx' }) - ); - describe('javascript', () => { - it('should wrap const declared meta', async () => { - await expect( - transform(dedent` const meta = { title: 'Component' }; export default meta; `) - ).resolves.toMatchInlineSnapshot(` + ).resolves.toMatchInlineSnapshot(` import { config } from '#.storybook/preview'; const meta = config.meta({ title: 'Component' }); `); - }); + }); - it('should transform and wrap inline default exported meta', async () => { - await expect( - transform(dedent` + it('should transform and wrap inline default exported meta', async () => { + await expect( + transform(dedent` export default { title: 'Component' }; `) - ).resolves.toMatchInlineSnapshot(` + ).resolves.toMatchInlineSnapshot(` import { config } from '#.storybook/preview'; const meta = config.meta({ title: 'Component', }); `); - }); + }); - it('should rename meta object to meta if it has a different name', async () => { - await expect( - transform(dedent` + it('should rename meta object to meta if it has a different name', async () => { + await expect( + transform(dedent` const componentMeta = { title: 'Component' }; export default componentMeta; `) - ).resolves.toMatchInlineSnapshot(` + ).resolves.toMatchInlineSnapshot(` import { config } from '#.storybook/preview'; const meta = config.meta({ title: 'Component' }); `); - }); + }); - it('should wrap stories in a meta.story method', async () => { - await expect( - transform(dedent` + it('should wrap stories in a meta.story method', async () => { + await expect( + transform(dedent` const componentMeta = { title: 'Component' }; export default componentMeta; export const A = { @@ -193,7 +68,7 @@ describe('csf-factories', () => { render: (args) => }; `) - ).resolves.toMatchInlineSnapshot(` + ).resolves.toMatchInlineSnapshot(` import { config } from '#.storybook/preview'; const meta = config.meta({ title: 'Component' }); @@ -202,11 +77,11 @@ describe('csf-factories', () => { render: (args) => , }); `); - }); + }); - it('should respect existing config imports', async () => { - await expect( - transform(dedent` + it('should respect existing config imports', async () => { + await expect( + transform(dedent` import { decorators } from "#.storybook/preview"; const componentMeta = { title: 'Component' }; export default componentMeta; @@ -215,7 +90,7 @@ describe('csf-factories', () => { render: (args) => }; `) - ).resolves.toMatchInlineSnapshot(` + ).resolves.toMatchInlineSnapshot(` import { config, decorators } from '#.storybook/preview'; const meta = config.meta({ title: 'Component' }); @@ -224,11 +99,11 @@ describe('csf-factories', () => { render: (args) => , }); `); - }); + }); - it('if there is an existing local constant called config, rename storybook config import', async () => { - await expect( - transform(dedent` + it('if there is an existing local constant called config, rename storybook config import', async () => { + await expect( + transform(dedent` const componentMeta = { title: 'Component' }; export default componentMeta; const config = {}; @@ -237,7 +112,7 @@ describe('csf-factories', () => { render: (args) => }; `) - ).resolves.toMatchInlineSnapshot(` + ).resolves.toMatchInlineSnapshot(` import { config as storybookConfig } from '#.storybook/preview'; const meta = storybookConfig.meta({ title: 'Component' }); @@ -247,16 +122,16 @@ describe('csf-factories', () => { render: (args) => , }); `); - }); + }); - it('converts CSF1 into CSF4 with render', async () => { - await expect( - transform(dedent` + it('converts CSF1 into CSF4 with render', async () => { + await expect( + transform(dedent` const meta = { title: 'Component' }; export default meta; export const CSF1Story = () =>
Hello
; `) - ).resolves.toMatchInlineSnapshot(` + ).resolves.toMatchInlineSnapshot(` import { config } from '#.storybook/preview'; const meta = config.meta({ title: 'Component' }); @@ -264,11 +139,11 @@ describe('csf-factories', () => { render: () =>
Hello
, }); `); - }); }); + }); - describe('typescript', () => { - const inlineMetaSatisfies = dedent` + describe('typescript', () => { + const inlineMetaSatisfies = dedent` import { Meta, StoryObj as CSF3 } from '@storybook/react'; import { ComponentProps } from './Component'; @@ -278,8 +153,8 @@ describe('csf-factories', () => { args: { primary: true } }; `; - it('meta satisfies syntax', async () => { - await expect(transform(inlineMetaSatisfies)).resolves.toMatchInlineSnapshot(` + it('meta satisfies syntax', async () => { + await expect(transform(inlineMetaSatisfies)).resolves.toMatchInlineSnapshot(` import { config } from '#.storybook/preview'; import { ComponentProps } from './Component'; @@ -290,9 +165,9 @@ describe('csf-factories', () => { args: { primary: true }, }); `); - }); + }); - const inlineMetaAs = dedent` + const inlineMetaAs = dedent` import { Meta, StoryObj as CSF3 } from '@storybook/react'; import { ComponentProps } from './Component'; @@ -302,8 +177,8 @@ describe('csf-factories', () => { args: { primary: true } }; `; - it('meta as syntax', async () => { - await expect(transform(inlineMetaAs)).resolves.toMatchInlineSnapshot(` + it('meta as syntax', async () => { + await expect(transform(inlineMetaAs)).resolves.toMatchInlineSnapshot(` import { config } from '#.storybook/preview'; import { ComponentProps } from './Component'; @@ -314,8 +189,8 @@ describe('csf-factories', () => { args: { primary: true }, }); `); - }); - const metaSatisfies = dedent` + }); + const metaSatisfies = dedent` import { Meta, StoryObj as CSF3 } from '@storybook/react'; import { ComponentProps } from './Component'; @@ -326,8 +201,8 @@ describe('csf-factories', () => { args: { primary: true } }; `; - it('meta satisfies syntax', async () => { - await expect(transform(metaSatisfies)).resolves.toMatchInlineSnapshot(` + it('meta satisfies syntax', async () => { + await expect(transform(metaSatisfies)).resolves.toMatchInlineSnapshot(` import { config } from '#.storybook/preview'; import { ComponentProps } from './Component'; @@ -338,9 +213,9 @@ describe('csf-factories', () => { args: { primary: true }, }); `); - }); + }); - const metaAs = dedent` + const metaAs = dedent` import { Meta, StoryObj as CSF3 } from '@storybook/react'; import { ComponentProps } from './Component'; @@ -351,8 +226,8 @@ describe('csf-factories', () => { args: { primary: true } }; `; - it('meta as syntax', async () => { - await expect(transform(metaAs)).resolves.toMatchInlineSnapshot(` + it('meta as syntax', async () => { + await expect(transform(metaAs)).resolves.toMatchInlineSnapshot(` import { config } from '#.storybook/preview'; import { ComponentProps } from './Component'; @@ -363,9 +238,9 @@ describe('csf-factories', () => { args: { primary: true }, }); `); - }); + }); - const storySatisfies = dedent` + const storySatisfies = dedent` import { Meta, StoryObj as CSF3 } from '@storybook/react'; import { ComponentProps } from './Component'; @@ -376,8 +251,8 @@ describe('csf-factories', () => { args: { primary: true } } satisfies CSF3; `; - it('story satisfies syntax', async () => { - await expect(transform(storySatisfies)).resolves.toMatchInlineSnapshot(` + it('story satisfies syntax', async () => { + await expect(transform(storySatisfies)).resolves.toMatchInlineSnapshot(` import { config } from '#.storybook/preview'; import { ComponentProps } from './Component'; @@ -388,9 +263,9 @@ describe('csf-factories', () => { args: { primary: true }, }); `); - }); + }); - const storyAs = dedent` + const storyAs = dedent` import { Meta, StoryObj as CSF3 } from '@storybook/react'; import { ComponentProps } from './Component'; @@ -401,8 +276,8 @@ describe('csf-factories', () => { args: { primary: true } } as CSF3; `; - it('story as syntax', async () => { - await expect(transform(storyAs)).resolves.toMatchInlineSnapshot(` + it('story as syntax', async () => { + await expect(transform(storyAs)).resolves.toMatchInlineSnapshot(` import { config } from '#.storybook/preview'; import { ComponentProps } from './Component'; @@ -413,21 +288,20 @@ describe('csf-factories', () => { args: { primary: true }, }); `); - }); + }); - it('should yield the same result to all syntaxes', async () => { - const allSnippets = await Promise.all([ - transform(inlineMetaSatisfies), - transform(inlineMetaAs), - transform(metaSatisfies), - transform(metaAs), - transform(storySatisfies), - transform(storyAs), - ]); - - allSnippets.forEach((result) => { - expect(result).toEqual(allSnippets[0]); - }); + it('should yield the same result to all syntaxes', async () => { + const allSnippets = await Promise.all([ + transform(inlineMetaSatisfies), + transform(inlineMetaAs), + transform(metaSatisfies), + transform(metaAs), + transform(storySatisfies), + transform(storyAs), + ]); + + allSnippets.forEach((result) => { + expect(result).toEqual(allSnippets[0]); }); }); }); diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts new file mode 100644 index 000000000000..c359b8dfa997 --- /dev/null +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts @@ -0,0 +1,171 @@ +/* eslint-disable no-underscore-dangle */ +import { types as t } from 'storybook/internal/babel'; +import { isValidPreviewPath, loadCsf, printCsf } from 'storybook/internal/csf-tools'; + +import type { FileInfo } from '../../automigrate/codemod'; +import { logger } from '../csf-factories'; +import { cleanupTypeImports } from './csf-factories-utils'; + +export async function storyToCsfFactory(info: FileInfo) { + const csf = loadCsf(info.source, { makeTitle: () => 'FIXME' }); + try { + csf.parse(); + } catch (err) { + logger.log(`Error when parsing ${info.path}, skipping:\n${err}`); + return info.source; + } + + const metaVariableName = 'meta'; + + /** + * Add the preview import if it doesn't exist yet: + * + * `import { config } from '#.storybook/preview'`; + */ + const programNode = csf._ast.program; + let foundConfigImport = false; + + // Check if a root-level constant named 'config' exists + const hasRootLevelConfig = programNode.body.some( + (n) => + t.isVariableDeclaration(n) && + n.declarations.some((declaration) => t.isIdentifier(declaration.id, { name: 'config' })) + ); + + const sbConfigImportName = hasRootLevelConfig ? 'storybookConfig' : 'config'; + + const sbConfigImportSpecifier = t.importSpecifier( + t.identifier(sbConfigImportName), + t.identifier('config') + ); + + programNode.body.forEach((node) => { + if (t.isImportDeclaration(node) && isValidPreviewPath(node.source.value)) { + const hasConfigSpecifier = node.specifiers.some( + (specifier) => + t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported, { name: 'config' }) + ); + + if (!hasConfigSpecifier) { + node.specifiers.push(sbConfigImportSpecifier); + } + + foundConfigImport = true; + } + }); + + const hasMeta = !!csf._meta; + + Object.entries(csf._storyExports).forEach(([key, decl]) => { + const id = decl.id; + const declarator = decl as t.VariableDeclarator; + let init = t.isVariableDeclarator(declarator) ? declarator.init : undefined; + + if (t.isIdentifier(id) && init) { + if (t.isTSSatisfiesExpression(init) || t.isTSAsExpression(init)) { + init = init.expression; + } + + if (t.isObjectExpression(init)) { + const typeAnnotation = id.typeAnnotation; + // Remove type annotation as it's now inferred + if (typeAnnotation) { + id.typeAnnotation = null; + } + + // Wrap the object in `meta.story()` + declarator.init = t.callExpression( + t.memberExpression(t.identifier(metaVariableName), t.identifier('story')), + [init] + ); + } else if (t.isArrowFunctionExpression(init)) { + // Transform CSF1 to meta.story({ render: }) + const renderProperty = t.objectProperty(t.identifier('render'), init); + + const objectExpression = t.objectExpression([renderProperty]); + + declarator.init = t.callExpression( + t.memberExpression(t.identifier(metaVariableName), t.identifier('story')), + [objectExpression] + ); + } + } + }); + + // modify meta + if (csf._metaPath) { + let declaration = csf._metaPath.node.declaration; + if (t.isTSSatisfiesExpression(declaration) || t.isTSAsExpression(declaration)) { + declaration = declaration.expression; + } + + if (t.isObjectExpression(declaration)) { + const metaVariable = t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier(metaVariableName), + t.callExpression( + t.memberExpression(t.identifier(sbConfigImportName), t.identifier('meta')), + [declaration] + ) + ), + ]); + csf._metaPath.replaceWith(metaVariable); + } else if (t.isIdentifier(declaration)) { + /** + * Transform const declared metas: + * + * `const meta = {}; export default meta;` + * + * Into a meta call: + * + * `const meta = config.meta({ title: 'A' });` + */ + const binding = csf._metaPath.scope.getBinding(declaration.name); + if (binding && binding.path.isVariableDeclarator()) { + const originalName = declaration.name; + + // Always rename the meta variable to 'meta' + binding.path.node.id = t.identifier(metaVariableName); + + let init = binding.path.node.init; + if (t.isTSSatisfiesExpression(init) || t.isTSAsExpression(init)) { + init = init.expression; + } + if (t.isObjectExpression(init)) { + binding.path.node.init = t.callExpression( + t.memberExpression(t.identifier(sbConfigImportName), t.identifier('meta')), + [init] + ); + } + + // Update all references to the original name + csf._metaPath.scope.rename(originalName, metaVariableName); + } + + // Remove the default export, it's not needed anymore + csf._metaPath.remove(); + } + } + + if (hasMeta && !foundConfigImport) { + const configImport = t.importDeclaration( + [sbConfigImportSpecifier], + t.stringLiteral('#.storybook/preview') + ); + programNode.body.unshift(configImport); + } + + // Remove type imports – now inferred – from @storybook/* packages + const disallowList = [ + 'Story', + 'StoryFn', + 'StoryObj', + 'Meta', + 'MetaObj', + 'ComponentStory', + 'ComponentMeta', + ]; + programNode.body = cleanupTypeImports(programNode, disallowList); + + return printCsf(csf).code; +} diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 2a86db4a73c3..be12a4ede276 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -819,55 +819,14 @@ export const extendPreview: Task['run'] = async ({ template, sandboxDir }) => { template.expected.framework === '@storybook/react-vite' && !template.skipTasks.includes('vitest-integration') ) { - // add CSF4 style config - previewConfig.setImport(['defineConfig'], '@storybook/react/preview'); - // and all of the addons/previewAnnotations that are needed previewConfig.setImport(null, '../src/stories/components'); - previewConfig.setImport( - { namespace: 'addonEssentialsAnnotations' }, - '@storybook/addon-essentials/entry-preview' - ); - previewConfig.setImport({ namespace: 'addonA11yAnnotations' }, '@storybook/addon-a11y/preview'); - previewConfig.setImport( - { namespace: 'addonActionsAnnotations' }, - '@storybook/addon-actions/preview' - ); - previewConfig.setImport( - { namespace: 'addonTestAnnotations' }, - '@storybook/experimental-addon-test/preview' - ); previewConfig.setImport({ namespace: 'coreAnnotations' }, '../template-stories/core/preview'); previewConfig.setImport( { namespace: 'toolbarAnnotations' }, '../template-stories/addons/toolbars/preview' ); - - previewConfig.setBodyDeclaration( - t.exportNamedDeclaration( - t.variableDeclaration('const', [ - t.variableDeclarator( - t.identifier('config'), - t.callExpression(t.identifier('defineConfig'), [ - t.objectExpression([ - t.spreadElement(t.identifier('preview')), - t.objectProperty( - t.identifier('addons'), - t.arrayExpression([ - t.identifier('addonEssentialsAnnotations'), - t.identifier('addonA11yAnnotations'), - t.identifier('addonActionsAnnotations'), - t.identifier('addonTestAnnotations'), - t.identifier('coreAnnotations'), - t.identifier('toolbarAnnotations'), - ]) - ), - ]), - ]) - ), - ]), - [] - ) - ); + previewConfig.appendNodeToArray(['addons'], t.identifier('coreAnnotations')); + previewConfig.appendNodeToArray(['addons'], t.identifier('toolbarAnnotations')); } if (template.expected.builder.includes('vite')) { diff --git a/scripts/tasks/sandbox.ts b/scripts/tasks/sandbox.ts index ef79e3d4c32f..7bca37085843 100644 --- a/scripts/tasks/sandbox.ts +++ b/scripts/tasks/sandbox.ts @@ -2,6 +2,7 @@ import dirSize from 'fast-folder-size'; // eslint-disable-next-line depend/ban-dependencies import { pathExists, remove } from 'fs-extra'; import { join } from 'path'; +import prompts from 'prompts'; import { promisify } from 'util'; import { now, saveBench } from '../bench/utils'; @@ -144,12 +145,12 @@ export const sandbox: Task = { await extendMain(details, options); - await extendPreview(details, options); - await setImportMap(details.sandboxDir); await runMigrations(details, options); + await extendPreview(details, options); + logger.info(`✅ Storybook sandbox created at ${details.sandboxDir}`); }, }; From 310ebf2243b3b8030a9074c1a085dbe0866c22ad Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 20 Jan 2025 13:07:45 +0100 Subject: [PATCH 069/144] sync main and preview in sb add --- code/lib/cli-storybook/src/add.ts | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/code/lib/cli-storybook/src/add.ts b/code/lib/cli-storybook/src/add.ts index 8ba600883e7e..2fb796d7793b 100644 --- a/code/lib/cli-storybook/src/add.ts +++ b/code/lib/cli-storybook/src/add.ts @@ -10,6 +10,8 @@ import { } from 'storybook/internal/common'; import { readConfig, writeConfig } from 'storybook/internal/csf-tools'; +import type { StorybookConfigRaw } from '@storybook/types'; + import prompts from 'prompts'; import SemVer from 'semver'; import { dedent } from 'ts-dedent'; @@ -18,6 +20,8 @@ import { getRequireWrapperName, wrapValueWithRequireWrapper, } from './automigrate/fixes/wrap-require-utils'; +import { getStorybookData } from './automigrate/helpers/mainConfigFile'; +import { syncStorybookAddons } from './codemod/helpers/csf-factories-utils'; import { postinstallAddon } from './postinstallAddon'; export interface PostinstallOptions { @@ -53,7 +57,7 @@ const requireMain = (configDir: string) => { return serverRequire(mainFile) ?? {}; }; -const checkInstalled = (addonName: string, main: any) => { +const checkInstalled = (addonName: string, main: StorybookConfigRaw) => { const existingAddon = main.addons?.find((entry: string | { name: string }) => { const name = typeof entry === 'string' ? entry : entry.name; return name?.endsWith(addonName); @@ -91,12 +95,11 @@ export async function add( const [addonName, inputVersion] = getVersionSpecifier(addon); const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr }); - const packageJson = await packageManager.retrievePackageJson(); - const { mainConfig, configDir: inferredConfigDir } = getStorybookInfo( - packageJson, - userSpecifiedConfigDir - ); - const configDir = userSpecifiedConfigDir || inferredConfigDir || '.storybook'; + const { mainConfig, mainConfigPath, configDir, previewConfigPath, storybookVersion } = + await getStorybookData({ + packageManager, + configDir: userSpecifiedConfigDir, + }); if (typeof configDir === 'undefined') { throw new Error(dedent` @@ -104,16 +107,16 @@ export async function add( `); } - if (!mainConfig) { + if (!mainConfigPath) { logger.error('Unable to find Storybook main.js config'); return; } let shouldAddToMain = true; - if (checkInstalled(addonName, requireMain(configDir))) { + if (checkInstalled(addonName, mainConfig)) { shouldAddToMain = false; if (!yes) { - logger.log(`The Storybook addon "${addonName}" is already present in ${mainConfig}.`); + logger.log(`The Storybook addon "${addonName}" is already present in ${mainConfigPath}.`); const { shouldForceInstall } = await prompts({ type: 'confirm', name: 'shouldForceInstall', @@ -126,11 +129,9 @@ export async function add( } } - const main = await readConfig(mainConfig); + const main = await readConfig(mainConfigPath); logger.log(`Verifying ${addonName}`); - const storybookVersion = await getCoercedStorybookVersion(packageManager); - let version = inputVersion; if (!version && isCoreAddon(addonName) && storybookVersion) { @@ -154,7 +155,7 @@ export async function add( await packageManager.addDependencies({ installAsDevDependencies: true }, [addonWithVersion]); if (shouldAddToMain) { - logger.log(`Adding '${addon}' to the "addons" field in ${mainConfig}`); + logger.log(`Adding '${addon}' to the "addons" field in ${mainConfigPath}`); const mainConfigAddons = main.getFieldNode(['addons']); if (mainConfigAddons && getRequireWrapperName(main) !== null) { @@ -168,6 +169,8 @@ export async function add( await writeConfig(main); } + await syncStorybookAddons(mainConfig, previewConfigPath!); + if (!skipPostinstall && isCoreAddon(addonName)) { await postinstallAddon(addonName, { packageManager: packageManager.type, configDir, yes }); } From 5c3752a7da58609901599f3745f6523f22dd28a8 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 20 Jan 2025 15:50:48 +0100 Subject: [PATCH 070/144] move defineMainConfig definition to the index level --- code/core/src/common/defineConfig.ts | 5 ----- code/core/src/common/index.ts | 1 - code/frameworks/angular/package.json | 6 ------ .../angular/src/csf-factory/defineMainConfig.ts | 7 ------- code/frameworks/angular/src/defineMainConfig.ts | 5 +++++ code/frameworks/angular/src/index.ts | 1 + code/frameworks/ember/package.json | 6 ------ .../frameworks/ember/src/csf-factory/defineMainConfig.ts | 7 ------- code/frameworks/ember/src/defineMainConfig.ts | 5 +++++ code/frameworks/ember/src/index.ts | 1 + code/frameworks/experimental-nextjs-vite/package.json | 7 ------- .../src/csf-factory/defineMainConfig.ts | 7 ------- .../experimental-nextjs-vite/src/defineMainConfig.ts | 5 +++++ code/frameworks/experimental-nextjs-vite/src/index.ts | 1 + code/frameworks/html-vite/package.json | 9 +-------- .../html-vite/src/csf-factory/defineMainConfig.ts | 7 ------- code/frameworks/html-vite/src/defineMainConfig.ts | 5 +++++ code/frameworks/html-vite/src/index.ts | 1 + code/frameworks/html-webpack5/package.json | 9 +-------- .../html-webpack5/src/csf-factory/defineMainConfig.ts | 7 ------- code/frameworks/html-webpack5/src/defineMainConfig.ts | 5 +++++ code/frameworks/html-webpack5/src/index.ts | 1 + code/frameworks/nextjs/package.json | 7 ------- .../nextjs/src/csf-factory/defineMainConfig.ts | 7 ------- code/frameworks/nextjs/src/defineMainConfig.ts | 5 +++++ code/frameworks/nextjs/src/index.ts | 1 + code/frameworks/preact-vite/package.json | 6 ------ .../preact-vite/src/csf-factory/defineMainConfig.ts | 7 ------- code/frameworks/preact-vite/src/defineMainConfig.ts | 5 +++++ code/frameworks/preact-vite/src/index.ts | 1 + code/frameworks/preact-webpack5/package.json | 9 +-------- .../preact-webpack5/src/csf-factory/defineMainConfig.ts | 7 ------- code/frameworks/preact-webpack5/src/defineMainConfig.ts | 5 +++++ code/frameworks/preact-webpack5/src/index.ts | 1 + code/frameworks/react-native-web-vite/package.json | 9 +-------- .../src/csf-factory/defineMainConfig.ts | 7 ------- .../react-native-web-vite/src/defineMainConfig.ts | 5 +++++ code/frameworks/react-native-web-vite/src/index.ts | 1 + code/frameworks/react-vite/package.json | 9 +-------- .../react-vite/src/csf-factory/defineMainConfig.ts | 7 ------- code/frameworks/react-vite/src/defineMainConfig.ts | 5 +++++ code/frameworks/react-vite/src/index.ts | 1 + code/frameworks/react-webpack5/package.json | 9 +-------- .../react-webpack5/src/csf-factory/defineMainConfig.ts | 7 ------- code/frameworks/react-webpack5/src/defineMainConfig.ts | 5 +++++ code/frameworks/react-webpack5/src/index.ts | 1 + code/frameworks/server-webpack5/package.json | 9 +-------- .../server-webpack5/src/csf-factory/defineMainConfig.ts | 7 ------- code/frameworks/server-webpack5/src/defineMainConfig.ts | 5 +++++ code/frameworks/server-webpack5/src/index.ts | 1 + code/frameworks/svelte-vite/package.json | 9 +-------- .../svelte-vite/src/csf-factory/defineMainConfig.ts | 7 ------- code/frameworks/svelte-vite/src/defineMainConfig.ts | 5 +++++ code/frameworks/svelte-vite/src/index.ts | 1 + code/frameworks/svelte-webpack5/package.json | 9 +-------- .../svelte-webpack5/src/csf-factory/defineMainConfig.ts | 7 ------- code/frameworks/svelte-webpack5/src/defineMainConfig.ts | 5 +++++ code/frameworks/svelte-webpack5/src/index.ts | 1 + code/frameworks/sveltekit/package.json | 9 +-------- .../sveltekit/src/csf-factory/defineMainConfig.ts | 7 ------- code/frameworks/sveltekit/src/defineMainConfig.ts | 5 +++++ code/frameworks/sveltekit/src/index.ts | 1 + code/frameworks/vue3-vite/package.json | 9 +-------- .../vue3-vite/src/csf-factory/defineMainConfig.ts | 7 ------- code/frameworks/vue3-vite/src/defineMainConfig.ts | 5 +++++ code/frameworks/vue3-vite/src/index.ts | 1 + code/frameworks/vue3-webpack5/package.json | 9 +-------- .../vue3-webpack5/src/csf-factory/defineMainConfig.ts | 7 ------- code/frameworks/vue3-webpack5/src/defineMainConfig.ts | 5 +++++ code/frameworks/vue3-webpack5/src/index.ts | 1 + code/frameworks/web-components-vite/package.json | 7 ------- .../src/csf-factory/defineMainConfig.ts | 7 ------- .../web-components-vite/src/defineMainConfig.ts | 5 +++++ code/frameworks/web-components-vite/src/index.ts | 1 + code/frameworks/web-components-webpack5/package.json | 9 +-------- .../src/csf-factory/defineMainConfig.ts | 7 ------- .../web-components-webpack5/src/defineMainConfig.ts | 5 +++++ code/frameworks/web-components-webpack5/src/index.ts | 1 + .../src/codemod/helpers/csf-factories-utils.ts | 1 + 79 files changed, 128 insertions(+), 282 deletions(-) delete mode 100644 code/core/src/common/defineConfig.ts delete mode 100644 code/frameworks/angular/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/angular/src/defineMainConfig.ts delete mode 100644 code/frameworks/ember/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/ember/src/defineMainConfig.ts delete mode 100644 code/frameworks/experimental-nextjs-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/experimental-nextjs-vite/src/defineMainConfig.ts delete mode 100644 code/frameworks/html-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/html-vite/src/defineMainConfig.ts delete mode 100644 code/frameworks/html-webpack5/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/html-webpack5/src/defineMainConfig.ts delete mode 100644 code/frameworks/nextjs/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/nextjs/src/defineMainConfig.ts delete mode 100644 code/frameworks/preact-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/preact-vite/src/defineMainConfig.ts delete mode 100644 code/frameworks/preact-webpack5/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/preact-webpack5/src/defineMainConfig.ts delete mode 100644 code/frameworks/react-native-web-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/react-native-web-vite/src/defineMainConfig.ts delete mode 100644 code/frameworks/react-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/react-vite/src/defineMainConfig.ts delete mode 100644 code/frameworks/react-webpack5/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/react-webpack5/src/defineMainConfig.ts delete mode 100644 code/frameworks/server-webpack5/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/server-webpack5/src/defineMainConfig.ts delete mode 100644 code/frameworks/svelte-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/svelte-vite/src/defineMainConfig.ts delete mode 100644 code/frameworks/svelte-webpack5/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/svelte-webpack5/src/defineMainConfig.ts delete mode 100644 code/frameworks/sveltekit/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/sveltekit/src/defineMainConfig.ts delete mode 100644 code/frameworks/vue3-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/vue3-vite/src/defineMainConfig.ts delete mode 100644 code/frameworks/vue3-webpack5/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/vue3-webpack5/src/defineMainConfig.ts delete mode 100644 code/frameworks/web-components-vite/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/web-components-vite/src/defineMainConfig.ts delete mode 100644 code/frameworks/web-components-webpack5/src/csf-factory/defineMainConfig.ts create mode 100644 code/frameworks/web-components-webpack5/src/defineMainConfig.ts diff --git a/code/core/src/common/defineConfig.ts b/code/core/src/common/defineConfig.ts deleted file mode 100644 index 2a16d88aa474..000000000000 --- a/code/core/src/common/defineConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from '@storybook/core/types'; - -export function defineConfig(config: TConfig): TConfig { - return config; -} diff --git a/code/core/src/common/index.ts b/code/core/src/common/index.ts index dcec4e025c53..6166e285ab05 100644 --- a/code/core/src/common/index.ts +++ b/code/core/src/common/index.ts @@ -42,7 +42,6 @@ export * from './utils/formatter'; export * from './utils/get-story-id'; export * from './utils/posix'; export * from './js-package-manager'; -export * from './defineConfig'; export { versions }; diff --git a/code/frameworks/angular/package.json b/code/frameworks/angular/package.json index 2c71784ede70..bc7162bad711 100644 --- a/code/frameworks/angular/package.json +++ b/code/frameworks/angular/package.json @@ -28,12 +28,6 @@ "require": "./dist/index.js" }, "./preset": "./preset.js", - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.js", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", diff --git a/code/frameworks/angular/src/csf-factory/defineMainConfig.ts b/code/frameworks/angular/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index 24bc05422ac9..000000000000 --- a/code/frameworks/angular/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/angular/src/defineMainConfig.ts b/code/frameworks/angular/src/defineMainConfig.ts new file mode 100644 index 000000000000..e0bb43b41ff9 --- /dev/null +++ b/code/frameworks/angular/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/angular/src/index.ts b/code/frameworks/angular/src/index.ts index 92f1fac0da0f..d57916a453f4 100644 --- a/code/frameworks/angular/src/index.ts +++ b/code/frameworks/angular/src/index.ts @@ -1,5 +1,6 @@ export * from './client/index'; export * from './types'; +export * from './defineMainConfig'; /* * ATTENTION: diff --git a/code/frameworks/ember/package.json b/code/frameworks/ember/package.json index 94b6468b39f8..f4b021d13e8e 100644 --- a/code/frameworks/ember/package.json +++ b/code/frameworks/ember/package.json @@ -24,12 +24,6 @@ "require": "./dist/index.js" }, "./preset": "./preset.js", - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.js", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", diff --git a/code/frameworks/ember/src/csf-factory/defineMainConfig.ts b/code/frameworks/ember/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/ember/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/ember/src/defineMainConfig.ts b/code/frameworks/ember/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/ember/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/ember/src/index.ts b/code/frameworks/ember/src/index.ts index b821bec98227..bdebdaee4b44 100644 --- a/code/frameworks/ember/src/index.ts +++ b/code/frameworks/ember/src/index.ts @@ -1,6 +1,7 @@ /// export * from './types'; +export * from './defineMainConfig'; // optimization: stop HMR propagation in webpack diff --git a/code/frameworks/experimental-nextjs-vite/package.json b/code/frameworks/experimental-nextjs-vite/package.json index 4b20e1113092..b5205345ed72 100644 --- a/code/frameworks/experimental-nextjs-vite/package.json +++ b/code/frameworks/experimental-nextjs-vite/package.json @@ -58,12 +58,6 @@ "import": "./dist/vite-plugin/index.mjs", "require": "./dist/vite-plugin/index.js" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -140,7 +134,6 @@ "./src/index.ts", "./src/vite-plugin/index.ts", "./src/preset.ts", - "./src/csf-factory/defineMainConfig.ts", "./src/preview.tsx", "./src/export-mocks/cache/index.ts", "./src/export-mocks/headers/index.ts", diff --git a/code/frameworks/experimental-nextjs-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/experimental-nextjs-vite/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/experimental-nextjs-vite/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/experimental-nextjs-vite/src/defineMainConfig.ts b/code/frameworks/experimental-nextjs-vite/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/experimental-nextjs-vite/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/experimental-nextjs-vite/src/index.ts b/code/frameworks/experimental-nextjs-vite/src/index.ts index 32476387c88c..af5050bce57f 100644 --- a/code/frameworks/experimental-nextjs-vite/src/index.ts +++ b/code/frameworks/experimental-nextjs-vite/src/index.ts @@ -1,6 +1,7 @@ import type vitePluginStorybookNextJs from 'vite-plugin-storybook-nextjs'; export * from './types'; +export * from './defineMainConfig'; export * from './portable-stories'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/code/frameworks/html-vite/package.json b/code/frameworks/html-vite/package.json index 1ce79c846355..804987dac1c7 100644 --- a/code/frameworks/html-vite/package.json +++ b/code/frameworks/html-vite/package.json @@ -30,12 +30,6 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -73,8 +67,7 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts", - "./src/csf-factory/defineMainConfig.ts" + "./src/preset.ts" ], "platform": "node" }, diff --git a/code/frameworks/html-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/html-vite/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/html-vite/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/html-vite/src/defineMainConfig.ts b/code/frameworks/html-vite/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/html-vite/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/html-vite/src/index.ts b/code/frameworks/html-vite/src/index.ts index fcb073fefcd6..bb8ba9918a0d 100644 --- a/code/frameworks/html-vite/src/index.ts +++ b/code/frameworks/html-vite/src/index.ts @@ -1 +1,2 @@ export * from './types'; +export * from './defineMainConfig'; diff --git a/code/frameworks/html-webpack5/package.json b/code/frameworks/html-webpack5/package.json index 8a7167dd0f54..03cd0ce7cfc2 100644 --- a/code/frameworks/html-webpack5/package.json +++ b/code/frameworks/html-webpack5/package.json @@ -30,12 +30,6 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -74,8 +68,7 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts", - "./src/csf-factory/defineMainConfig.ts" + "./src/preset.ts" ], "platform": "node" }, diff --git a/code/frameworks/html-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/html-webpack5/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/html-webpack5/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/html-webpack5/src/defineMainConfig.ts b/code/frameworks/html-webpack5/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/html-webpack5/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/html-webpack5/src/index.ts b/code/frameworks/html-webpack5/src/index.ts index fcb073fefcd6..bb8ba9918a0d 100644 --- a/code/frameworks/html-webpack5/src/index.ts +++ b/code/frameworks/html-webpack5/src/index.ts @@ -1 +1,2 @@ export * from './types'; +export * from './defineMainConfig'; diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index 41dea2891772..a440c6b17a17 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -87,12 +87,6 @@ "import": "./dist/export-mocks/router/index.mjs", "require": "./dist/export-mocks/router/index.js" }, - "./main": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -215,7 +209,6 @@ "./src/image-context.ts", "./src/index.ts", "./src/preset.ts", - "./src/csf-factory/defineMainConfig.ts", "./src/preview.tsx", "./src/export-mocks/index.ts", "./src/export-mocks/cache/index.ts", diff --git a/code/frameworks/nextjs/src/csf-factory/defineMainConfig.ts b/code/frameworks/nextjs/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/nextjs/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/nextjs/src/defineMainConfig.ts b/code/frameworks/nextjs/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/nextjs/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/nextjs/src/index.ts b/code/frameworks/nextjs/src/index.ts index a904f93ec89d..4e3e0a0ec393 100644 --- a/code/frameworks/nextjs/src/index.ts +++ b/code/frameworks/nextjs/src/index.ts @@ -1,2 +1,3 @@ export * from './types'; +export * from './defineMainConfig'; export * from './portable-stories'; diff --git a/code/frameworks/preact-vite/package.json b/code/frameworks/preact-vite/package.json index e639c1c7e0bb..5b146f3a9bbf 100644 --- a/code/frameworks/preact-vite/package.json +++ b/code/frameworks/preact-vite/package.json @@ -29,12 +29,6 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", diff --git a/code/frameworks/preact-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/preact-vite/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/preact-vite/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/preact-vite/src/defineMainConfig.ts b/code/frameworks/preact-vite/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/preact-vite/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/preact-vite/src/index.ts b/code/frameworks/preact-vite/src/index.ts index fcb073fefcd6..bb8ba9918a0d 100644 --- a/code/frameworks/preact-vite/src/index.ts +++ b/code/frameworks/preact-vite/src/index.ts @@ -1 +1,2 @@ export * from './types'; +export * from './defineMainConfig'; diff --git a/code/frameworks/preact-webpack5/package.json b/code/frameworks/preact-webpack5/package.json index cd7effb465d0..5f0290ffcebc 100644 --- a/code/frameworks/preact-webpack5/package.json +++ b/code/frameworks/preact-webpack5/package.json @@ -30,12 +30,6 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -75,8 +69,7 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts", - "./src/csf-factory/defineMainConfig.ts" + "./src/preset.ts" ], "platform": "node" }, diff --git a/code/frameworks/preact-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/preact-webpack5/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/preact-webpack5/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/preact-webpack5/src/defineMainConfig.ts b/code/frameworks/preact-webpack5/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/preact-webpack5/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/preact-webpack5/src/index.ts b/code/frameworks/preact-webpack5/src/index.ts index fcb073fefcd6..bb8ba9918a0d 100644 --- a/code/frameworks/preact-webpack5/src/index.ts +++ b/code/frameworks/preact-webpack5/src/index.ts @@ -1 +1,2 @@ export * from './types'; +export * from './defineMainConfig'; diff --git a/code/frameworks/react-native-web-vite/package.json b/code/frameworks/react-native-web-vite/package.json index 8f392bc254dd..da31ba3bca39 100644 --- a/code/frameworks/react-native-web-vite/package.json +++ b/code/frameworks/react-native-web-vite/package.json @@ -35,12 +35,6 @@ "import": "./dist/vite-plugin.mjs", "require": "./dist/vite-plugin.js" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -92,8 +86,7 @@ "entries": [ "./src/index.ts", "./src/preset.ts", - "./src/vite-plugin.ts", - "./src/csf-factory/defineMainConfig.ts" + "./src/vite-plugin.ts" ], "platform": "node" }, diff --git a/code/frameworks/react-native-web-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/react-native-web-vite/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/react-native-web-vite/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/react-native-web-vite/src/defineMainConfig.ts b/code/frameworks/react-native-web-vite/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/react-native-web-vite/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/react-native-web-vite/src/index.ts b/code/frameworks/react-native-web-vite/src/index.ts index 1855ad61a70b..c316aff16a2c 100644 --- a/code/frameworks/react-native-web-vite/src/index.ts +++ b/code/frameworks/react-native-web-vite/src/index.ts @@ -1 +1,2 @@ export type { FrameworkOptions, StorybookConfig } from './types'; +export * from './defineMainConfig'; diff --git a/code/frameworks/react-vite/package.json b/code/frameworks/react-vite/package.json index 6228aea36f4c..d871585cf42c 100644 --- a/code/frameworks/react-vite/package.json +++ b/code/frameworks/react-vite/package.json @@ -30,12 +30,6 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -99,8 +93,7 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts", - "./src/csf-factory/defineMainConfig.ts" + "./src/preset.ts" ], "platform": "node" }, diff --git a/code/frameworks/react-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/react-vite/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/react-vite/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/react-vite/src/defineMainConfig.ts b/code/frameworks/react-vite/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/react-vite/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/react-vite/src/index.ts b/code/frameworks/react-vite/src/index.ts index fcb073fefcd6..bb8ba9918a0d 100644 --- a/code/frameworks/react-vite/src/index.ts +++ b/code/frameworks/react-vite/src/index.ts @@ -1 +1,2 @@ export * from './types'; +export * from './defineMainConfig'; diff --git a/code/frameworks/react-webpack5/package.json b/code/frameworks/react-webpack5/package.json index 8c26cc0072aa..c6c05892a81f 100644 --- a/code/frameworks/react-webpack5/package.json +++ b/code/frameworks/react-webpack5/package.json @@ -30,12 +30,6 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -80,8 +74,7 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts", - "./src/csf-factory/defineMainConfig.ts" + "./src/preset.ts" ], "platform": "node" }, diff --git a/code/frameworks/react-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/react-webpack5/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/react-webpack5/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/react-webpack5/src/defineMainConfig.ts b/code/frameworks/react-webpack5/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/react-webpack5/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/react-webpack5/src/index.ts b/code/frameworks/react-webpack5/src/index.ts index fcb073fefcd6..bb8ba9918a0d 100644 --- a/code/frameworks/react-webpack5/src/index.ts +++ b/code/frameworks/react-webpack5/src/index.ts @@ -1 +1,2 @@ export * from './types'; +export * from './defineMainConfig'; diff --git a/code/frameworks/server-webpack5/package.json b/code/frameworks/server-webpack5/package.json index 1f61cce000ec..ef6b005495c5 100644 --- a/code/frameworks/server-webpack5/package.json +++ b/code/frameworks/server-webpack5/package.json @@ -30,12 +30,6 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -73,8 +67,7 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts", - "./src/csf-factory/defineMainConfig.ts" + "./src/preset.ts" ], "platform": "node" }, diff --git a/code/frameworks/server-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/server-webpack5/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/server-webpack5/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/server-webpack5/src/defineMainConfig.ts b/code/frameworks/server-webpack5/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/server-webpack5/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/server-webpack5/src/index.ts b/code/frameworks/server-webpack5/src/index.ts index fcb073fefcd6..bb8ba9918a0d 100644 --- a/code/frameworks/server-webpack5/src/index.ts +++ b/code/frameworks/server-webpack5/src/index.ts @@ -1 +1,2 @@ export * from './types'; +export * from './defineMainConfig'; diff --git a/code/frameworks/svelte-vite/package.json b/code/frameworks/svelte-vite/package.json index b82e3b98b28b..5007a4d2ff35 100644 --- a/code/frameworks/svelte-vite/package.json +++ b/code/frameworks/svelte-vite/package.json @@ -30,12 +30,6 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -84,8 +78,7 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts", - "./src/csf-factory/defineMainConfig.ts" + "./src/preset.ts" ], "platform": "node" }, diff --git a/code/frameworks/svelte-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/svelte-vite/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/svelte-vite/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/svelte-vite/src/defineMainConfig.ts b/code/frameworks/svelte-vite/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/svelte-vite/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/svelte-vite/src/index.ts b/code/frameworks/svelte-vite/src/index.ts index fcb073fefcd6..bb8ba9918a0d 100644 --- a/code/frameworks/svelte-vite/src/index.ts +++ b/code/frameworks/svelte-vite/src/index.ts @@ -1 +1,2 @@ export * from './types'; +export * from './defineMainConfig'; diff --git a/code/frameworks/svelte-webpack5/package.json b/code/frameworks/svelte-webpack5/package.json index f483c25bf7e3..78b652a38c07 100644 --- a/code/frameworks/svelte-webpack5/package.json +++ b/code/frameworks/svelte-webpack5/package.json @@ -30,12 +30,6 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -76,8 +70,7 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts", - "./src/csf-factory/defineMainConfig.ts" + "./src/preset.ts" ], "platform": "node" }, diff --git a/code/frameworks/svelte-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/svelte-webpack5/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/svelte-webpack5/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/svelte-webpack5/src/defineMainConfig.ts b/code/frameworks/svelte-webpack5/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/svelte-webpack5/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/svelte-webpack5/src/index.ts b/code/frameworks/svelte-webpack5/src/index.ts index fcb073fefcd6..bb8ba9918a0d 100644 --- a/code/frameworks/svelte-webpack5/src/index.ts +++ b/code/frameworks/svelte-webpack5/src/index.ts @@ -1 +1,2 @@ export * from './types'; +export * from './defineMainConfig'; diff --git a/code/frameworks/sveltekit/package.json b/code/frameworks/sveltekit/package.json index b47ab7a26343..6165990d8b1e 100644 --- a/code/frameworks/sveltekit/package.json +++ b/code/frameworks/sveltekit/package.json @@ -41,12 +41,6 @@ "require": "./dist/vite-plugin.js", "import": "./dist/vite-plugin.mjs" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -90,8 +84,7 @@ "./src/index.ts", "./src/preview.ts", "./src/preset.ts", - "./src/vite-plugin.ts", - "./src/csf-factory/defineMainConfig.ts" + "./src/vite-plugin.ts" ], "platform": "node" }, diff --git a/code/frameworks/sveltekit/src/csf-factory/defineMainConfig.ts b/code/frameworks/sveltekit/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/sveltekit/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/sveltekit/src/defineMainConfig.ts b/code/frameworks/sveltekit/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/sveltekit/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/sveltekit/src/index.ts b/code/frameworks/sveltekit/src/index.ts index a904f93ec89d..4e3e0a0ec393 100644 --- a/code/frameworks/sveltekit/src/index.ts +++ b/code/frameworks/sveltekit/src/index.ts @@ -1,2 +1,3 @@ export * from './types'; +export * from './defineMainConfig'; export * from './portable-stories'; diff --git a/code/frameworks/vue3-vite/package.json b/code/frameworks/vue3-vite/package.json index 2852d303990b..8d9b0f427a6a 100644 --- a/code/frameworks/vue3-vite/package.json +++ b/code/frameworks/vue3-vite/package.json @@ -35,12 +35,6 @@ "require": "./dist/vite-plugin.js", "import": "./dist/vite-plugin.mjs" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -86,8 +80,7 @@ "entries": [ "./src/index.ts", "./src/preset.ts", - "./src/vite-plugin.ts", - "./src/csf-factory/defineMainConfig.ts" + "./src/vite-plugin.ts" ], "platform": "node" }, diff --git a/code/frameworks/vue3-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/vue3-vite/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/vue3-vite/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/vue3-vite/src/defineMainConfig.ts b/code/frameworks/vue3-vite/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/vue3-vite/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/vue3-vite/src/index.ts b/code/frameworks/vue3-vite/src/index.ts index fcb073fefcd6..bb8ba9918a0d 100644 --- a/code/frameworks/vue3-vite/src/index.ts +++ b/code/frameworks/vue3-vite/src/index.ts @@ -1 +1,2 @@ export * from './types'; +export * from './defineMainConfig'; diff --git a/code/frameworks/vue3-webpack5/package.json b/code/frameworks/vue3-webpack5/package.json index 973f16e8a820..b08859e0a714 100644 --- a/code/frameworks/vue3-webpack5/package.json +++ b/code/frameworks/vue3-webpack5/package.json @@ -30,12 +30,6 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -77,8 +71,7 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts", - "./src/csf-factory/defineMainConfig.ts" + "./src/preset.ts" ], "platform": "node" }, diff --git a/code/frameworks/vue3-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/vue3-webpack5/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/vue3-webpack5/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/vue3-webpack5/src/defineMainConfig.ts b/code/frameworks/vue3-webpack5/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/vue3-webpack5/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/vue3-webpack5/src/index.ts b/code/frameworks/vue3-webpack5/src/index.ts index fcb073fefcd6..bb8ba9918a0d 100644 --- a/code/frameworks/vue3-webpack5/src/index.ts +++ b/code/frameworks/vue3-webpack5/src/index.ts @@ -1 +1,2 @@ export * from './types'; +export * from './defineMainConfig'; diff --git a/code/frameworks/web-components-vite/package.json b/code/frameworks/web-components-vite/package.json index 27b17e55155e..92a76784a0aa 100644 --- a/code/frameworks/web-components-vite/package.json +++ b/code/frameworks/web-components-vite/package.json @@ -30,12 +30,6 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -72,7 +66,6 @@ }, "bundler": { "entries": [ - "./src/csf-factory/defineMainConfig.ts", "./src/index.ts", "./src/preset.ts" ], diff --git a/code/frameworks/web-components-vite/src/csf-factory/defineMainConfig.ts b/code/frameworks/web-components-vite/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/web-components-vite/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/web-components-vite/src/defineMainConfig.ts b/code/frameworks/web-components-vite/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/web-components-vite/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/web-components-vite/src/index.ts b/code/frameworks/web-components-vite/src/index.ts index fcb073fefcd6..bb8ba9918a0d 100644 --- a/code/frameworks/web-components-vite/src/index.ts +++ b/code/frameworks/web-components-vite/src/index.ts @@ -1 +1,2 @@ export * from './types'; +export * from './defineMainConfig'; diff --git a/code/frameworks/web-components-webpack5/package.json b/code/frameworks/web-components-webpack5/package.json index 2e9b89f3223c..aa4281ef433e 100644 --- a/code/frameworks/web-components-webpack5/package.json +++ b/code/frameworks/web-components-webpack5/package.json @@ -33,12 +33,6 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, - "./node": { - "types": "./dist/csf-factory/defineMainConfig.d.ts", - "node": "./dist/csf-factory/defineMainConfig.js", - "import": "./dist/csf-factory/defineMainConfig.mjs", - "require": "./dist/csf-factory/defineMainConfig.js" - }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -77,8 +71,7 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts", - "./src/csf-factory/defineMainConfig.ts" + "./src/preset.ts" ], "platform": "node" }, diff --git a/code/frameworks/web-components-webpack5/src/csf-factory/defineMainConfig.ts b/code/frameworks/web-components-webpack5/src/csf-factory/defineMainConfig.ts deleted file mode 100644 index dd999c40b17b..000000000000 --- a/code/frameworks/web-components-webpack5/src/csf-factory/defineMainConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig as commonDefineConfig } from 'storybook/internal/common'; - -import type { StorybookConfig } from '../types'; - -export function defineMain(config: StorybookConfig) { - return commonDefineConfig(config); -} diff --git a/code/frameworks/web-components-webpack5/src/defineMainConfig.ts b/code/frameworks/web-components-webpack5/src/defineMainConfig.ts new file mode 100644 index 000000000000..29be946269ac --- /dev/null +++ b/code/frameworks/web-components-webpack5/src/defineMainConfig.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from './types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/web-components-webpack5/src/index.ts b/code/frameworks/web-components-webpack5/src/index.ts index fcb073fefcd6..bb8ba9918a0d 100644 --- a/code/frameworks/web-components-webpack5/src/index.ts +++ b/code/frameworks/web-components-webpack5/src/index.ts @@ -1 +1,2 @@ export * from './types'; +export * from './defineMainConfig'; diff --git a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts index 7a89afabdecf..42bbc3b75abe 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts @@ -83,6 +83,7 @@ export async function getSyncedStorybookAddons( */ addons.forEach(async (addon) => { const annotations = await getAddonAnnotations(addon); + console.log(); if (annotations) { previewConfig.setImport({ namespace: annotations.importName }, annotations.importPath); const existingAddons = previewConfig.getFieldNode(['addons']); From 04d9f58cbda25029dce44081a2846c2af2c91bab Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 20 Jan 2025 17:47:56 +0100 Subject: [PATCH 071/144] modify preview/main config format in codemods/csf file --- code/.storybook/main.ts | 6 +- code/.storybook/preview.tsx | 4 +- code/core/src/csf-tools/ConfigFile.test.ts | 61 +++--- code/core/src/csf-tools/ConfigFile.ts | 2 +- .../template/stories/csf4.stories.tsx | 5 +- .../helpers/config-to-csf-factory.test.ts | 16 +- .../codemod/helpers/config-to-csf-factory.ts | 40 +++- .../helpers/csf-factories-utils.test.ts | 31 ++- .../codemod/helpers/csf-factories-utils.ts | 13 +- .../helpers/story-to-csf-factory.test.ts | 190 ++++++++++-------- .../codemod/helpers/story-to-csf-factory.ts | 18 +- .../src/__test__/Button.csf4.stories.tsx | 4 +- .../react/src/csf-factories.test.tsx | 4 +- code/renderers/react/src/preview.tsx | 2 +- 14 files changed, 236 insertions(+), 160 deletions(-) diff --git a/code/.storybook/main.ts b/code/.storybook/main.ts index 48109681b6b0..371aa942f5f3 100644 --- a/code/.storybook/main.ts +++ b/code/.storybook/main.ts @@ -1,12 +1,12 @@ import { join } from 'node:path'; -import type { StorybookConfig } from '../frameworks/react-vite'; +import { defineMain } from '../frameworks/react-vite'; const componentsPath = join(__dirname, '../core/src/components'); const managerApiPath = join(__dirname, '../core/src/manager-api'); const imageContextPath = join(__dirname, '..//frameworks/nextjs/src/image-context.ts'); -const config: StorybookConfig = { +const config = defineMain({ stories: [ './*.stories.@(js|jsx|ts|tsx)', { @@ -169,6 +169,6 @@ const config: StorybookConfig = { } satisfies typeof viteConfig); }, // logLevel: 'debug', -}; +}); export default config; diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index 2437d15e4a39..a4f48da6580f 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -18,7 +18,7 @@ import { import { DocsContext } from '@storybook/blocks'; import { global } from '@storybook/global'; import type { Decorator, Loader, ReactRenderer } from '@storybook/react'; -import { defineConfig } from '@storybook/react/preview'; +import { definePreview } from '@storybook/react/preview'; // TODO add empty preview // import * as storysource from '@storybook/addon-storysource'; @@ -375,7 +375,7 @@ const parameters = { }, }; -export const config = defineConfig({ +export const config = definePreview({ addons: [addonThemes, essentials, a11y, addonsPreview, templatePreview], parameters, decorators, diff --git a/code/core/src/csf-tools/ConfigFile.test.ts b/code/core/src/csf-tools/ConfigFile.test.ts index b4c3895538f8..e6739e8a27ec 100644 --- a/code/core/src/csf-tools/ConfigFile.test.ts +++ b/code/core/src/csf-tools/ConfigFile.test.ts @@ -247,9 +247,9 @@ describe('ConfigFile', () => { describe('factory config', () => { it('parses correctly', () => { const source = dedent` - import { defineConfig } from '@storybook/react-vite/browser'; + import { definePreview } from '@storybook/react-vite/preview'; - const config = defineConfig({ + const config = definePreview({ framework: 'foo', }); export default config; @@ -262,20 +262,20 @@ describe('ConfigFile', () => { getField( ['core', 'builder'], dedent` - import { defineConfig } from '@storybook/react-vite/preview'; - export const foo = defineConfig({ core: { builder: 'webpack5' } }); + import { definePreview } from '@storybook/react-vite/preview'; + export const foo = definePreview({ core: { builder: 'webpack5' } }); ` ) ).toEqual('webpack5'); }); - it('tags', () => { + it.only('tags', () => { expect( getField( ['tags'], dedent` - import { defineConfig } from '@storybook/react-vite/preview'; + import { definePreview } from '@storybook/react-vite/preview'; const parameters = {}; - export const config = defineConfig({ + export const config = definePreview({ parameters, tags: ['test', 'vitest', '!a11ytest'], }); @@ -528,21 +528,21 @@ describe('ConfigFile', () => { ['core', 'builder'], 'webpack5', dedent` - import { defineConfig } from '@storybook/react-vite/preview'; - export const foo = defineConfig({ + import { definePreview } from '@storybook/react-vite/preview'; + export const foo = definePreview({ addons: [], }); ` ) ).toMatchInlineSnapshot(` - import { defineConfig } from '@storybook/react-vite/preview'; - export const foo = defineConfig({ + import { definePreview } from '@storybook/react-vite/preview'; + export const foo = definePreview({ addons: [], - - core: { - builder: 'webpack5' - } }); + + export const core = { + builder: 'webpack5' + }; `); }); it('missing field', () => { @@ -551,20 +551,21 @@ describe('ConfigFile', () => { ['core', 'builder'], 'webpack5', dedent` - import { defineConfig } from '@storybook/react-vite/preview'; - export const foo = defineConfig({ + import { definePreview } from '@storybook/react-vite/preview'; + export const foo = definePreview({ core: { foo: 'bar' }, }); ` ) ).toMatchInlineSnapshot(` - import { defineConfig } from '@storybook/react-vite/preview'; - export const foo = defineConfig({ - core: { - foo: 'bar', - builder: 'webpack5' - }, + import { definePreview } from '@storybook/react-vite/preview'; + export const foo = definePreview({ + core: { foo: 'bar' }, }); + + export const core = { + builder: 'webpack5' + }; `); }); it('found scalar', () => { @@ -573,17 +574,21 @@ describe('ConfigFile', () => { ['core', 'builder'], 'webpack5', dedent` - import { defineConfig } from '@storybook/react-vite/preview'; - export const foo = defineConfig({ + import { definePreview } from '@storybook/react-vite/preview'; + export const foo = definePreview({ core: { builder: 'webpack4' }, }); ` ) ).toMatchInlineSnapshot(` - import { defineConfig } from '@storybook/react-vite/preview'; - export const foo = defineConfig({ - core: { builder: 'webpack5' }, + import { definePreview } from '@storybook/react-vite/preview'; + export const foo = definePreview({ + core: { builder: 'webpack4' }, }); + + export const core = { + builder: 'webpack5' + }; `); }); }); diff --git a/code/core/src/csf-tools/ConfigFile.ts b/code/core/src/csf-tools/ConfigFile.ts index 7ea42448dfc6..d1aecfb7ae63 100644 --- a/code/core/src/csf-tools/ConfigFile.ts +++ b/code/core/src/csf-tools/ConfigFile.ts @@ -318,7 +318,7 @@ export class ConfigFile { enter: ({ node }) => { if ( t.isIdentifier(node.callee) && - node.callee.name === 'defineConfig' && + node.callee.name === 'definePreview' && node.arguments.length === 1 && t.isObjectExpression(node.arguments[0]) ) { diff --git a/code/frameworks/react-vite/template/stories/csf4.stories.tsx b/code/frameworks/react-vite/template/stories/csf4.stories.tsx index 012c356881c6..4bb4a60cbf35 100644 --- a/code/frameworks/react-vite/template/stories/csf4.stories.tsx +++ b/code/frameworks/react-vite/template/stories/csf4.stories.tsx @@ -1,6 +1,5 @@ -import React from 'react'; - -import { config } from '#.storybook/preview'; +// @ts-expect-error this will be part of the package.json of the sandbox +import config from '#.storybook/preview'; const meta = config.meta({ component: globalThis.Components.Button, diff --git a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts index 847368f71e15..ee0a31900810 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts @@ -28,7 +28,7 @@ describe('main/preview codemod: general parsing functionality', () => { }; `) ).resolves.toMatchInlineSnapshot(` - import { defineMain } from '@storybook/react-vite/node'; + import { defineMain } from '@storybook/react-vite'; export default defineMain({ stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], @@ -49,7 +49,7 @@ describe('main/preview codemod: general parsing functionality', () => { export default config; `) ).resolves.toMatchInlineSnapshot(` - import { defineMain } from '@storybook/react-vite/node'; + import { defineMain } from '@storybook/react-vite'; export default defineMain({ stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], @@ -69,7 +69,7 @@ describe('main/preview codemod: general parsing functionality', () => { export default config; `) ).resolves.toMatchInlineSnapshot(` - import { defineMain } from '@storybook/react-vite/node'; + import { defineMain } from '@storybook/react-vite'; const config = { framework: '@storybook/react-vite', @@ -87,7 +87,7 @@ describe('main/preview codemod: general parsing functionality', () => { export const framework = '@storybook/react-vite'; `) ).resolves.toMatchInlineSnapshot(` - import { defineMain } from '@storybook/react-vite/node'; + import { defineMain } from '@storybook/react-vite'; export default defineMain({ stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], @@ -111,7 +111,7 @@ describe('main/preview codemod: general parsing functionality', () => { it('should remove legacy main config type imports', async () => { await expect( transform(dedent` - import type { StorybookConfig } from '@storybook/react' + import { type StorybookConfig } from '@storybook/react-vite' const config: StorybookConfig = { stories: [] @@ -119,7 +119,7 @@ describe('main/preview codemod: general parsing functionality', () => { export default config; `) ).resolves.toMatchInlineSnapshot(` - import { defineMain } from '@storybook/react-vite/node'; + import { defineMain } from '@storybook/react-vite'; export default defineMain({ stories: [], @@ -145,7 +145,7 @@ describe('preview specific functionality', () => { }; `) ).resolves.toMatchInlineSnapshot(` - import { definePreview } from '@storybook/react-vite/browser'; + import { definePreview } from '@storybook/react/preview'; export default definePreview({ tags: ['test'], @@ -164,7 +164,7 @@ describe('preview specific functionality', () => { export default preview; `) ).resolves.toMatchInlineSnapshot(` - import { definePreview } from '@storybook/react-vite/browser'; + import { definePreview } from '@storybook/react/preview'; export default definePreview({ tags: [], diff --git a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts index ea5bf4f5e93c..65bc136af80c 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts @@ -23,6 +23,12 @@ export async function configToCsfFactory( } const methodName = configType === 'main' ? 'defineMain' : 'definePreview'; + // TODO: remove this later, it's just a quick workaround for preview imports + // while it is part of @storybook/react and not @storybook/react-vite + frameworkPackage = + configType === 'preview' && frameworkPackage === '@storybook/react-vite' + ? '@storybook/react' + : frameworkPackage; const programNode = config._ast.program; const hasNamedExports = Object.keys(config._exportDecls).length > 0; @@ -151,14 +157,34 @@ export async function configToCsfFactory( const configImport = t.importDeclaration( [t.importSpecifier(t.identifier(methodName), t.identifier(methodName))], - t.stringLiteral(frameworkPackage + `/${configType === 'main' ? 'node' : 'browser'}`) + t.stringLiteral(frameworkPackage + `${configType === 'preview' ? '/preview' : ''}`) ); - // only add the import if it doesn't exist yet - if ( - !programNode.body.some( - (node) => t.isImportDeclaration(node) && node.source.value === configImport.source.value - ) - ) { + + // Check whether @storybook/framework import already exists + const existingImport = programNode.body.find( + (node) => + t.isImportDeclaration(node) && + node.source.value === configImport.source.value && + !node.importKind + ); + + if (existingImport && t.isImportDeclaration(existingImport)) { + // If it does, check whether defineMain/definePreview is already imported + // and only add it if it's not + const hasMethodName = existingImport.specifiers.some( + (specifier) => + t.isImportSpecifier(specifier) && + t.isIdentifier(specifier.imported) && + specifier.imported.name === methodName + ); + + if (!hasMethodName) { + existingImport.specifiers.push( + t.importSpecifier(t.identifier(methodName), t.identifier(methodName)) + ); + } + } else { + // if not, add import { defineMain } from '@storybook/framework' programNode.body.unshift(configImport); } diff --git a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts index 27e065c86455..1eeb7f6282f7 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts @@ -25,7 +25,7 @@ describe('getSyncedStorybookAddons', () => { it('should sync addons between main and preview', async () => { const preview = loadConfig(` import * as myAddonAnnotations from "custom-addon/preview"; - import { definePreview } from "@storybook/react-vite/browser"; + import { definePreview } from "@storybook/react/preview"; export default definePreview({ addons: [myAddonAnnotations], @@ -40,18 +40,43 @@ describe('getSyncedStorybookAddons', () => { expect(printConfig(result).code).toMatchInlineSnapshot(` import * as addonA11yAnnotations from "@storybook/addon-a11y/preview"; import * as myAddonAnnotations from "custom-addon/preview"; - import { definePreview } from "@storybook/react-vite/browser"; + import { definePreview } from "@storybook/react/preview"; export default definePreview({ addons: [myAddonAnnotations, addonA11yAnnotations], }); `); }); + it.only('should add addons if the preview has no addons field', async () => { + const originalCode = ` + import { definePreview } from "@storybook/react/preview"; + + export default definePreview({ + tags: [] + }); + `; + const preview = loadConfig(originalCode).parse(); + + (getAddonAnnotations as Mock).mockImplementation(() => { + return { importName: 'addonA11yAnnotations', importPath: '@storybook/addon-a11y/preview' }; + }); + + const result = await getSyncedStorybookAddons(mainConfig, preview); + expect(printConfig(result).code).toMatchInlineSnapshot(` + import * as addonA11yAnnotations from "@storybook/addon-a11y/preview"; + import { definePreview } from "@storybook/react/preview"; + + export default definePreview({ + tags: [], + addons: [addonA11yAnnotations] + }); + `); + }); it('should not modify the code if all addons are already synced', async () => { const originalCode = ` import * as addonA11yAnnotations from "@storybook/addon-a11y/preview"; import * as myAddonAnnotations from "custom-addon/preview"; - import { definePreview } from "@storybook/react-vite/browser"; + import { definePreview } from "@storybook/react/preview"; export default definePreview({ addons: [myAddonAnnotations, addonA11yAnnotations], diff --git a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts index 42bbc3b75abe..c01ade0954c3 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts @@ -59,7 +59,7 @@ export async function getSyncedStorybookAddons( return ( t.isImportDeclaration(node) && node.source.value.includes('@storybook') && - node.source.value.endsWith('/browser') && + node.source.value.endsWith('/preview') && node.specifiers.some((specifier) => { return ( t.isImportSpecifier(specifier) && @@ -83,15 +83,16 @@ export async function getSyncedStorybookAddons( */ addons.forEach(async (addon) => { const annotations = await getAddonAnnotations(addon); - console.log(); if (annotations) { previewConfig.setImport({ namespace: annotations.importName }, annotations.importPath); const existingAddons = previewConfig.getFieldNode(['addons']); + if ( - t.isArrayExpression(existingAddons) && - !existingAddons.elements.some( - (element) => t.isIdentifier(element) && element.name === annotations.importName - ) + !existingAddons || + (t.isArrayExpression(existingAddons) && + !existingAddons.elements.some( + (element) => t.isIdentifier(element) && element.name === annotations.importName + )) ) { previewConfig.appendNodeToArray(['addons'], t.identifier(annotations.importName)); } diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts index 3a98dbeece0b..ea19c007f7f8 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts @@ -25,10 +25,10 @@ describe('stories codemod', () => { export default meta; `) ).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; + import config from '#.storybook/preview'; - const meta = config.meta({ title: 'Component' }); - `); + const meta = config.meta({ title: 'Component' }); + `); }); it('should transform and wrap inline default exported meta', async () => { @@ -37,12 +37,12 @@ describe('stories codemod', () => { export default { title: 'Component' }; `) ).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; + import config from '#.storybook/preview'; - const meta = config.meta({ - title: 'Component', - }); - `); + const meta = config.meta({ + title: 'Component', + }); + `); }); it('should rename meta object to meta if it has a different name', async () => { @@ -52,10 +52,10 @@ describe('stories codemod', () => { export default componentMeta; `) ).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; + import config from '#.storybook/preview'; - const meta = config.meta({ title: 'Component' }); - `); + const meta = config.meta({ title: 'Component' }); + `); }); it('should wrap stories in a meta.story method', async () => { @@ -69,14 +69,14 @@ describe('stories codemod', () => { }; `) ).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; - - const meta = config.meta({ title: 'Component' }); - export const A = meta.story({ - args: { primary: true }, - render: (args) => , - }); - `); + import config from '#.storybook/preview'; + + const meta = config.meta({ title: 'Component' }); + export const A = meta.story({ + args: { primary: true }, + render: (args) => , + }); + `); }); it('should respect existing config imports', async () => { @@ -91,14 +91,36 @@ describe('stories codemod', () => { }; `) ).resolves.toMatchInlineSnapshot(` - import { config, decorators } from '#.storybook/preview'; - - const meta = config.meta({ title: 'Component' }); - export const A = meta.story({ - args: { primary: true }, - render: (args) => , - }); - `); + import config, { decorators } from '#.storybook/preview'; + + const meta = config.meta({ title: 'Component' }); + export const A = meta.story({ + args: { primary: true }, + render: (args) => , + }); + `); + }); + + it('should reuse existing default config import name', async () => { + await expect( + transform(dedent` + import previewConfig from "#.storybook/preview"; + const componentMeta = { title: 'Component' }; + export default componentMeta; + export const A = { + args: { primary: true }, + render: (args) => + }; + `) + ).resolves.toMatchInlineSnapshot(` + import previewConfig from '#.storybook/preview'; + + const meta = previewConfig.meta({ title: 'Component' }); + export const A = meta.story({ + args: { primary: true }, + render: (args) => , + }); + `); }); it('if there is an existing local constant called config, rename storybook config import', async () => { @@ -113,15 +135,15 @@ describe('stories codemod', () => { }; `) ).resolves.toMatchInlineSnapshot(` - import { config as storybookConfig } from '#.storybook/preview'; - - const meta = storybookConfig.meta({ title: 'Component' }); - const config = {}; - export const A = meta.story({ - args: { primary: true }, - render: (args) => , - }); - `); + import storybookConfig from '#.storybook/preview'; + + const meta = storybookConfig.meta({ title: 'Component' }); + const config = {}; + export const A = meta.story({ + args: { primary: true }, + render: (args) => , + }); + `); }); it('converts CSF1 into CSF4 with render', async () => { @@ -132,13 +154,13 @@ describe('stories codemod', () => { export const CSF1Story = () =>
Hello
; `) ).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; + import config from '#.storybook/preview'; - const meta = config.meta({ title: 'Component' }); - export const CSF1Story = meta.story({ - render: () =>
Hello
, - }); - `); + const meta = config.meta({ title: 'Component' }); + export const CSF1Story = meta.story({ + render: () =>
Hello
, + }); + `); }); }); @@ -155,16 +177,16 @@ describe('stories codemod', () => { `; it('meta satisfies syntax', async () => { await expect(transform(inlineMetaSatisfies)).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; + import config from '#.storybook/preview'; - import { ComponentProps } from './Component'; + import { ComponentProps } from './Component'; - const meta = config.meta({ title: 'Component', component: Component }); + const meta = config.meta({ title: 'Component', component: Component }); - export const A = meta.story({ - args: { primary: true }, - }); - `); + export const A = meta.story({ + args: { primary: true }, + }); + `); }); const inlineMetaAs = dedent` @@ -179,16 +201,16 @@ describe('stories codemod', () => { `; it('meta as syntax', async () => { await expect(transform(inlineMetaAs)).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; + import config from '#.storybook/preview'; - import { ComponentProps } from './Component'; + import { ComponentProps } from './Component'; - const meta = config.meta({ title: 'Component', component: Component }); + const meta = config.meta({ title: 'Component', component: Component }); - export const A = meta.story({ - args: { primary: true }, - }); - `); + export const A = meta.story({ + args: { primary: true }, + }); + `); }); const metaSatisfies = dedent` import { Meta, StoryObj as CSF3 } from '@storybook/react'; @@ -203,16 +225,16 @@ describe('stories codemod', () => { `; it('meta satisfies syntax', async () => { await expect(transform(metaSatisfies)).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; + import config from '#.storybook/preview'; - import { ComponentProps } from './Component'; + import { ComponentProps } from './Component'; - const meta = config.meta({ title: 'Component', component: Component }); + const meta = config.meta({ title: 'Component', component: Component }); - export const A = meta.story({ - args: { primary: true }, - }); - `); + export const A = meta.story({ + args: { primary: true }, + }); + `); }); const metaAs = dedent` @@ -228,16 +250,16 @@ describe('stories codemod', () => { `; it('meta as syntax', async () => { await expect(transform(metaAs)).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; + import config from '#.storybook/preview'; - import { ComponentProps } from './Component'; + import { ComponentProps } from './Component'; - const meta = config.meta({ title: 'Component', component: Component }); + const meta = config.meta({ title: 'Component', component: Component }); - export const A = meta.story({ - args: { primary: true }, - }); - `); + export const A = meta.story({ + args: { primary: true }, + }); + `); }); const storySatisfies = dedent` @@ -253,16 +275,16 @@ describe('stories codemod', () => { `; it('story satisfies syntax', async () => { await expect(transform(storySatisfies)).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; + import config from '#.storybook/preview'; - import { ComponentProps } from './Component'; + import { ComponentProps } from './Component'; - const meta = config.meta({ title: 'Component', component: Component }); + const meta = config.meta({ title: 'Component', component: Component }); - export const A = meta.story({ - args: { primary: true }, - }); - `); + export const A = meta.story({ + args: { primary: true }, + }); + `); }); const storyAs = dedent` @@ -278,16 +300,16 @@ describe('stories codemod', () => { `; it('story as syntax', async () => { await expect(transform(storyAs)).resolves.toMatchInlineSnapshot(` - import { config } from '#.storybook/preview'; + import config from '#.storybook/preview'; - import { ComponentProps } from './Component'; + import { ComponentProps } from './Component'; - const meta = config.meta({ title: 'Component', component: Component }); + const meta = config.meta({ title: 'Component', component: Component }); - export const A = meta.story({ - args: { primary: true }, - }); - `); + export const A = meta.story({ + args: { primary: true }, + }); + `); }); it('should yield the same result to all syntaxes', async () => { diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts index c359b8dfa997..acf02ee8274f 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts @@ -32,22 +32,20 @@ export async function storyToCsfFactory(info: FileInfo) { n.declarations.some((declaration) => t.isIdentifier(declaration.id, { name: 'config' })) ); - const sbConfigImportName = hasRootLevelConfig ? 'storybookConfig' : 'config'; + let sbConfigImportName = hasRootLevelConfig ? 'storybookConfig' : 'config'; - const sbConfigImportSpecifier = t.importSpecifier( - t.identifier(sbConfigImportName), - t.identifier('config') - ); + const sbConfigImportSpecifier = t.importDefaultSpecifier(t.identifier(sbConfigImportName)); programNode.body.forEach((node) => { if (t.isImportDeclaration(node) && isValidPreviewPath(node.source.value)) { - const hasConfigSpecifier = node.specifiers.some( - (specifier) => - t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported, { name: 'config' }) + const defaultImportSpecifier = node.specifiers.find((specifier) => + t.isImportDefaultSpecifier(specifier) ); - if (!hasConfigSpecifier) { + if (!defaultImportSpecifier) { node.specifiers.push(sbConfigImportSpecifier); + } else if (defaultImportSpecifier.local.name !== sbConfigImportName) { + sbConfigImportName = defaultImportSpecifier.local.name; } foundConfigImport = true; @@ -149,7 +147,7 @@ export async function storyToCsfFactory(info: FileInfo) { if (hasMeta && !foundConfigImport) { const configImport = t.importDeclaration( - [sbConfigImportSpecifier], + [t.importDefaultSpecifier(t.identifier(sbConfigImportName))], t.stringLiteral('#.storybook/preview') ); programNode.body.unshift(configImport); diff --git a/code/renderers/react/src/__test__/Button.csf4.stories.tsx b/code/renderers/react/src/__test__/Button.csf4.stories.tsx index 6a57e1d5a909..067e481a3d6a 100644 --- a/code/renderers/react/src/__test__/Button.csf4.stories.tsx +++ b/code/renderers/react/src/__test__/Button.csf4.stories.tsx @@ -5,10 +5,10 @@ import { expect, fn, mocked, userEvent, within } from '@storybook/test'; import { action } from '@storybook/addon-actions'; -import { defineConfig } from '../preview'; +import { definePreview } from '../preview'; import { Button } from './Button'; -const config = defineConfig({ args: { children: 'TODO: THIS IS NOT WORKING YET' } }); +const config = definePreview({}); const meta = config.meta({ id: 'button-component', diff --git a/code/renderers/react/src/csf-factories.test.tsx b/code/renderers/react/src/csf-factories.test.tsx index 12ea20a79ce2..2bceb23a53c6 100644 --- a/code/renderers/react/src/csf-factories.test.tsx +++ b/code/renderers/react/src/csf-factories.test.tsx @@ -1,10 +1,10 @@ import { expect, test } from 'vitest'; import { Button } from './__test__/Button'; -import { defineConfig } from './preview'; +import { definePreview } from './preview'; test('csf factories', () => { - const config = defineConfig({ + const config = definePreview({ addons: [ { decorators: [], diff --git a/code/renderers/react/src/preview.tsx b/code/renderers/react/src/preview.tsx index 55489ef5bdab..9230029d907c 100644 --- a/code/renderers/react/src/preview.tsx +++ b/code/renderers/react/src/preview.tsx @@ -17,7 +17,7 @@ import * as reactAnnotations from './entry-preview'; import * as reactDocsAnnotations from './entry-preview-docs'; import type { ReactRenderer } from './types'; -export function defineConfig(config: PreviewConfigData) { +export function definePreview(config: PreviewConfigData) { return new PreviewConfig({ ...config, addons: [reactAnnotations, reactDocsAnnotations, ...(config.addons ?? [])], From 71fefdaba67ce1e4aa343c0630f4fbdebaec21cb Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 20 Jan 2025 18:35:24 +0100 Subject: [PATCH 072/144] add fixes, fix types, fix tests --- code/lib/cli-storybook/src/add.test.ts | 2 ++ code/lib/cli-storybook/src/add.ts | 2 -- .../missing-storybook-dependencies.test.ts | 2 ++ .../src/automigrate/index.test.ts | 2 ++ .../codemod/helpers/csf-factories-utils.ts | 2 -- .../helpers/story-to-csf-factory.test.ts | 22 +++++++++++++++++++ .../codemod/helpers/story-to-csf-factory.ts | 19 ++++++++++++++++ code/lib/cli-storybook/src/migrate.ts | 17 +++++++------- code/lib/cli-storybook/src/upgrade.ts | 21 +++++++++--------- scripts/tasks/sandbox-parts.ts | 1 - 10 files changed, 65 insertions(+), 25 deletions(-) diff --git a/code/lib/cli-storybook/src/add.test.ts b/code/lib/cli-storybook/src/add.test.ts index 1bbf88275afc..0d5241b033bc 100644 --- a/code/lib/cli-storybook/src/add.test.ts +++ b/code/lib/cli-storybook/src/add.test.ts @@ -47,10 +47,12 @@ vi.mock('./postinstallAddon', () => { vi.mock('./automigrate/fixes/wrap-require-utils', () => { return MockWrapRequireUtils; }); +vi.mock('./codemod/helpers/csf-factories-utils'); vi.mock('storybook/internal/common', () => { return { getStorybookInfo: vi.fn(() => ({ mainConfig: {}, configDir: '' })), serverRequire: vi.fn(() => ({})), + loadMainConfig: vi.fn(() => ({})), JsPackageManagerFactory: { getPackageManager: vi.fn(() => MockedPackageManager), }, diff --git a/code/lib/cli-storybook/src/add.ts b/code/lib/cli-storybook/src/add.ts index 2fb796d7793b..02b8fc366a4e 100644 --- a/code/lib/cli-storybook/src/add.ts +++ b/code/lib/cli-storybook/src/add.ts @@ -3,8 +3,6 @@ import { isAbsolute, join } from 'node:path'; import { JsPackageManagerFactory, type PackageManagerName, - getCoercedStorybookVersion, - getStorybookInfo, serverRequire, versions, } from 'storybook/internal/common'; 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 index 2729cfb1da16..b39926413778 100644 --- 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 @@ -109,6 +109,8 @@ describe('missingStorybookDependencies', () => { await missingStorybookDependencies.run!({ result: { packageUsage }, dryRun, + packageJson: {}, + mainConfig: { stories: [] }, packageManager: mockPackageManager as JsPackageManager, mainConfigPath: 'path/to/main-config.js', }); diff --git a/code/lib/cli-storybook/src/automigrate/index.test.ts b/code/lib/cli-storybook/src/automigrate/index.test.ts index 9bc05affbacc..1ca806a22c91 100644 --- a/code/lib/cli-storybook/src/automigrate/index.test.ts +++ b/code/lib/cli-storybook/src/automigrate/index.test.ts @@ -89,6 +89,8 @@ const runFixWrapper = async ({ fixes, dryRun, yes, + packageJson: {}, + mainConfig: { stories: [] }, rendererPackage, skipInstall, configDir, diff --git a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts index c01ade0954c3..c12ef4fc4e31 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts @@ -1,6 +1,4 @@ /* eslint-disable no-underscore-dangle */ -import path from 'node:path'; - import { types as t } from 'storybook/internal/babel'; import { type ConfigFile, readConfig, writeConfig } from 'storybook/internal/csf-tools'; diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts index ea19c007f7f8..3064115ca976 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts @@ -326,5 +326,27 @@ describe('stories codemod', () => { expect(result).toEqual(allSnippets[0]); }); }); + + it('should remove unused Story types', async () => { + await expect( + transform( + `import { Meta, StoryObj as CSF3 } from '@storybook/react'; + import { ComponentProps } from './Component'; + + export default {}; + type Story = StoryObj; + + export const A: Story = {};` + ) + ).resolves.toMatchInlineSnapshot(` + import config from '#.storybook/preview'; + + import { ComponentProps } from './Component'; + + const meta = config.meta({}); + + export const A = meta.story({}); + `); + }); }); }); diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts index acf02ee8274f..f374eea9df80 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts @@ -165,5 +165,24 @@ export async function storyToCsfFactory(info: FileInfo) { ]; programNode.body = cleanupTypeImports(programNode, disallowList); + // Remove unused type aliases e.g. `type Story = StoryObj;` + programNode.body.forEach((node, index) => { + if (t.isTSTypeAliasDeclaration(node)) { + const isUsed = programNode.body.some((otherNode) => { + if (t.isVariableDeclaration(otherNode)) { + return otherNode.declarations.some( + (declaration) => + t.isIdentifier(declaration.init) && declaration.init.name === node.id.name + ); + } + return false; + }); + + if (!isUsed) { + programNode.body.splice(index, 1); + } + } + }); + return printCsf(csf).code; } diff --git a/code/lib/cli-storybook/src/migrate.ts b/code/lib/cli-storybook/src/migrate.ts index e985971b5a05..9075a9d9e8b2 100644 --- a/code/lib/cli-storybook/src/migrate.ts +++ b/code/lib/cli-storybook/src/migrate.ts @@ -9,6 +9,7 @@ import { listCodemods, runCodemod } from '@storybook/codemod'; import { runFixes } from './automigrate'; import { mdxToCSF } from './automigrate/fixes/mdx-to-csf'; +import { getStorybookData } from './automigrate/helpers/mainConfigFile'; const logger = console; @@ -33,15 +34,11 @@ export async function migrate( if (migration === 'mdx-to-csf' && !dryRun) { const packageManager = JsPackageManagerFactory.getPackageManager(); - const [packageJson, storybookVersion] = await Promise.all([ - packageManager.retrievePackageJson(), - getCoercedStorybookVersion(packageManager), - ]); - const { configDir: inferredConfigDir, mainConfig: mainConfigPath } = getStorybookInfo( - packageJson, - userSpecifiedConfigDir - ); - const configDir = userSpecifiedConfigDir || inferredConfigDir || '.storybook'; + const { configDir, mainConfig, mainConfigPath, storybookVersion, packageJson } = + await getStorybookData({ + packageManager, + configDir: userSpecifiedConfigDir, + }); // GUARDS if (!storybookVersion) { @@ -57,6 +54,8 @@ export async function migrate( configDir, mainConfigPath, packageManager, + mainConfig, + packageJson, storybookVersion, beforeVersion: storybookVersion, isUpgrade: false, diff --git a/code/lib/cli-storybook/src/upgrade.ts b/code/lib/cli-storybook/src/upgrade.ts index 6657fb0ee691..5a8bd0a4efb0 100644 --- a/code/lib/cli-storybook/src/upgrade.ts +++ b/code/lib/cli-storybook/src/upgrade.ts @@ -24,6 +24,7 @@ import semver, { clean, eq, lt, prerelease } from 'semver'; import { dedent } from 'ts-dedent'; import { autoblock } from './autoblock/index'; +import { getStorybookData } from './automigrate/helpers/mainConfigFile'; import { automigrate } from './automigrate/index'; type Package = { @@ -157,10 +158,7 @@ export const doUpgrade = async ({ logger.warn(new UpgradeStorybookToSameVersionError({ beforeVersion }).message); } - const [latestCLIVersionOnNPM, packageJson] = await Promise.all([ - packageManager.latestVersion('storybook'), - packageManager.retrievePackageJson(), - ]); + const latestCLIVersionOnNPM = await packageManager.latestVersion('storybook'); const isCLIOutdated = lt(currentCLIVersion, latestCLIVersionOnNPM); const isCLIExactLatest = currentCLIVersion === latestCLIVersionOnNPM; @@ -198,13 +196,11 @@ export const doUpgrade = async ({ let results; - const { configDir: inferredConfigDir, mainConfig: mainConfigPath } = getStorybookInfo( - packageJson, - userSpecifiedConfigDir - ); - const configDir = userSpecifiedConfigDir || inferredConfigDir || '.storybook'; - - const mainConfig = await loadMainConfig({ configDir }); + const { configDir, mainConfig, mainConfigPath, previewConfigPath, packageJson } = + await getStorybookData({ + packageManager, + configDir: userSpecifiedConfigDir, + }); // GUARDS if (!beforeVersion) { @@ -277,7 +273,10 @@ export const doUpgrade = async ({ dryRun, yes, packageManager, + packageJson, + mainConfig, configDir, + previewConfigPath, mainConfigPath, beforeVersion, storybookVersion: currentCLIVersion, diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index be12a4ede276..f26599d99073 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -858,7 +858,6 @@ export async function setImportMap(cwd: string) { storybook: './template-stories/core/utils.mock.ts', default: './template-stories/core/utils.ts', }, - '#*': ['./*', './*.ts', './*.tsx'], }; await writeJson(join(cwd, 'package.json'), packageJson, { spaces: 2 }); From 69941ca2083de9cd2ccb8437f95634e8f5640eea Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 20 Jan 2025 18:41:30 +0100 Subject: [PATCH 073/144] fix portable stories test --- scripts/tasks/sandbox-parts.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index f26599d99073..9c5a40bf2771 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -426,10 +426,10 @@ export async function setupVitest(details: TemplateDetails, options: PassedOptio setupFilePath, dedent`import { beforeAll } from 'vitest' import { setProjectAnnotations } from '${storybookPackage}' - import * as projectAnnotations from './preview' + import projectAnnotations from './preview' // setProjectAnnotations still kept to support non-CSF4 story tests - const annotations = setProjectAnnotations(projectAnnotations.config.annotations) + const annotations = setProjectAnnotations(projectAnnotations.annotations) beforeAll(annotations.beforeAll) ` ); From 5b4cd434189a283544bb9660e3c00d2c2a0acb83 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 20 Jan 2025 19:39:55 +0100 Subject: [PATCH 074/144] fix build --- .../src/automigrate/index.test.ts | 20 ++++++++++--------- .../helpers/csf-factories-utils.test.ts | 2 +- .../codemod/helpers/csf-factories-utils.ts | 4 +++- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/code/lib/cli-storybook/src/automigrate/index.test.ts b/code/lib/cli-storybook/src/automigrate/index.test.ts index 1ca806a22c91..32a4a512cbc4 100644 --- a/code/lib/cli-storybook/src/automigrate/index.test.ts +++ b/code/lib/cli-storybook/src/automigrate/index.test.ts @@ -136,15 +136,17 @@ describe('runFixes', () => { expect(fixResults).toEqual({ 'fix-1': 'succeeded', }); - expect(run1).toHaveBeenCalledWith({ - dryRun, - mainConfigPath, - packageManager, - result: { - some: 'result', - }, - skipInstall, - }); + expect(run1).toHaveBeenCalledWith( + expect.objectContaining({ + dryRun, + mainConfigPath, + packageManager, + result: { + some: 'result', + }, + skipInstall, + }) + ); }); it('should fail if an error is thrown', async () => { diff --git a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts index 1eeb7f6282f7..4c7167b6b209 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts @@ -47,7 +47,7 @@ describe('getSyncedStorybookAddons', () => { }); `); }); - it.only('should add addons if the preview has no addons field', async () => { + it('should add addons if the preview has no addons field', async () => { const originalCode = ` import { definePreview } from "@storybook/react/preview"; diff --git a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts index c12ef4fc4e31..7995479f9159 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts @@ -35,7 +35,9 @@ export function cleanupTypeImports(programNode: t.Program, disallowList: string[ // Retain all other nodes return true; - }); + // @TODO adding any for now, unsure how to fix the following error: + // error TS4058: Return type of exported function has or is using name 'BlockStatement' from external module "/code/core/dist/babel/index" but cannot be named + }) as any; } export async function syncStorybookAddons( From 80c80acdebf1b1a461ca39e69cade330fe1d1aa6 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 20 Jan 2025 21:49:21 +0100 Subject: [PATCH 075/144] fix lint --- code/core/src/csf-tools/ConfigFile.test.ts | 2 +- .../src/codemod/helpers/csf-factories-utils.test.ts | 4 ++-- scripts/tasks/sandbox.ts | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/code/core/src/csf-tools/ConfigFile.test.ts b/code/core/src/csf-tools/ConfigFile.test.ts index e6739e8a27ec..f7b349669feb 100644 --- a/code/core/src/csf-tools/ConfigFile.test.ts +++ b/code/core/src/csf-tools/ConfigFile.test.ts @@ -268,7 +268,7 @@ describe('ConfigFile', () => { ) ).toEqual('webpack5'); }); - it.only('tags', () => { + it('tags', () => { expect( getField( ['tags'], diff --git a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts index 4c7167b6b209..af79e7442dac 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts @@ -48,7 +48,7 @@ describe('getSyncedStorybookAddons', () => { `); }); it('should add addons if the preview has no addons field', async () => { - const originalCode = ` + const originalCode = dedent` import { definePreview } from "@storybook/react/preview"; export default definePreview({ @@ -73,7 +73,7 @@ describe('getSyncedStorybookAddons', () => { `); }); it('should not modify the code if all addons are already synced', async () => { - const originalCode = ` + const originalCode = dedent` import * as addonA11yAnnotations from "@storybook/addon-a11y/preview"; import * as myAddonAnnotations from "custom-addon/preview"; import { definePreview } from "@storybook/react/preview"; diff --git a/scripts/tasks/sandbox.ts b/scripts/tasks/sandbox.ts index 7bca37085843..33a3282d44d2 100644 --- a/scripts/tasks/sandbox.ts +++ b/scripts/tasks/sandbox.ts @@ -2,7 +2,6 @@ import dirSize from 'fast-folder-size'; // eslint-disable-next-line depend/ban-dependencies import { pathExists, remove } from 'fs-extra'; import { join } from 'path'; -import prompts from 'prompts'; import { promisify } from 'util'; import { now, saveBench } from '../bench/utils'; From 2ec88ee8c1ffb7d7460f059c7b8f36e98ef13f2e Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 21 Jan 2025 08:13:54 +0100 Subject: [PATCH 076/144] update snapshots --- code/core/src/csf-tools/ConfigFile.test.ts | 23 +++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/code/core/src/csf-tools/ConfigFile.test.ts b/code/core/src/csf-tools/ConfigFile.test.ts index f7b349669feb..6661cfb37622 100644 --- a/code/core/src/csf-tools/ConfigFile.test.ts +++ b/code/core/src/csf-tools/ConfigFile.test.ts @@ -538,11 +538,11 @@ describe('ConfigFile', () => { import { definePreview } from '@storybook/react-vite/preview'; export const foo = definePreview({ addons: [], - }); - export const core = { - builder: 'webpack5' - }; + core: { + builder: 'webpack5' + } + }); `); }); it('missing field', () => { @@ -560,12 +560,11 @@ describe('ConfigFile', () => { ).toMatchInlineSnapshot(` import { definePreview } from '@storybook/react-vite/preview'; export const foo = definePreview({ - core: { foo: 'bar' }, + core: { + foo: 'bar', + builder: 'webpack5' + }, }); - - export const core = { - builder: 'webpack5' - }; `); }); it('found scalar', () => { @@ -583,12 +582,8 @@ describe('ConfigFile', () => { ).toMatchInlineSnapshot(` import { definePreview } from '@storybook/react-vite/preview'; export const foo = definePreview({ - core: { builder: 'webpack4' }, + core: { builder: 'webpack5' }, }); - - export const core = { - builder: 'webpack5' - }; `); }); }); From ae4128b5ad53b8b959f1d73cb322293aa82cff52 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 21 Jan 2025 14:42:45 +0100 Subject: [PATCH 077/144] sync addons between main and preview.js in storybook dev --- code/core/src/common/index.ts | 2 + .../utils}/get-addon-annotations.test.ts | 0 .../common/utils}/get-addon-annotations.ts | 0 .../src/common/utils/get-addon-names.test.ts | 84 ++++++++++++++++++ code/core/src/common/utils/get-addon-names.ts | 31 +++++++ .../utils/sync-main-preview-addons.test.ts} | 25 +++++- .../common/utils/sync-main-preview-addons.ts | 86 +++++++++++++++++++ code/core/src/core-server/build-dev.ts | 5 ++ code/lib/cli-storybook/src/add.ts | 2 +- .../fixes/addon-a11y-addon-test.ts | 4 +- .../src/automigrate/fixes/addon-postcss.ts | 3 +- .../src/automigrate/fixes/vta.ts | 4 +- .../fixes/webpack5-compiler-setup.ts | 3 +- .../src/automigrate/helpers/mainConfigFile.ts | 27 +----- .../src/codemod/csf-factories.ts | 4 +- .../codemod/helpers/csf-factories-utils.ts | 70 --------------- code/lib/cli-storybook/tsconfig.json | 6 +- 17 files changed, 249 insertions(+), 107 deletions(-) rename code/{lib/cli-storybook/src/codemod/helpers => core/src/common/utils}/get-addon-annotations.test.ts (100%) rename code/{lib/cli-storybook/src/codemod/helpers => core/src/common/utils}/get-addon-annotations.ts (100%) create mode 100644 code/core/src/common/utils/get-addon-names.test.ts create mode 100644 code/core/src/common/utils/get-addon-names.ts rename code/{lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts => core/src/common/utils/sync-main-preview-addons.test.ts} (78%) create mode 100644 code/core/src/common/utils/sync-main-preview-addons.ts diff --git a/code/core/src/common/index.ts b/code/core/src/common/index.ts index 6166e285ab05..6700cdf14fad 100644 --- a/code/core/src/common/index.ts +++ b/code/core/src/common/index.ts @@ -41,6 +41,8 @@ export * from './utils/strip-abs-node-modules-path'; export * from './utils/formatter'; export * from './utils/get-story-id'; export * from './utils/posix'; +export * from './utils/get-addon-names'; +export * from './utils/sync-main-preview-addons'; export * from './js-package-manager'; export { versions }; diff --git a/code/lib/cli-storybook/src/codemod/helpers/get-addon-annotations.test.ts b/code/core/src/common/utils/get-addon-annotations.test.ts similarity index 100% rename from code/lib/cli-storybook/src/codemod/helpers/get-addon-annotations.test.ts rename to code/core/src/common/utils/get-addon-annotations.test.ts diff --git a/code/lib/cli-storybook/src/codemod/helpers/get-addon-annotations.ts b/code/core/src/common/utils/get-addon-annotations.ts similarity index 100% rename from code/lib/cli-storybook/src/codemod/helpers/get-addon-annotations.ts rename to code/core/src/common/utils/get-addon-annotations.ts diff --git a/code/core/src/common/utils/get-addon-names.test.ts b/code/core/src/common/utils/get-addon-names.test.ts new file mode 100644 index 000000000000..c7769063afe3 --- /dev/null +++ b/code/core/src/common/utils/get-addon-names.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, it } from 'vitest'; + +import { getAddonNames } from './get-addon-names'; + +describe('getAddonNames', () => { + it('should extract addon names from simple strings', () => { + const config = { + stories: [], + addons: ['@storybook/addon-actions', '@storybook/addon-outline'], + }; + const result = getAddonNames(config); + expect(result).toEqual(['@storybook/addon-actions', '@storybook/addon-outline']); + }); + + it('should extract addon names from object notation', () => { + const config = { + stories: [], + addons: [{ name: '@storybook/addon-actions' }, { name: '@storybook/addon-outline' }], + }; + const result = getAddonNames(config); + expect(result).toEqual(['@storybook/addon-actions', '@storybook/addon-outline']); + }); + + it('should filter out relative paths for local addons', () => { + const config = { + stories: [], + addons: ['./local-addon', { name: './another-local-addon' }], + }; + const result = getAddonNames(config); + expect(result).toEqual([]); + }); + + it('should extract addon names from absolute paths', () => { + const config = { + stories: [], + addons: [ + '/sandbox/react-vite-default-ts/node_modules/@storybook/addon-actions', + '/sandbox/react-vite-default-ts/node_modules/@storybook/addon-outline', + ], + }; + const result = getAddonNames(config); + expect(result).toEqual(['@storybook/addon-actions', '@storybook/addon-outline']); + }); + + it('should extract addon names from pnpm paths', () => { + const config = { + stories: [], + addons: [ + '/Users/xxx/node_modules/.pnpm/@storybook+addon-essentials@8.5.0-beta.5_@types+react@18.2.33_storybook@8.5.0-beta.5_prettier@3.2.5_/node_modules/@storybook/addon-essentials', + ], + }; + const result = getAddonNames(config); + expect(result).toEqual(['@storybook/addon-essentials']); + }); + + it('should extract addon names from yarn pnp paths', () => { + const config = { + stories: [], + addons: [ + '/Users/xxx/.yarn/__virtual__/@storybook-addon-essentials-virtual-5c3b9b3005/3/.yarn/berry/cache/@storybook-addon-essentials-npm-8.5.0-bbaf03c190-10c0.zip/node_modules/@storybook/addon-essentials', + ], + }; + const result = getAddonNames(config); + expect(result).toEqual(['@storybook/addon-essentials']); + }); + + it('should handle mixed addon configurations', () => { + const config = { + stories: [], + addons: [ + '@storybook/addon-actions', + { name: '@storybook/addon-outline' }, + './local-addon', + '/sandbox/react-vite-default-ts/node_modules/@storybook/addon-controls', + ], + }; + const result = getAddonNames(config); + expect(result).toEqual([ + '@storybook/addon-actions', + '@storybook/addon-outline', + '@storybook/addon-controls', + ]); + }); +}); diff --git a/code/core/src/common/utils/get-addon-names.ts b/code/core/src/common/utils/get-addon-names.ts new file mode 100644 index 000000000000..d3081a729438 --- /dev/null +++ b/code/core/src/common/utils/get-addon-names.ts @@ -0,0 +1,31 @@ +import type { StorybookConfig } from '@storybook/types'; + +export const getAddonNames = (mainConfig: StorybookConfig): string[] => { + const addons = mainConfig.addons || []; + const addonList = addons.map((addon) => { + let name = ''; + if (typeof addon === 'string') { + name = addon; + } else if (typeof addon === 'object') { + name = addon.name; + } + + if (name.startsWith('.')) { + return undefined; + } + + // For absolute paths, pnpm and yarn pnp, + // Remove everything before and including "node_modules/" + name = name.replace(/.*node_modules\//, ''); + + // Further clean up package names + return name + .replace(/\/dist\/.*$/, '') + .replace(/\.[mc]?[tj]?sx?$/, '') + .replace(/\/register$/, '') + .replace(/\/manager$/, '') + .replace(/\/preset$/, ''); + }); + + return addonList.filter((item): item is NonNullable => item != null); +}; diff --git a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts b/code/core/src/common/utils/sync-main-preview-addons.test.ts similarity index 78% rename from code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts rename to code/core/src/common/utils/sync-main-preview-addons.test.ts index af79e7442dac..57ef3b033bd1 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.test.ts +++ b/code/core/src/common/utils/sync-main-preview-addons.test.ts @@ -7,8 +7,8 @@ import { loadConfig, printConfig } from '@storybook/core/csf-tools'; import { dedent } from 'ts-dedent'; -import { getSyncedStorybookAddons } from './csf-factories-utils'; import { getAddonAnnotations } from './get-addon-annotations'; +import { getSyncedStorybookAddons } from './sync-main-preview-addons'; vi.mock('./get-addon-annotations'); @@ -22,6 +22,7 @@ describe('getSyncedStorybookAddons', () => { stories: [], addons: ['custom-addon', '@storybook/addon-a11y'], }; + it('should sync addons between main and preview', async () => { const preview = loadConfig(` import * as myAddonAnnotations from "custom-addon/preview"; @@ -47,6 +48,7 @@ describe('getSyncedStorybookAddons', () => { }); `); }); + it('should add addons if the preview has no addons field', async () => { const originalCode = dedent` import { definePreview } from "@storybook/react/preview"; @@ -72,6 +74,27 @@ describe('getSyncedStorybookAddons', () => { }); `); }); + + it('should not add an addon if its annotations path has already been imported', async () => { + const originalCode = dedent` + import * as addonA11yAnnotations from "@storybook/addon-a11y/preview"; + import * as myAddonAnnotations from "custom-addon/preview"; + import { definePreview } from "@storybook/react/preview"; + const extraAddons = [addonA11yAnnotations] + export default definePreview({ + addons: [myAddonAnnotations, ...extraAddons], + }); + `; + const preview = loadConfig(originalCode).parse(); + + (getAddonAnnotations as Mock).mockImplementation(() => { + return { importName: 'addonA11yAnnotations', importPath: '@storybook/addon-a11y/preview' }; + }); + + const result = await getSyncedStorybookAddons(mainConfig, preview); + expect(printConfig(result).code).toEqual(originalCode); + }); + it('should not modify the code if all addons are already synced', async () => { const originalCode = dedent` import * as addonA11yAnnotations from "@storybook/addon-a11y/preview"; diff --git a/code/core/src/common/utils/sync-main-preview-addons.ts b/code/core/src/common/utils/sync-main-preview-addons.ts new file mode 100644 index 000000000000..636dc5b46f7f --- /dev/null +++ b/code/core/src/common/utils/sync-main-preview-addons.ts @@ -0,0 +1,86 @@ +/* eslint-disable no-underscore-dangle */ +import { types as t } from '@storybook/core/babel'; +import type { StorybookConfig } from '@storybook/types'; + +import { type ConfigFile, readConfig, writeConfig } from '@storybook/core/csf-tools'; + +import { getAddonAnnotations } from './get-addon-annotations'; +import { getAddonNames } from './get-addon-names'; + +const logger = console; + +export async function syncStorybookAddons(mainConfig: StorybookConfig, previewConfigPath: string) { + const previewConfig = await readConfig(previewConfigPath!); + const modifiedConfig = await getSyncedStorybookAddons(mainConfig, previewConfig); + + await writeConfig(modifiedConfig); +} + +export async function getSyncedStorybookAddons( + mainConfig: StorybookConfig, + previewConfig: ConfigFile +): Promise { + const program = previewConfig._ast.program; + const isCsfFactoryPreview = !!program.body.find((node) => { + return ( + t.isImportDeclaration(node) && + node.source.value.includes('@storybook') && + node.source.value.endsWith('/preview') && + node.specifiers.some((specifier) => { + return ( + t.isImportSpecifier(specifier) && + t.isIdentifier(specifier.imported) && + specifier.imported.name === 'definePreview' + ); + }) + ); + }); + + if (!isCsfFactoryPreview) { + return previewConfig; + } + + const addons = getAddonNames(mainConfig); + if (!addons) { + return previewConfig; + } + + const syncedAddons: string[] = []; + const existingAddons = previewConfig.getFieldNode(['addons']); + /** + * This goes through all mainConfig.addons, read their package.json and check whether they have an + * exports map called preview, if so add to the array + */ + await addons.forEach(async (addon) => { + const annotations = await getAddonAnnotations(addon); + if (annotations) { + const hasAlreadyImportedAddonAnnotations = previewConfig._ast.program.body.find( + (node) => t.isImportDeclaration(node) && node.source.value === annotations.importPath + ); + + if (!!hasAlreadyImportedAddonAnnotations) { + return; + } + + if ( + !existingAddons || + (t.isArrayExpression(existingAddons) && + !existingAddons.elements.some( + (element) => t.isIdentifier(element) && element.name === annotations.importName + )) + ) { + syncedAddons.push(addon); + previewConfig.setImport({ namespace: annotations.importName }, annotations.importPath); + previewConfig.appendNodeToArray(['addons'], t.identifier(annotations.importName)); + } + } + }); + + if (syncedAddons.length > 0) { + logger.info( + `Synchronizing addons from main config in ${previewConfig.fileName}:\n${syncedAddons.join(', ')}` + ); + } + + return previewConfig; +} diff --git a/code/core/src/core-server/build-dev.ts b/code/core/src/core-server/build-dev.ts index c8b336a22e09..be55dc10ff09 100644 --- a/code/core/src/core-server/build-dev.ts +++ b/code/core/src/core-server/build-dev.ts @@ -9,6 +9,7 @@ import { resolveAddonName, resolvePathInStorybookCache, serverResolve, + syncStorybookAddons, validateFrameworkName, versions, } from '@storybook/core/common'; @@ -113,6 +114,10 @@ export async function buildDevStandalone( console.warn('Storybook failed to check addon compatibility', e); } + try { + await syncStorybookAddons(config, previewConfigPath!); + } catch (e) {} + try { await warnWhenUsingArgTypesRegex(previewConfigPath, config); } catch (e) {} diff --git a/code/lib/cli-storybook/src/add.ts b/code/lib/cli-storybook/src/add.ts index 02b8fc366a4e..44184518519a 100644 --- a/code/lib/cli-storybook/src/add.ts +++ b/code/lib/cli-storybook/src/add.ts @@ -4,6 +4,7 @@ import { JsPackageManagerFactory, type PackageManagerName, serverRequire, + syncStorybookAddons, versions, } from 'storybook/internal/common'; import { readConfig, writeConfig } from 'storybook/internal/csf-tools'; @@ -19,7 +20,6 @@ import { wrapValueWithRequireWrapper, } from './automigrate/fixes/wrap-require-utils'; import { getStorybookData } from './automigrate/helpers/mainConfigFile'; -import { syncStorybookAddons } from './codemod/helpers/csf-factories-utils'; import { postinstallAddon } from './postinstallAddon'; export interface PostinstallOptions { diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts index 80b1ccbf5658..29b07c54b064 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts @@ -1,4 +1,4 @@ -import { formatFileContent, rendererPackages } from 'storybook/internal/common'; +import { formatFileContent, getAddonNames, rendererPackages } from 'storybook/internal/common'; import { formatConfig, loadConfig } from 'storybook/internal/csf-tools'; import { type ArrayExpression } from '@babel/types'; @@ -13,7 +13,7 @@ import { SUPPORTED_FRAMEWORKS, SUPPORTED_RENDERERS, } from '../../../../../addons/test/src/constants'; -import { getAddonNames, getFrameworkPackageName, getRendererName } from '../helpers/mainConfigFile'; +import { getFrameworkPackageName, getRendererName } from '../helpers/mainConfigFile'; import type { Fix } from '../types'; export const fileExtensions = [ diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-postcss.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-postcss.ts index d8bd135da2f9..42047be06132 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-postcss.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-postcss.ts @@ -1,7 +1,8 @@ +import { getAddonNames } from 'storybook/internal/common'; + import picocolors from 'picocolors'; import { dedent } from 'ts-dedent'; -import { getAddonNames } from '../helpers/mainConfigFile'; import type { Fix } from '../types'; interface AddonPostcssRunOptions { diff --git a/code/lib/cli-storybook/src/automigrate/fixes/vta.ts b/code/lib/cli-storybook/src/automigrate/fixes/vta.ts index 47cd1a9fe0ff..0b9d4f481e5b 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/vta.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/vta.ts @@ -1,7 +1,9 @@ +import { getAddonNames } from 'storybook/internal/common'; + import picocolors from 'picocolors'; import { dedent } from 'ts-dedent'; -import { getAddonNames, updateMainConfig } from '../helpers/mainConfigFile'; +import { updateMainConfig } from '../helpers/mainConfigFile'; import type { Fix } from '../types'; const logger = console; diff --git a/code/lib/cli-storybook/src/automigrate/fixes/webpack5-compiler-setup.ts b/code/lib/cli-storybook/src/automigrate/fixes/webpack5-compiler-setup.ts index e0c5f0e9cfc7..531da92a827a 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/webpack5-compiler-setup.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/webpack5-compiler-setup.ts @@ -5,7 +5,7 @@ import { builderNameToCoreBuilder, compilerNameToCoreCompiler, } from 'storybook/internal/cli'; -import { frameworkPackages } from 'storybook/internal/common'; +import { frameworkPackages, getAddonNames } from 'storybook/internal/common'; import type { SupportedFrameworks } from 'storybook/internal/types'; import picocolors from 'picocolors'; @@ -14,7 +14,6 @@ import { dedent } from 'ts-dedent'; import { add } from '../../add'; import { - getAddonNames, getBuilderPackageName, getFrameworkOptions, getFrameworkPackageName, diff --git a/code/lib/cli-storybook/src/automigrate/helpers/mainConfigFile.ts b/code/lib/cli-storybook/src/automigrate/helpers/mainConfigFile.ts index 717f92b646b3..6a93538da497 100644 --- a/code/lib/cli-storybook/src/automigrate/helpers/mainConfigFile.ts +++ b/code/lib/cli-storybook/src/automigrate/helpers/mainConfigFile.ts @@ -13,7 +13,7 @@ import type { JsPackageManager } from 'storybook/internal/common'; import { getCoercedStorybookVersion } from 'storybook/internal/common'; import type { ConfigFile } from 'storybook/internal/csf-tools'; import { readConfig, writeConfig as writeConfigFile } from 'storybook/internal/csf-tools'; -import type { StorybookConfig, StorybookConfigRaw } from 'storybook/internal/types'; +import type { StorybookConfigRaw } from 'storybook/internal/types'; import picocolors from 'picocolors'; import { dedent } from 'ts-dedent'; @@ -202,28 +202,3 @@ export const updateMainConfig = async ( ); } }; - -export const getAddonNames = (mainConfig: StorybookConfig): string[] => { - const addons = mainConfig.addons || []; - const addonList = addons.map((addon) => { - let name = ''; - if (typeof addon === 'string') { - name = addon; - } else if (typeof addon === 'object') { - name = addon.name; - } - - if (name.startsWith('.')) { - return undefined; - } - - return name - .replace(/\/dist\/.*/, '') - .replace(/\.[mc]?[tj]?s[x]?$/, '') - .replace(/\/register$/, '') - .replace(/\/manager$/, '') - .replace(/\/preset$/, ''); - }); - - return addonList.filter((item): item is NonNullable => item != null); -}; diff --git a/code/lib/cli-storybook/src/codemod/csf-factories.ts b/code/lib/cli-storybook/src/codemod/csf-factories.ts index 963aa3649152..d5d3a73108af 100644 --- a/code/lib/cli-storybook/src/codemod/csf-factories.ts +++ b/code/lib/cli-storybook/src/codemod/csf-factories.ts @@ -1,10 +1,11 @@ +import { syncStorybookAddons } from 'storybook/internal/common'; + import prompts from 'prompts'; import { runCodemod } from '../automigrate/codemod'; import { getFrameworkPackageName } from '../automigrate/helpers/mainConfigFile'; import type { CommandFix } from '../automigrate/types'; import { configToCsfFactory } from './helpers/config-to-csf-factory'; -import { syncStorybookAddons } from './helpers/csf-factories-utils'; import { storyToCsfFactory } from './helpers/story-to-csf-factory'; export const logger = console; @@ -69,7 +70,6 @@ export const csfFactories: CommandFix = { configToCsfFactory(fileInfo, { configType: 'preview', frameworkPackage }, { dryRun }) ); - logger.log('Synchronizing addons between main and preview config...'); await syncStorybookAddons(mainConfig, previewConfigPath!); }, }; diff --git a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts index 7995479f9159..ef536f09087c 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts @@ -1,12 +1,4 @@ -/* eslint-disable no-underscore-dangle */ import { types as t } from 'storybook/internal/babel'; -import { type ConfigFile, readConfig, writeConfig } from 'storybook/internal/csf-tools'; - -import type { StorybookConfigRaw } from '@storybook/types'; - -import { getAddonNames } from '../../automigrate/helpers/mainConfigFile'; -import { logger } from '../csf-factories'; -import { getAddonAnnotations } from './get-addon-annotations'; export function cleanupTypeImports(programNode: t.Program, disallowList: string[]) { return programNode.body.filter((node) => { @@ -39,65 +31,3 @@ export function cleanupTypeImports(programNode: t.Program, disallowList: string[ // error TS4058: Return type of exported function has or is using name 'BlockStatement' from external module "/code/core/dist/babel/index" but cannot be named }) as any; } - -export async function syncStorybookAddons( - mainConfig: StorybookConfigRaw, - previewConfigPath: string -) { - const previewConfig = await readConfig(previewConfigPath!); - const modifiedConfig = await getSyncedStorybookAddons(mainConfig, previewConfig); - - await writeConfig(modifiedConfig); -} - -export async function getSyncedStorybookAddons( - mainConfig: StorybookConfigRaw, - previewConfig: ConfigFile -): Promise { - const program = previewConfig._ast.program; - const isCsfFactoryPreview = !!program.body.find((node) => { - return ( - t.isImportDeclaration(node) && - node.source.value.includes('@storybook') && - node.source.value.endsWith('/preview') && - node.specifiers.some((specifier) => { - return ( - t.isImportSpecifier(specifier) && - t.isIdentifier(specifier.imported) && - specifier.imported.name === 'definePreview' - ); - }) - ); - }); - - if (!isCsfFactoryPreview) { - logger.log('Skipping syncStorybookAddons as the preview config is not a csf factory'); - return previewConfig; - } - - const addons = getAddonNames(mainConfig); - - /** - * This goes through all mainConfig.addons, read their package.json and check whether they have an - * exports map called preview, if so add to the array - */ - addons.forEach(async (addon) => { - const annotations = await getAddonAnnotations(addon); - if (annotations) { - previewConfig.setImport({ namespace: annotations.importName }, annotations.importPath); - const existingAddons = previewConfig.getFieldNode(['addons']); - - if ( - !existingAddons || - (t.isArrayExpression(existingAddons) && - !existingAddons.elements.some( - (element) => t.isIdentifier(element) && element.name === annotations.importName - )) - ) { - previewConfig.appendNodeToArray(['addons'], t.identifier(annotations.importName)); - } - } - }); - - return previewConfig; -} diff --git a/code/lib/cli-storybook/tsconfig.json b/code/lib/cli-storybook/tsconfig.json index 73a65ef2ef6e..a8cd464246ca 100644 --- a/code/lib/cli-storybook/tsconfig.json +++ b/code/lib/cli-storybook/tsconfig.json @@ -1,5 +1,9 @@ { "extends": "../../tsconfig.json", "compilerOptions": {}, - "include": ["src/**/*"] + "include": [ + "src/**/*", + "../../core/src/common/utils/get-addon-annotations.test.ts", + "../../core/src/common/utils/get-addon-annotations.ts" + ] } From 5e0b3206bbf57b542905c465bde83a5c74e5907f Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 22 Jan 2025 09:32:24 +0100 Subject: [PATCH 078/144] fix tests --- code/lib/cli-storybook/src/add.test.ts | 1 + .../src/automigrate/fixes/addon-a11y-addon-test.test.ts | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/code/lib/cli-storybook/src/add.test.ts b/code/lib/cli-storybook/src/add.test.ts index 0d5241b033bc..d685798d4d22 100644 --- a/code/lib/cli-storybook/src/add.test.ts +++ b/code/lib/cli-storybook/src/add.test.ts @@ -56,6 +56,7 @@ vi.mock('storybook/internal/common', () => { JsPackageManagerFactory: { getPackageManager: vi.fn(() => MockedPackageManager), }, + syncStorybookAddons: vi.fn(), getCoercedStorybookVersion: vi.fn(() => '8.0.0'), versions: { '@storybook/addon-docs': '^8.0.0', diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts index 3de227e2f765..cfb38417ff1b 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts @@ -1,18 +1,19 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { getAddonNames } from 'storybook/internal/common'; + import { existsSync, readFileSync, writeFileSync } from 'fs'; import * as jscodeshift from 'jscodeshift'; import path from 'path'; import dedent from 'ts-dedent'; -import { getAddonNames } from '../helpers/mainConfigFile'; import { addonA11yAddonTest, transformPreviewFile, transformSetupFile, } from './addon-a11y-addon-test'; -vi.mock('../helpers/mainConfigFile', async (importOriginal) => { +vi.mock('storybook/internal/common', async (importOriginal) => { const mod = (await importOriginal()) as any; return { ...mod, From e2f0eb264b79f3d56fac959ed6f91db9f158e439 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 22 Jan 2025 02:04:34 -0800 Subject: [PATCH 079/144] fix tests on windows --- .../src/common/utils/sync-main-preview-addons.test.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/code/core/src/common/utils/sync-main-preview-addons.test.ts b/code/core/src/common/utils/sync-main-preview-addons.test.ts index 57ef3b033bd1..223b33d10970 100644 --- a/code/core/src/common/utils/sync-main-preview-addons.test.ts +++ b/code/core/src/common/utils/sync-main-preview-addons.test.ts @@ -75,6 +75,8 @@ describe('getSyncedStorybookAddons', () => { `); }); + // necessary for windows and unix output to match in the assertions + const normalizeLineBreaks = (str: string) => str.replace(/\r/g, '').trim(); it('should not add an addon if its annotations path has already been imported', async () => { const originalCode = dedent` import * as addonA11yAnnotations from "@storybook/addon-a11y/preview"; @@ -92,7 +94,9 @@ describe('getSyncedStorybookAddons', () => { }); const result = await getSyncedStorybookAddons(mainConfig, preview); - expect(printConfig(result).code).toEqual(originalCode); + const transformedCode = normalizeLineBreaks(printConfig(result).code); + + expect(transformedCode).toMatch(originalCode); }); it('should not modify the code if all addons are already synced', async () => { @@ -112,6 +116,8 @@ describe('getSyncedStorybookAddons', () => { }); const result = await getSyncedStorybookAddons(mainConfig, preview); - expect(printConfig(result).code).toEqual(originalCode); + const transformedCode = normalizeLineBreaks(printConfig(result).code); + + expect(transformedCode).toMatch(originalCode); }); }); From 78d2d935488c6f4c07f7c4610247b8cab1cd9b9d Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 22 Jan 2025 11:19:49 +0100 Subject: [PATCH 080/144] add some color --- code/core/src/common/utils/sync-main-preview-addons.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/core/src/common/utils/sync-main-preview-addons.ts b/code/core/src/common/utils/sync-main-preview-addons.ts index 636dc5b46f7f..5f2b262fa27a 100644 --- a/code/core/src/common/utils/sync-main-preview-addons.ts +++ b/code/core/src/common/utils/sync-main-preview-addons.ts @@ -4,6 +4,8 @@ import type { StorybookConfig } from '@storybook/types'; import { type ConfigFile, readConfig, writeConfig } from '@storybook/core/csf-tools'; +import picocolors from 'picocolors'; + import { getAddonAnnotations } from './get-addon-annotations'; import { getAddonNames } from './get-addon-names'; @@ -78,7 +80,7 @@ export async function getSyncedStorybookAddons( if (syncedAddons.length > 0) { logger.info( - `Synchronizing addons from main config in ${previewConfig.fileName}:\n${syncedAddons.join(', ')}` + `Synchronizing addons from main config in ${picocolors.cyan(previewConfig.fileName)}:\n${syncedAddons.map(picocolors.magenta).join(', ')}` ); } From 9bbaee4514baba7ec9454e4ec5412e121459bef4 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 22 Jan 2025 18:43:39 +0100 Subject: [PATCH 081/144] Csf: Support named exports as functions --- code/core/src/csf-tools/ConfigFile.test.ts | 11 +++++ code/core/src/csf-tools/ConfigFile.ts | 13 +++++- .../helpers/config-to-csf-factory.test.ts | 15 +++++- .../codemod/helpers/config-to-csf-factory.ts | 46 ++++--------------- .../codemod/helpers/csf-factories-utils.ts | 41 +++++++++++++++++ 5 files changed, 86 insertions(+), 40 deletions(-) diff --git a/code/core/src/csf-tools/ConfigFile.test.ts b/code/core/src/csf-tools/ConfigFile.test.ts index 6661cfb37622..6425a0ced108 100644 --- a/code/core/src/csf-tools/ConfigFile.test.ts +++ b/code/core/src/csf-tools/ConfigFile.test.ts @@ -1466,5 +1466,16 @@ describe('ConfigFile', () => { expect(config._exportDecls['path']).toBe(undefined); expect(config._exports['path']).toBe(undefined); }); + + it('detects const and function export declarations', () => { + const source = dedent` + export function normalFunction() { }; + export const value = ['@storybook/addon-essentials']; + export async function asyncFunction() { }; + `; + const config = loadConfig(source).parse(); + + expect(Object.keys(config._exportDecls)).toHaveLength(3); + }); }); }); diff --git a/code/core/src/csf-tools/ConfigFile.ts b/code/core/src/csf-tools/ConfigFile.ts index d1aecfb7ae63..2b76055fa0f8 100644 --- a/code/core/src/csf-tools/ConfigFile.ts +++ b/code/core/src/csf-tools/ConfigFile.ts @@ -161,7 +161,7 @@ export class ConfigFile { // FIXME: this is a hack. this is only used in the case where the user is // modifying a named export that's a scalar. The _exports map is not suitable // for that. But rather than refactor the whole thing, we just use this as a stopgap. - _exportDecls: Record = {}; + _exportDecls: Record = {}; _exportsObject: t.ObjectExpression | undefined; @@ -238,6 +238,13 @@ export class ConfigFile { self._exportDecls[exportName] = decl; } }); + } else if (t.isFunctionDeclaration(node.declaration)) { + // export function X() {...}; + const decl = node.declaration; + if (t.isIdentifier(decl.id)) { + const { name: exportName } = decl.id; + self._exportDecls[exportName] = decl; + } } else if (node.specifiers) { // export { X }; node.specifiers.forEach((spec) => { @@ -380,7 +387,9 @@ export class ConfigFile { _updateExportNode(rest, expr, exportNode); } else if (exportNode && rest.length === 0 && this._exportDecls[path[0]]) { const decl = this._exportDecls[path[0]]; - decl.init = _makeObjectExpression([], expr); + if (t.isVariableDeclarator(decl)) { + decl.init = _makeObjectExpression([], expr); + } } else if (this.hasDefaultExport) { // This means the main.js of the user has a default export that is not an object expression, therefore we can'types change the AST. throw new Error( diff --git a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts index ee0a31900810..13eade866cbf 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts @@ -58,10 +58,12 @@ describe('main/preview codemod: general parsing functionality', () => { }); `); }); + it('should wrap defineMain call from const declared default export and default export mix', async () => { await expect( transform(dedent` export const tags = []; + export async function viteFinal(config) { return config }; const config = { framework: '@storybook/react-vite', }; @@ -74,6 +76,9 @@ describe('main/preview codemod: general parsing functionality', () => { const config = { framework: '@storybook/react-vite', tags: [], + viteFinal: () => { + return config; + }, }; export default config; @@ -82,16 +87,22 @@ describe('main/preview codemod: general parsing functionality', () => { it('should wrap defineMain call from named exports format', async () => { await expect( transform(dedent` - export const stories = ['../src/**/*.stories.@(js|jsx|ts|tsx)']; + export function stories() { return ['../src/**/*.stories.@(js|jsx|ts|tsx)'] }; export const addons = ['@storybook/addon-essentials']; + export async function viteFinal(config) { return config }; export const framework = '@storybook/react-vite'; `) ).resolves.toMatchInlineSnapshot(` import { defineMain } from '@storybook/react-vite'; export default defineMain({ - stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], + stories: () => { + return ['../src/**/*.stories.@(js|jsx|ts|tsx)']; + }, addons: ['@storybook/addon-essentials'], + viteFinal: () => { + return config; + }, framework: '@storybook/react-vite', }); `); diff --git a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts index 65bc136af80c..db02b0a695ab 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts @@ -7,7 +7,11 @@ import picocolors from 'picocolors'; import type { FileInfo } from '../../automigrate/codemod'; import { logger } from '../csf-factories'; -import { cleanupTypeImports } from './csf-factories-utils'; +import { + cleanupTypeImports, + getConfigProperties, + removeExportDeclarations, +} from './csf-factories-utils'; export async function configToCsfFactory( info: FileInfo, @@ -48,23 +52,10 @@ export async function configToCsfFactory( if (config._exportsObject && hasNamedExports) { const exportDecls = config._exportDecls; - for (const [name, decl] of Object.entries(exportDecls)) { - if (decl.init) { - config._exportsObject.properties.push(t.objectProperty(t.identifier(name), decl.init)); - } - } + const defineConfigProps = getConfigProperties(exportDecls); + config._exportsObject.properties.push(...defineConfigProps); - programNode.body = programNode.body.filter((node) => { - if (t.isExportNamedDeclaration(node) && node.declaration) { - if (t.isVariableDeclaration(node.declaration)) { - node.declaration.declarations = node.declaration.declarations.filter( - (decl) => t.isIdentifier(decl.id) && !exportDecls[decl.id.name] - ); - return node.declaration.declarations.length > 0; - } - } - return true; - }); + programNode.body = removeExportDeclarations(programNode, exportDecls); } else if (config._exportsObject) { /** * Scenario 2: Default exports @@ -124,14 +115,7 @@ export async function configToCsfFactory( * Transform into: export default defineMain({ foo: {}, bar: '' }); */ const exportDecls = config._exportDecls; - const defineConfigProps = []; - - // Collect properties from named exports - for (const [name, decl] of Object.entries(exportDecls)) { - if (decl.init) { - defineConfigProps.push(t.objectProperty(t.identifier(name), decl.init)); - } - } + const defineConfigProps = getConfigProperties(exportDecls); // Construct the `define` call const defineConfigCall = t.callExpression(t.identifier(methodName), [ @@ -139,17 +123,7 @@ export async function configToCsfFactory( ]); // Remove all related named exports - programNode.body = programNode.body.filter((node) => { - if (t.isExportNamedDeclaration(node) && node.declaration) { - if (t.isVariableDeclaration(node.declaration)) { - node.declaration.declarations = node.declaration.declarations.filter( - (decl) => t.isIdentifier(decl.id) && !exportDecls[decl.id.name] - ); - return node.declaration.declarations.length > 0; - } - } - return true; - }); + programNode.body = removeExportDeclarations(programNode, exportDecls); // Add the new export default declaration programNode.body.push(t.exportDefaultDeclaration(defineConfigCall)); diff --git a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts index ef536f09087c..9216841bc314 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts @@ -31,3 +31,44 @@ export function cleanupTypeImports(programNode: t.Program, disallowList: string[ // error TS4058: Return type of exported function has or is using name 'BlockStatement' from external module "/code/core/dist/babel/index" but cannot be named }) as any; } + +export function removeExportDeclarations( + programNode: t.Program, + exportDecls: Record +): t.Statement[] { + return programNode.body.filter((node) => { + if (t.isExportNamedDeclaration(node) && node.declaration) { + if (t.isVariableDeclaration(node.declaration)) { + // Handle variable declarations + node.declaration.declarations = node.declaration.declarations.filter( + (decl) => t.isIdentifier(decl.id) && !exportDecls[decl.id.name] + ); + return node.declaration.declarations.length > 0; + } else if (t.isFunctionDeclaration(node.declaration)) { + // Handle function declarations + const funcDecl = node.declaration; + return t.isIdentifier(funcDecl.id) && !exportDecls[funcDecl.id.name]; + } + } + return true; + }); +} + +export function getConfigProperties( + exportDecls: Record +) { + const properties = []; + + // Collect properties from named exports + for (const [name, decl] of Object.entries(exportDecls)) { + if (t.isVariableDeclarator(decl) && decl.init) { + properties.push(t.objectProperty(t.identifier(name), decl.init)); + } else if (t.isFunctionDeclaration(decl)) { + properties.push( + t.objectProperty(t.identifier(name), t.arrowFunctionExpression([], decl.body)) + ); + } + } + + return properties; +} From 62e4b081a121dd2ecd1c6f947656a16b1d59eccd Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Thu, 23 Jan 2025 10:21:04 +0100 Subject: [PATCH 082/144] work around type issue --- .../src/codemod/helpers/csf-factories-utils.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts index 9216841bc314..2d32386bb4ca 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/csf-factories-utils.ts @@ -35,7 +35,7 @@ export function cleanupTypeImports(programNode: t.Program, disallowList: string[ export function removeExportDeclarations( programNode: t.Program, exportDecls: Record -): t.Statement[] { +) { return programNode.body.filter((node) => { if (t.isExportNamedDeclaration(node) && node.declaration) { if (t.isVariableDeclaration(node.declaration)) { @@ -51,7 +51,9 @@ export function removeExportDeclarations( } } return true; - }); + // @TODO adding any for now, unsure how to fix the following error: + // error TS4058: Return type of exported function has or is using name 'ObjectProperty' from external module "/tmp/storybook/code/core/dist/babel/index" but cannot be named. + }) as any; } export function getConfigProperties( @@ -70,5 +72,7 @@ export function getConfigProperties( } } - return properties; + // @TODO adding any for now, unsure how to fix the following error: + // error TS4058: Return type of exported function has or is using name 'ObjectProperty' from external module "/tmp/storybook/code/core/dist/babel/index" but cannot be named. + return properties as any; } From 27e0406e22ddaebb1f8c6920d2e68fadee66ebf5 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Thu, 23 Jan 2025 14:42:34 +0100 Subject: [PATCH 083/144] Refactor defineMain to be part of /node entrypoint --- code/.storybook/main.ts | 2 +- code/frameworks/angular/package.json | 6 ++++++ code/frameworks/angular/src/index.ts | 1 - .../angular/src/{defineMainConfig.ts => node/index.ts} | 2 +- code/frameworks/ember/package.json | 6 ++++++ code/frameworks/ember/src/index.ts | 1 - .../ember/src/{defineMainConfig.ts => node/index.ts} | 2 +- code/frameworks/experimental-nextjs-vite/package.json | 7 +++++++ code/frameworks/experimental-nextjs-vite/src/index.ts | 1 - .../src/{defineMainConfig.ts => node/index.ts} | 2 +- code/frameworks/html-vite/package.json | 9 ++++++++- code/frameworks/html-vite/src/index.ts | 1 - .../src/{defineMainConfig.ts => node/index.ts} | 2 +- code/frameworks/html-webpack5/package.json | 9 ++++++++- code/frameworks/html-webpack5/src/defineMainConfig.ts | 5 ----- code/frameworks/html-webpack5/src/index.ts | 1 - code/frameworks/html-webpack5/src/node/index.ts | 5 +++++ code/frameworks/nextjs/package.json | 7 +++++++ code/frameworks/nextjs/src/defineMainConfig.ts | 5 ----- code/frameworks/nextjs/src/index.ts | 1 - code/frameworks/nextjs/src/node/index.ts | 5 +++++ code/frameworks/preact-vite/package.json | 6 ++++++ code/frameworks/preact-vite/src/defineMainConfig.ts | 5 ----- code/frameworks/preact-vite/src/index.ts | 1 - code/frameworks/preact-vite/src/node/index.ts | 5 +++++ code/frameworks/preact-webpack5/package.json | 9 ++++++++- .../frameworks/preact-webpack5/src/defineMainConfig.ts | 5 ----- code/frameworks/preact-webpack5/src/index.ts | 1 - code/frameworks/preact-webpack5/src/node/index.ts | 5 +++++ code/frameworks/react-native-web-vite/package.json | 9 ++++++++- .../react-native-web-vite/src/defineMainConfig.ts | 5 ----- code/frameworks/react-native-web-vite/src/index.ts | 1 - .../frameworks/react-native-web-vite/src/node/index.ts | 5 +++++ code/frameworks/react-vite/package.json | 9 ++++++++- code/frameworks/react-vite/src/defineMainConfig.ts | 5 ----- code/frameworks/react-vite/src/index.ts | 1 - code/frameworks/react-vite/src/node/index.ts | 5 +++++ code/frameworks/react-webpack5/package.json | 9 ++++++++- code/frameworks/react-webpack5/src/defineMainConfig.ts | 5 ----- code/frameworks/react-webpack5/src/index.ts | 1 - code/frameworks/react-webpack5/src/node/index.ts | 5 +++++ code/frameworks/server-webpack5/package.json | 9 ++++++++- .../frameworks/server-webpack5/src/defineMainConfig.ts | 5 ----- code/frameworks/server-webpack5/src/index.ts | 1 - code/frameworks/server-webpack5/src/node/index.ts | 5 +++++ code/frameworks/svelte-vite/package.json | 9 ++++++++- code/frameworks/svelte-vite/src/defineMainConfig.ts | 5 ----- code/frameworks/svelte-vite/src/index.ts | 1 - code/frameworks/svelte-vite/src/node/index.ts | 5 +++++ code/frameworks/svelte-webpack5/package.json | 9 ++++++++- .../frameworks/svelte-webpack5/src/defineMainConfig.ts | 5 ----- code/frameworks/svelte-webpack5/src/index.ts | 1 - code/frameworks/svelte-webpack5/src/node/index.ts | 5 +++++ code/frameworks/sveltekit/package.json | 9 ++++++++- code/frameworks/sveltekit/src/defineMainConfig.ts | 5 ----- code/frameworks/sveltekit/src/index.ts | 1 - code/frameworks/sveltekit/src/node/index.ts | 5 +++++ code/frameworks/vue3-vite/package.json | 9 ++++++++- code/frameworks/vue3-vite/src/defineMainConfig.ts | 5 ----- code/frameworks/vue3-vite/src/index.ts | 1 - code/frameworks/vue3-vite/src/node/index.ts | 5 +++++ code/frameworks/vue3-webpack5/package.json | 9 ++++++++- code/frameworks/vue3-webpack5/src/defineMainConfig.ts | 5 ----- code/frameworks/vue3-webpack5/src/index.ts | 1 - code/frameworks/vue3-webpack5/src/node/index.ts | 5 +++++ code/frameworks/web-components-vite/package.json | 7 +++++++ .../web-components-vite/src/defineMainConfig.ts | 5 ----- code/frameworks/web-components-vite/src/index.ts | 1 - code/frameworks/web-components-vite/src/node/index.ts | 5 +++++ code/frameworks/web-components-webpack5/package.json | 9 ++++++++- .../web-components-webpack5/src/defineMainConfig.ts | 5 ----- code/frameworks/web-components-webpack5/src/index.ts | 1 - .../web-components-webpack5/src/node/index.ts | 5 +++++ .../src/codemod/helpers/config-to-csf-factory.test.ts | 10 +++++----- .../src/codemod/helpers/config-to-csf-factory.ts | 2 +- 75 files changed, 229 insertions(+), 118 deletions(-) rename code/frameworks/angular/src/{defineMainConfig.ts => node/index.ts} (62%) rename code/frameworks/ember/src/{defineMainConfig.ts => node/index.ts} (60%) rename code/frameworks/experimental-nextjs-vite/src/{defineMainConfig.ts => node/index.ts} (60%) rename code/frameworks/html-vite/src/{defineMainConfig.ts => node/index.ts} (60%) delete mode 100644 code/frameworks/html-webpack5/src/defineMainConfig.ts create mode 100644 code/frameworks/html-webpack5/src/node/index.ts delete mode 100644 code/frameworks/nextjs/src/defineMainConfig.ts create mode 100644 code/frameworks/nextjs/src/node/index.ts delete mode 100644 code/frameworks/preact-vite/src/defineMainConfig.ts create mode 100644 code/frameworks/preact-vite/src/node/index.ts delete mode 100644 code/frameworks/preact-webpack5/src/defineMainConfig.ts create mode 100644 code/frameworks/preact-webpack5/src/node/index.ts delete mode 100644 code/frameworks/react-native-web-vite/src/defineMainConfig.ts create mode 100644 code/frameworks/react-native-web-vite/src/node/index.ts delete mode 100644 code/frameworks/react-vite/src/defineMainConfig.ts create mode 100644 code/frameworks/react-vite/src/node/index.ts delete mode 100644 code/frameworks/react-webpack5/src/defineMainConfig.ts create mode 100644 code/frameworks/react-webpack5/src/node/index.ts delete mode 100644 code/frameworks/server-webpack5/src/defineMainConfig.ts create mode 100644 code/frameworks/server-webpack5/src/node/index.ts delete mode 100644 code/frameworks/svelte-vite/src/defineMainConfig.ts create mode 100644 code/frameworks/svelte-vite/src/node/index.ts delete mode 100644 code/frameworks/svelte-webpack5/src/defineMainConfig.ts create mode 100644 code/frameworks/svelte-webpack5/src/node/index.ts delete mode 100644 code/frameworks/sveltekit/src/defineMainConfig.ts create mode 100644 code/frameworks/sveltekit/src/node/index.ts delete mode 100644 code/frameworks/vue3-vite/src/defineMainConfig.ts create mode 100644 code/frameworks/vue3-vite/src/node/index.ts delete mode 100644 code/frameworks/vue3-webpack5/src/defineMainConfig.ts create mode 100644 code/frameworks/vue3-webpack5/src/node/index.ts delete mode 100644 code/frameworks/web-components-vite/src/defineMainConfig.ts create mode 100644 code/frameworks/web-components-vite/src/node/index.ts delete mode 100644 code/frameworks/web-components-webpack5/src/defineMainConfig.ts create mode 100644 code/frameworks/web-components-webpack5/src/node/index.ts diff --git a/code/.storybook/main.ts b/code/.storybook/main.ts index 371aa942f5f3..fc9ed6edf6f3 100644 --- a/code/.storybook/main.ts +++ b/code/.storybook/main.ts @@ -1,6 +1,6 @@ import { join } from 'node:path'; -import { defineMain } from '../frameworks/react-vite'; +import { defineMain } from '../frameworks/react-vite/src/node'; const componentsPath = join(__dirname, '../core/src/components'); const managerApiPath = join(__dirname, '../core/src/manager-api'); diff --git a/code/frameworks/angular/package.json b/code/frameworks/angular/package.json index dcb80e7337c0..ddc4b2435f1c 100644 --- a/code/frameworks/angular/package.json +++ b/code/frameworks/angular/package.json @@ -28,6 +28,12 @@ "require": "./dist/index.js" }, "./preset": "./preset.js", + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.js", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", diff --git a/code/frameworks/angular/src/index.ts b/code/frameworks/angular/src/index.ts index d57916a453f4..92f1fac0da0f 100644 --- a/code/frameworks/angular/src/index.ts +++ b/code/frameworks/angular/src/index.ts @@ -1,6 +1,5 @@ export * from './client/index'; export * from './types'; -export * from './defineMainConfig'; /* * ATTENTION: diff --git a/code/frameworks/angular/src/defineMainConfig.ts b/code/frameworks/angular/src/node/index.ts similarity index 62% rename from code/frameworks/angular/src/defineMainConfig.ts rename to code/frameworks/angular/src/node/index.ts index e0bb43b41ff9..16fcde688ae7 100644 --- a/code/frameworks/angular/src/defineMainConfig.ts +++ b/code/frameworks/angular/src/node/index.ts @@ -1,4 +1,4 @@ -import { StorybookConfig } from './types'; +import { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; diff --git a/code/frameworks/ember/package.json b/code/frameworks/ember/package.json index 53c8976de3cd..498c8c23ba82 100644 --- a/code/frameworks/ember/package.json +++ b/code/frameworks/ember/package.json @@ -24,6 +24,12 @@ "require": "./dist/index.js" }, "./preset": "./preset.js", + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.js", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", diff --git a/code/frameworks/ember/src/index.ts b/code/frameworks/ember/src/index.ts index bdebdaee4b44..b821bec98227 100644 --- a/code/frameworks/ember/src/index.ts +++ b/code/frameworks/ember/src/index.ts @@ -1,7 +1,6 @@ /// export * from './types'; -export * from './defineMainConfig'; // optimization: stop HMR propagation in webpack diff --git a/code/frameworks/ember/src/defineMainConfig.ts b/code/frameworks/ember/src/node/index.ts similarity index 60% rename from code/frameworks/ember/src/defineMainConfig.ts rename to code/frameworks/ember/src/node/index.ts index 29be946269ac..bbfba66cc964 100644 --- a/code/frameworks/ember/src/defineMainConfig.ts +++ b/code/frameworks/ember/src/node/index.ts @@ -1,4 +1,4 @@ -import type { StorybookConfig } from './types'; +import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; diff --git a/code/frameworks/experimental-nextjs-vite/package.json b/code/frameworks/experimental-nextjs-vite/package.json index 174b6af31fa6..0026e5078d36 100644 --- a/code/frameworks/experimental-nextjs-vite/package.json +++ b/code/frameworks/experimental-nextjs-vite/package.json @@ -58,6 +58,12 @@ "import": "./dist/vite-plugin/index.mjs", "require": "./dist/vite-plugin/index.js" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -134,6 +140,7 @@ "./src/index.ts", "./src/vite-plugin/index.ts", "./src/preset.ts", + "./src/node/index.ts", "./src/preview.tsx", "./src/export-mocks/cache/index.ts", "./src/export-mocks/headers/index.ts", diff --git a/code/frameworks/experimental-nextjs-vite/src/index.ts b/code/frameworks/experimental-nextjs-vite/src/index.ts index af5050bce57f..32476387c88c 100644 --- a/code/frameworks/experimental-nextjs-vite/src/index.ts +++ b/code/frameworks/experimental-nextjs-vite/src/index.ts @@ -1,7 +1,6 @@ import type vitePluginStorybookNextJs from 'vite-plugin-storybook-nextjs'; export * from './types'; -export * from './defineMainConfig'; export * from './portable-stories'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/code/frameworks/experimental-nextjs-vite/src/defineMainConfig.ts b/code/frameworks/experimental-nextjs-vite/src/node/index.ts similarity index 60% rename from code/frameworks/experimental-nextjs-vite/src/defineMainConfig.ts rename to code/frameworks/experimental-nextjs-vite/src/node/index.ts index 29be946269ac..bbfba66cc964 100644 --- a/code/frameworks/experimental-nextjs-vite/src/defineMainConfig.ts +++ b/code/frameworks/experimental-nextjs-vite/src/node/index.ts @@ -1,4 +1,4 @@ -import type { StorybookConfig } from './types'; +import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; diff --git a/code/frameworks/html-vite/package.json b/code/frameworks/html-vite/package.json index ef544631ec30..7d9ccd3303c7 100644 --- a/code/frameworks/html-vite/package.json +++ b/code/frameworks/html-vite/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -67,7 +73,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/node/index.ts" ], "platform": "node" }, diff --git a/code/frameworks/html-vite/src/index.ts b/code/frameworks/html-vite/src/index.ts index bb8ba9918a0d..fcb073fefcd6 100644 --- a/code/frameworks/html-vite/src/index.ts +++ b/code/frameworks/html-vite/src/index.ts @@ -1,2 +1 @@ export * from './types'; -export * from './defineMainConfig'; diff --git a/code/frameworks/html-vite/src/defineMainConfig.ts b/code/frameworks/html-vite/src/node/index.ts similarity index 60% rename from code/frameworks/html-vite/src/defineMainConfig.ts rename to code/frameworks/html-vite/src/node/index.ts index 29be946269ac..bbfba66cc964 100644 --- a/code/frameworks/html-vite/src/defineMainConfig.ts +++ b/code/frameworks/html-vite/src/node/index.ts @@ -1,4 +1,4 @@ -import type { StorybookConfig } from './types'; +import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; diff --git a/code/frameworks/html-webpack5/package.json b/code/frameworks/html-webpack5/package.json index 0b023a842be0..9f0f69934327 100644 --- a/code/frameworks/html-webpack5/package.json +++ b/code/frameworks/html-webpack5/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -68,7 +74,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/node/index.ts" ], "platform": "node" }, diff --git a/code/frameworks/html-webpack5/src/defineMainConfig.ts b/code/frameworks/html-webpack5/src/defineMainConfig.ts deleted file mode 100644 index 29be946269ac..000000000000 --- a/code/frameworks/html-webpack5/src/defineMainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from './types'; - -export function defineMain(config: StorybookConfig) { - return config; -} diff --git a/code/frameworks/html-webpack5/src/index.ts b/code/frameworks/html-webpack5/src/index.ts index bb8ba9918a0d..fcb073fefcd6 100644 --- a/code/frameworks/html-webpack5/src/index.ts +++ b/code/frameworks/html-webpack5/src/index.ts @@ -1,2 +1 @@ export * from './types'; -export * from './defineMainConfig'; diff --git a/code/frameworks/html-webpack5/src/node/index.ts b/code/frameworks/html-webpack5/src/node/index.ts new file mode 100644 index 000000000000..bbfba66cc964 --- /dev/null +++ b/code/frameworks/html-webpack5/src/node/index.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index 4a472e9ccfee..0a8750f46c0d 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -87,6 +87,12 @@ "import": "./dist/export-mocks/router/index.mjs", "require": "./dist/export-mocks/router/index.js" }, + "./main": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -209,6 +215,7 @@ "./src/image-context.ts", "./src/index.ts", "./src/preset.ts", + "./src/node/index.ts", "./src/preview.tsx", "./src/export-mocks/index.ts", "./src/export-mocks/cache/index.ts", diff --git a/code/frameworks/nextjs/src/defineMainConfig.ts b/code/frameworks/nextjs/src/defineMainConfig.ts deleted file mode 100644 index 29be946269ac..000000000000 --- a/code/frameworks/nextjs/src/defineMainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from './types'; - -export function defineMain(config: StorybookConfig) { - return config; -} diff --git a/code/frameworks/nextjs/src/index.ts b/code/frameworks/nextjs/src/index.ts index 4e3e0a0ec393..a904f93ec89d 100644 --- a/code/frameworks/nextjs/src/index.ts +++ b/code/frameworks/nextjs/src/index.ts @@ -1,3 +1,2 @@ export * from './types'; -export * from './defineMainConfig'; export * from './portable-stories'; diff --git a/code/frameworks/nextjs/src/node/index.ts b/code/frameworks/nextjs/src/node/index.ts new file mode 100644 index 000000000000..bbfba66cc964 --- /dev/null +++ b/code/frameworks/nextjs/src/node/index.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/preact-vite/package.json b/code/frameworks/preact-vite/package.json index 8c246fa936a3..f34830a9cbdb 100644 --- a/code/frameworks/preact-vite/package.json +++ b/code/frameworks/preact-vite/package.json @@ -29,6 +29,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", diff --git a/code/frameworks/preact-vite/src/defineMainConfig.ts b/code/frameworks/preact-vite/src/defineMainConfig.ts deleted file mode 100644 index 29be946269ac..000000000000 --- a/code/frameworks/preact-vite/src/defineMainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from './types'; - -export function defineMain(config: StorybookConfig) { - return config; -} diff --git a/code/frameworks/preact-vite/src/index.ts b/code/frameworks/preact-vite/src/index.ts index bb8ba9918a0d..fcb073fefcd6 100644 --- a/code/frameworks/preact-vite/src/index.ts +++ b/code/frameworks/preact-vite/src/index.ts @@ -1,2 +1 @@ export * from './types'; -export * from './defineMainConfig'; diff --git a/code/frameworks/preact-vite/src/node/index.ts b/code/frameworks/preact-vite/src/node/index.ts new file mode 100644 index 000000000000..bbfba66cc964 --- /dev/null +++ b/code/frameworks/preact-vite/src/node/index.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/preact-webpack5/package.json b/code/frameworks/preact-webpack5/package.json index 690a225ba22b..8b2a885cd02a 100644 --- a/code/frameworks/preact-webpack5/package.json +++ b/code/frameworks/preact-webpack5/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -69,7 +75,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/node/index.ts" ], "platform": "node" }, diff --git a/code/frameworks/preact-webpack5/src/defineMainConfig.ts b/code/frameworks/preact-webpack5/src/defineMainConfig.ts deleted file mode 100644 index 29be946269ac..000000000000 --- a/code/frameworks/preact-webpack5/src/defineMainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from './types'; - -export function defineMain(config: StorybookConfig) { - return config; -} diff --git a/code/frameworks/preact-webpack5/src/index.ts b/code/frameworks/preact-webpack5/src/index.ts index bb8ba9918a0d..fcb073fefcd6 100644 --- a/code/frameworks/preact-webpack5/src/index.ts +++ b/code/frameworks/preact-webpack5/src/index.ts @@ -1,2 +1 @@ export * from './types'; -export * from './defineMainConfig'; diff --git a/code/frameworks/preact-webpack5/src/node/index.ts b/code/frameworks/preact-webpack5/src/node/index.ts new file mode 100644 index 000000000000..bbfba66cc964 --- /dev/null +++ b/code/frameworks/preact-webpack5/src/node/index.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/react-native-web-vite/package.json b/code/frameworks/react-native-web-vite/package.json index baf76151ef3d..ef6f426df94b 100644 --- a/code/frameworks/react-native-web-vite/package.json +++ b/code/frameworks/react-native-web-vite/package.json @@ -35,6 +35,12 @@ "import": "./dist/vite-plugin.mjs", "require": "./dist/vite-plugin.js" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -87,7 +93,8 @@ "entries": [ "./src/index.ts", "./src/preset.ts", - "./src/vite-plugin.ts" + "./src/vite-plugin.ts", + "./src/node/index.ts" ], "platform": "node" }, diff --git a/code/frameworks/react-native-web-vite/src/defineMainConfig.ts b/code/frameworks/react-native-web-vite/src/defineMainConfig.ts deleted file mode 100644 index 29be946269ac..000000000000 --- a/code/frameworks/react-native-web-vite/src/defineMainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from './types'; - -export function defineMain(config: StorybookConfig) { - return config; -} diff --git a/code/frameworks/react-native-web-vite/src/index.ts b/code/frameworks/react-native-web-vite/src/index.ts index c316aff16a2c..1855ad61a70b 100644 --- a/code/frameworks/react-native-web-vite/src/index.ts +++ b/code/frameworks/react-native-web-vite/src/index.ts @@ -1,2 +1 @@ export type { FrameworkOptions, StorybookConfig } from './types'; -export * from './defineMainConfig'; diff --git a/code/frameworks/react-native-web-vite/src/node/index.ts b/code/frameworks/react-native-web-vite/src/node/index.ts new file mode 100644 index 000000000000..bbfba66cc964 --- /dev/null +++ b/code/frameworks/react-native-web-vite/src/node/index.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/react-vite/package.json b/code/frameworks/react-vite/package.json index f8a41b141f4c..c5bdb57076aa 100644 --- a/code/frameworks/react-vite/package.json +++ b/code/frameworks/react-vite/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -93,7 +99,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/node/index.ts" ], "platform": "node" }, diff --git a/code/frameworks/react-vite/src/defineMainConfig.ts b/code/frameworks/react-vite/src/defineMainConfig.ts deleted file mode 100644 index 29be946269ac..000000000000 --- a/code/frameworks/react-vite/src/defineMainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from './types'; - -export function defineMain(config: StorybookConfig) { - return config; -} diff --git a/code/frameworks/react-vite/src/index.ts b/code/frameworks/react-vite/src/index.ts index bb8ba9918a0d..fcb073fefcd6 100644 --- a/code/frameworks/react-vite/src/index.ts +++ b/code/frameworks/react-vite/src/index.ts @@ -1,2 +1 @@ export * from './types'; -export * from './defineMainConfig'; diff --git a/code/frameworks/react-vite/src/node/index.ts b/code/frameworks/react-vite/src/node/index.ts new file mode 100644 index 000000000000..bbfba66cc964 --- /dev/null +++ b/code/frameworks/react-vite/src/node/index.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/react-webpack5/package.json b/code/frameworks/react-webpack5/package.json index 00d8a6469753..3c0cef079983 100644 --- a/code/frameworks/react-webpack5/package.json +++ b/code/frameworks/react-webpack5/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -74,7 +80,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/node/index.ts" ], "platform": "node" }, diff --git a/code/frameworks/react-webpack5/src/defineMainConfig.ts b/code/frameworks/react-webpack5/src/defineMainConfig.ts deleted file mode 100644 index 29be946269ac..000000000000 --- a/code/frameworks/react-webpack5/src/defineMainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from './types'; - -export function defineMain(config: StorybookConfig) { - return config; -} diff --git a/code/frameworks/react-webpack5/src/index.ts b/code/frameworks/react-webpack5/src/index.ts index bb8ba9918a0d..fcb073fefcd6 100644 --- a/code/frameworks/react-webpack5/src/index.ts +++ b/code/frameworks/react-webpack5/src/index.ts @@ -1,2 +1 @@ export * from './types'; -export * from './defineMainConfig'; diff --git a/code/frameworks/react-webpack5/src/node/index.ts b/code/frameworks/react-webpack5/src/node/index.ts new file mode 100644 index 000000000000..bbfba66cc964 --- /dev/null +++ b/code/frameworks/react-webpack5/src/node/index.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/server-webpack5/package.json b/code/frameworks/server-webpack5/package.json index 7b5e6f5c6c7e..3a4066e71aec 100644 --- a/code/frameworks/server-webpack5/package.json +++ b/code/frameworks/server-webpack5/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -67,7 +73,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/node/index.ts" ], "platform": "node" }, diff --git a/code/frameworks/server-webpack5/src/defineMainConfig.ts b/code/frameworks/server-webpack5/src/defineMainConfig.ts deleted file mode 100644 index 29be946269ac..000000000000 --- a/code/frameworks/server-webpack5/src/defineMainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from './types'; - -export function defineMain(config: StorybookConfig) { - return config; -} diff --git a/code/frameworks/server-webpack5/src/index.ts b/code/frameworks/server-webpack5/src/index.ts index bb8ba9918a0d..fcb073fefcd6 100644 --- a/code/frameworks/server-webpack5/src/index.ts +++ b/code/frameworks/server-webpack5/src/index.ts @@ -1,2 +1 @@ export * from './types'; -export * from './defineMainConfig'; diff --git a/code/frameworks/server-webpack5/src/node/index.ts b/code/frameworks/server-webpack5/src/node/index.ts new file mode 100644 index 000000000000..bbfba66cc964 --- /dev/null +++ b/code/frameworks/server-webpack5/src/node/index.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/svelte-vite/package.json b/code/frameworks/svelte-vite/package.json index 907e074a1415..b42f5019288e 100644 --- a/code/frameworks/svelte-vite/package.json +++ b/code/frameworks/svelte-vite/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -78,7 +84,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/node/index.ts" ], "platform": "node" }, diff --git a/code/frameworks/svelte-vite/src/defineMainConfig.ts b/code/frameworks/svelte-vite/src/defineMainConfig.ts deleted file mode 100644 index 29be946269ac..000000000000 --- a/code/frameworks/svelte-vite/src/defineMainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from './types'; - -export function defineMain(config: StorybookConfig) { - return config; -} diff --git a/code/frameworks/svelte-vite/src/index.ts b/code/frameworks/svelte-vite/src/index.ts index bb8ba9918a0d..fcb073fefcd6 100644 --- a/code/frameworks/svelte-vite/src/index.ts +++ b/code/frameworks/svelte-vite/src/index.ts @@ -1,2 +1 @@ export * from './types'; -export * from './defineMainConfig'; diff --git a/code/frameworks/svelte-vite/src/node/index.ts b/code/frameworks/svelte-vite/src/node/index.ts new file mode 100644 index 000000000000..bbfba66cc964 --- /dev/null +++ b/code/frameworks/svelte-vite/src/node/index.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/svelte-webpack5/package.json b/code/frameworks/svelte-webpack5/package.json index af8595e8631c..9903ef1c3e1c 100644 --- a/code/frameworks/svelte-webpack5/package.json +++ b/code/frameworks/svelte-webpack5/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -70,7 +76,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/node/index.ts" ], "platform": "node" }, diff --git a/code/frameworks/svelte-webpack5/src/defineMainConfig.ts b/code/frameworks/svelte-webpack5/src/defineMainConfig.ts deleted file mode 100644 index 29be946269ac..000000000000 --- a/code/frameworks/svelte-webpack5/src/defineMainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from './types'; - -export function defineMain(config: StorybookConfig) { - return config; -} diff --git a/code/frameworks/svelte-webpack5/src/index.ts b/code/frameworks/svelte-webpack5/src/index.ts index bb8ba9918a0d..fcb073fefcd6 100644 --- a/code/frameworks/svelte-webpack5/src/index.ts +++ b/code/frameworks/svelte-webpack5/src/index.ts @@ -1,2 +1 @@ export * from './types'; -export * from './defineMainConfig'; diff --git a/code/frameworks/svelte-webpack5/src/node/index.ts b/code/frameworks/svelte-webpack5/src/node/index.ts new file mode 100644 index 000000000000..bbfba66cc964 --- /dev/null +++ b/code/frameworks/svelte-webpack5/src/node/index.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/sveltekit/package.json b/code/frameworks/sveltekit/package.json index e8a101b0a40d..1ed75b6cf003 100644 --- a/code/frameworks/sveltekit/package.json +++ b/code/frameworks/sveltekit/package.json @@ -41,6 +41,12 @@ "require": "./dist/vite-plugin.js", "import": "./dist/vite-plugin.mjs" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -84,7 +90,8 @@ "./src/index.ts", "./src/preview.ts", "./src/preset.ts", - "./src/vite-plugin.ts" + "./src/vite-plugin.ts", + "./src/node/index.ts" ], "platform": "node" }, diff --git a/code/frameworks/sveltekit/src/defineMainConfig.ts b/code/frameworks/sveltekit/src/defineMainConfig.ts deleted file mode 100644 index 29be946269ac..000000000000 --- a/code/frameworks/sveltekit/src/defineMainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from './types'; - -export function defineMain(config: StorybookConfig) { - return config; -} diff --git a/code/frameworks/sveltekit/src/index.ts b/code/frameworks/sveltekit/src/index.ts index 4e3e0a0ec393..a904f93ec89d 100644 --- a/code/frameworks/sveltekit/src/index.ts +++ b/code/frameworks/sveltekit/src/index.ts @@ -1,3 +1,2 @@ export * from './types'; -export * from './defineMainConfig'; export * from './portable-stories'; diff --git a/code/frameworks/sveltekit/src/node/index.ts b/code/frameworks/sveltekit/src/node/index.ts new file mode 100644 index 000000000000..bbfba66cc964 --- /dev/null +++ b/code/frameworks/sveltekit/src/node/index.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/vue3-vite/package.json b/code/frameworks/vue3-vite/package.json index b433075ce13e..07abb65e2654 100644 --- a/code/frameworks/vue3-vite/package.json +++ b/code/frameworks/vue3-vite/package.json @@ -35,6 +35,12 @@ "require": "./dist/vite-plugin.js", "import": "./dist/vite-plugin.mjs" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -80,7 +86,8 @@ "entries": [ "./src/index.ts", "./src/preset.ts", - "./src/vite-plugin.ts" + "./src/vite-plugin.ts", + "./src/node/index.ts" ], "platform": "node" }, diff --git a/code/frameworks/vue3-vite/src/defineMainConfig.ts b/code/frameworks/vue3-vite/src/defineMainConfig.ts deleted file mode 100644 index 29be946269ac..000000000000 --- a/code/frameworks/vue3-vite/src/defineMainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from './types'; - -export function defineMain(config: StorybookConfig) { - return config; -} diff --git a/code/frameworks/vue3-vite/src/index.ts b/code/frameworks/vue3-vite/src/index.ts index bb8ba9918a0d..fcb073fefcd6 100644 --- a/code/frameworks/vue3-vite/src/index.ts +++ b/code/frameworks/vue3-vite/src/index.ts @@ -1,2 +1 @@ export * from './types'; -export * from './defineMainConfig'; diff --git a/code/frameworks/vue3-vite/src/node/index.ts b/code/frameworks/vue3-vite/src/node/index.ts new file mode 100644 index 000000000000..bbfba66cc964 --- /dev/null +++ b/code/frameworks/vue3-vite/src/node/index.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/vue3-webpack5/package.json b/code/frameworks/vue3-webpack5/package.json index 8208e1195d92..0532d367d8f9 100644 --- a/code/frameworks/vue3-webpack5/package.json +++ b/code/frameworks/vue3-webpack5/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -71,7 +77,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/node/index.ts" ], "platform": "node" }, diff --git a/code/frameworks/vue3-webpack5/src/defineMainConfig.ts b/code/frameworks/vue3-webpack5/src/defineMainConfig.ts deleted file mode 100644 index 29be946269ac..000000000000 --- a/code/frameworks/vue3-webpack5/src/defineMainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from './types'; - -export function defineMain(config: StorybookConfig) { - return config; -} diff --git a/code/frameworks/vue3-webpack5/src/index.ts b/code/frameworks/vue3-webpack5/src/index.ts index bb8ba9918a0d..fcb073fefcd6 100644 --- a/code/frameworks/vue3-webpack5/src/index.ts +++ b/code/frameworks/vue3-webpack5/src/index.ts @@ -1,2 +1 @@ export * from './types'; -export * from './defineMainConfig'; diff --git a/code/frameworks/vue3-webpack5/src/node/index.ts b/code/frameworks/vue3-webpack5/src/node/index.ts new file mode 100644 index 000000000000..bbfba66cc964 --- /dev/null +++ b/code/frameworks/vue3-webpack5/src/node/index.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/web-components-vite/package.json b/code/frameworks/web-components-vite/package.json index 316058f80164..c99c910ba800 100644 --- a/code/frameworks/web-components-vite/package.json +++ b/code/frameworks/web-components-vite/package.json @@ -30,6 +30,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -66,6 +72,7 @@ }, "bundler": { "entries": [ + "./src/node/index.ts", "./src/index.ts", "./src/preset.ts" ], diff --git a/code/frameworks/web-components-vite/src/defineMainConfig.ts b/code/frameworks/web-components-vite/src/defineMainConfig.ts deleted file mode 100644 index 29be946269ac..000000000000 --- a/code/frameworks/web-components-vite/src/defineMainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from './types'; - -export function defineMain(config: StorybookConfig) { - return config; -} diff --git a/code/frameworks/web-components-vite/src/index.ts b/code/frameworks/web-components-vite/src/index.ts index bb8ba9918a0d..fcb073fefcd6 100644 --- a/code/frameworks/web-components-vite/src/index.ts +++ b/code/frameworks/web-components-vite/src/index.ts @@ -1,2 +1 @@ export * from './types'; -export * from './defineMainConfig'; diff --git a/code/frameworks/web-components-vite/src/node/index.ts b/code/frameworks/web-components-vite/src/node/index.ts new file mode 100644 index 000000000000..bbfba66cc964 --- /dev/null +++ b/code/frameworks/web-components-vite/src/node/index.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/frameworks/web-components-webpack5/package.json b/code/frameworks/web-components-webpack5/package.json index 20ca68943b95..29a567cf29ee 100644 --- a/code/frameworks/web-components-webpack5/package.json +++ b/code/frameworks/web-components-webpack5/package.json @@ -33,6 +33,12 @@ "types": "./dist/preset.d.ts", "require": "./dist/preset.js" }, + "./node": { + "types": "./dist/node/index.d.ts", + "node": "./dist/node/index.js", + "import": "./dist/node/index.mjs", + "require": "./dist/node/index.js" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -71,7 +77,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/preset.ts" + "./src/preset.ts", + "./src/node/index.ts" ], "platform": "node" }, diff --git a/code/frameworks/web-components-webpack5/src/defineMainConfig.ts b/code/frameworks/web-components-webpack5/src/defineMainConfig.ts deleted file mode 100644 index 29be946269ac..000000000000 --- a/code/frameworks/web-components-webpack5/src/defineMainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StorybookConfig } from './types'; - -export function defineMain(config: StorybookConfig) { - return config; -} diff --git a/code/frameworks/web-components-webpack5/src/index.ts b/code/frameworks/web-components-webpack5/src/index.ts index bb8ba9918a0d..fcb073fefcd6 100644 --- a/code/frameworks/web-components-webpack5/src/index.ts +++ b/code/frameworks/web-components-webpack5/src/index.ts @@ -1,2 +1 @@ export * from './types'; -export * from './defineMainConfig'; diff --git a/code/frameworks/web-components-webpack5/src/node/index.ts b/code/frameworks/web-components-webpack5/src/node/index.ts new file mode 100644 index 000000000000..bbfba66cc964 --- /dev/null +++ b/code/frameworks/web-components-webpack5/src/node/index.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '../types'; + +export function defineMain(config: StorybookConfig) { + return config; +} diff --git a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts index 13eade866cbf..8863f5f30c61 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts @@ -28,7 +28,7 @@ describe('main/preview codemod: general parsing functionality', () => { }; `) ).resolves.toMatchInlineSnapshot(` - import { defineMain } from '@storybook/react-vite'; + import { defineMain } from '@storybook/react-vite/node'; export default defineMain({ stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], @@ -49,7 +49,7 @@ describe('main/preview codemod: general parsing functionality', () => { export default config; `) ).resolves.toMatchInlineSnapshot(` - import { defineMain } from '@storybook/react-vite'; + import { defineMain } from '@storybook/react-vite/node'; export default defineMain({ stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], @@ -71,7 +71,7 @@ describe('main/preview codemod: general parsing functionality', () => { export default config; `) ).resolves.toMatchInlineSnapshot(` - import { defineMain } from '@storybook/react-vite'; + import { defineMain } from '@storybook/react-vite/node'; const config = { framework: '@storybook/react-vite', @@ -93,7 +93,7 @@ describe('main/preview codemod: general parsing functionality', () => { export const framework = '@storybook/react-vite'; `) ).resolves.toMatchInlineSnapshot(` - import { defineMain } from '@storybook/react-vite'; + import { defineMain } from '@storybook/react-vite/node'; export default defineMain({ stories: () => { @@ -130,7 +130,7 @@ describe('main/preview codemod: general parsing functionality', () => { export default config; `) ).resolves.toMatchInlineSnapshot(` - import { defineMain } from '@storybook/react-vite'; + import { defineMain } from '@storybook/react-vite/node'; export default defineMain({ stories: [], diff --git a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts index db02b0a695ab..103415ac0694 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts @@ -131,7 +131,7 @@ export async function configToCsfFactory( const configImport = t.importDeclaration( [t.importSpecifier(t.identifier(methodName), t.identifier(methodName))], - t.stringLiteral(frameworkPackage + `${configType === 'preview' ? '/preview' : ''}`) + t.stringLiteral(frameworkPackage + `${configType === 'main' ? '/node' : '/preview'}`) ); // Check whether @storybook/framework import already exists From bf9f008719cd50ea8b22d19c031c6f2d06b8c2c2 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Thu, 23 Jan 2025 14:46:02 +0100 Subject: [PATCH 084/144] React: Refactor definePreview utility to index export --- code/.storybook/preview.tsx | 2 +- .../src/codemod/helpers/config-to-csf-factory.test.ts | 4 ++-- .../src/codemod/helpers/config-to-csf-factory.ts | 2 +- code/renderers/react/src/index.ts | 2 ++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index a4f48da6580f..de55a044b2ae 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -18,7 +18,7 @@ import { import { DocsContext } from '@storybook/blocks'; import { global } from '@storybook/global'; import type { Decorator, Loader, ReactRenderer } from '@storybook/react'; -import { definePreview } from '@storybook/react/preview'; +import { definePreview } from '@storybook/react'; // TODO add empty preview // import * as storysource from '@storybook/addon-storysource'; diff --git a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts index 8863f5f30c61..85174836901b 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts @@ -156,7 +156,7 @@ describe('preview specific functionality', () => { }; `) ).resolves.toMatchInlineSnapshot(` - import { definePreview } from '@storybook/react/preview'; + import { definePreview } from '@storybook/react'; export default definePreview({ tags: ['test'], @@ -175,7 +175,7 @@ describe('preview specific functionality', () => { export default preview; `) ).resolves.toMatchInlineSnapshot(` - import { definePreview } from '@storybook/react/preview'; + import { definePreview } from '@storybook/react'; export default definePreview({ tags: [], diff --git a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts index 103415ac0694..4231c3a6fd2a 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts @@ -131,7 +131,7 @@ export async function configToCsfFactory( const configImport = t.importDeclaration( [t.importSpecifier(t.identifier(methodName), t.identifier(methodName))], - t.stringLiteral(frameworkPackage + `${configType === 'main' ? '/node' : '/preview'}`) + t.stringLiteral(frameworkPackage + `${configType === 'main' ? '/node' : ''}`) ); // Check whether @storybook/framework import already exists diff --git a/code/renderers/react/src/index.ts b/code/renderers/react/src/index.ts index 263d7546f10e..66c491981bce 100644 --- a/code/renderers/react/src/index.ts +++ b/code/renderers/react/src/index.ts @@ -5,6 +5,8 @@ export * from './public-types'; export * from './portable-stories'; +export { definePreview } from './preview'; + // optimization: stop HMR propagation in webpack // optimization: stop HMR propagation in webpack From 5e0e2c2939841abf04a613816ce544dfdf28f060 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Thu, 23 Jan 2025 15:12:34 +0100 Subject: [PATCH 085/144] move .annotation to .input --- code/.storybook/storybook.setup.ts | 2 +- .../test/src/vitest-plugin/test-utils.ts | 6 +-- .../src/codegen-modern-iframe-script.ts | 2 +- .../common/utils/sync-main-preview-addons.ts | 1 - code/core/src/csf-tools/enrichCsf.test.ts | 8 ++-- code/core/src/csf-tools/enrichCsf.ts | 4 +- .../preview-web/docs-context/DocsContext.ts | 2 +- .../modules/store/csf/portable-stories.ts | 6 +-- .../modules/store/csf/processCSFFile.ts | 6 +-- .../portable-stories-factory.test.tsx | 44 +++++++++---------- .../react/src/csf-factories.test.tsx | 2 +- code/renderers/react/src/preview.tsx | 10 ++--- scripts/tasks/sandbox-parts.ts | 2 +- 13 files changed, 47 insertions(+), 48 deletions(-) diff --git a/code/.storybook/storybook.setup.ts b/code/.storybook/storybook.setup.ts index 8408bfe53160..3521dcafb8cc 100644 --- a/code/.storybook/storybook.setup.ts +++ b/code/.storybook/storybook.setup.ts @@ -8,7 +8,7 @@ import { config } from './preview'; vi.spyOn(console, 'warn').mockImplementation((...args) => console.log(...args)); const annotations = setProjectAnnotations([ - config.annotations, + config.input, { // experiment with injecting Vitest's interactivity API over our userEvent while tests run in browser mode // https://vitest.dev/guide/browser/interactivity-api.html diff --git a/code/addons/test/src/vitest-plugin/test-utils.ts b/code/addons/test/src/vitest-plugin/test-utils.ts index dc206af2a5e6..4d757a3404ba 100644 --- a/code/addons/test/src/vitest-plugin/test-utils.ts +++ b/code/addons/test/src/vitest-plugin/test-utils.ts @@ -27,10 +27,10 @@ export const testStory = ( ) => { return async (context: TestContext & { story: ComposedStoryFn }) => { const composedStory = composeStory( - 'isCSFFactory' in story ? (story as any).annotations : story, - 'isCSFFactory' in story ? (meta as any).annotations : meta, + 'isCSFFactory' in story ? (story as any).input : story, + 'isCSFFactory' in story ? (meta as any).input : meta, { initialGlobals: (await getInitialGlobals?.()) ?? {}, tags: await getTags?.() }, - 'isCSFFactory' in story ? (story as any).config?.annotations : undefined, + 'isCSFFactory' in story ? (story as any).config?.input : undefined, exportName ); diff --git a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts index 7a17d5642447..02316bf05582 100644 --- a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts +++ b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts @@ -29,7 +29,7 @@ export async function generateModernIframeScriptCode(options: Options, projectRo }); if (csfFactoryPreview) { - return csfFactoryPreview.annotations; + return csfFactoryPreview.input; } const configs = await Promise.all([${previewAnnotationURLs diff --git a/code/core/src/common/utils/sync-main-preview-addons.ts b/code/core/src/common/utils/sync-main-preview-addons.ts index 5f2b262fa27a..c0618705836a 100644 --- a/code/core/src/common/utils/sync-main-preview-addons.ts +++ b/code/core/src/common/utils/sync-main-preview-addons.ts @@ -27,7 +27,6 @@ export async function getSyncedStorybookAddons( return ( t.isImportDeclaration(node) && node.source.value.includes('@storybook') && - node.source.value.endsWith('/preview') && node.specifiers.some((specifier) => { return ( t.isImportSpecifier(specifier) && diff --git a/code/core/src/csf-tools/enrichCsf.test.ts b/code/core/src/csf-tools/enrichCsf.test.ts index e46c908bd658..a54a6373f489 100644 --- a/code/core/src/csf-tools/enrichCsf.test.ts +++ b/code/core/src/csf-tools/enrichCsf.test.ts @@ -182,13 +182,13 @@ describe('enrichCsf', () => { } }); export const Story = meta.story({}); - Story.annotations.parameters = { - ...Story.annotations.parameters, + Story.input.parameters = { + ...Story.input.parameters, docs: { - ...Story.annotations.parameters?.docs, + ...Story.input.parameters?.docs, source: { originalSource: "meta.story({})", - ...Story.annotations.parameters?.docs?.source + ...Story.input.parameters?.docs?.source } } }; diff --git a/code/core/src/csf-tools/enrichCsf.ts b/code/core/src/csf-tools/enrichCsf.ts index 11b1a239c7ae..e920a2e816da 100644 --- a/code/core/src/csf-tools/enrichCsf.ts +++ b/code/core/src/csf-tools/enrichCsf.ts @@ -24,9 +24,9 @@ export const enrichCsfStory = ( const description = !options?.disableDescription && extractDescription(csfSource._storyStatements[key]); const parameters = []; - // in csf 1/2/3 use Story.parameters; CSF factories use Story.annotations.parameters + // in csf 1/2/3 use Story.parameters; CSF factories use Story.input.parameters const baseStoryObject = isCsfFactory - ? t.memberExpression(t.identifier(key), t.identifier('annotations')) + ? t.memberExpression(t.identifier(key), t.identifier('input')) : t.identifier(key); const originalParameters = t.memberExpression(baseStoryObject, t.identifier('parameters')); parameters.push(t.spreadElement(originalParameters)); diff --git a/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts b/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts index 0f26f4f04edc..b5fc421e5364 100644 --- a/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts +++ b/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts @@ -163,7 +163,7 @@ export class DocsContext implements DocsContextProps } const story = this.exportToStory.get( - 'isCSFFactory' in moduleExportOrType ? moduleExportOrType.annotations : moduleExportOrType + 'isCSFFactory' in moduleExportOrType ? moduleExportOrType.input : moduleExportOrType ); if (story) { 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 88bf9a4eee92..43b3f385e7f7 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 @@ -98,9 +98,9 @@ export function getAnnotations( // CSF4 if (!defaultExport && 'isCSFFactory' in firstStory) { const meta: NormalizedComponentAnnotations = - normalizeComponentAnnotations(firstStory.meta.annotations, title, importPath); + normalizeComponentAnnotations(firstStory.meta.input, title, importPath); checkDisallowedParameters(meta.parameters); const csfFile: CSFFile = { meta, stories: {}, moduleExports }; Object.keys(namedExports).forEach((key) => { if (isExportStory(key, meta)) { - const storyMeta = normalizeStory(key, namedExports[key].annotations, meta); + const storyMeta = normalizeStory(key, namedExports[key].input, meta); checkDisallowedParameters(storyMeta.parameters); csfFile.stories[storyMeta.id] = storyMeta; } }); - csfFile.projectAnnotations = firstStory.config.annotations; + csfFile.projectAnnotations = firstStory.config.input; return csfFile; } diff --git a/code/renderers/react/src/__test__/portable-stories-factory.test.tsx b/code/renderers/react/src/__test__/portable-stories-factory.test.tsx index e74f94886f23..6accc1bf88bc 100644 --- a/code/renderers/react/src/__test__/portable-stories-factory.test.tsx +++ b/code/renderers/react/src/__test__/portable-stories-factory.test.tsx @@ -21,8 +21,8 @@ import * as ButtonStories from './Button.csf4.stories'; import * as ComponentWithErrorStories from './ComponentWithError.stories'; const HooksStory = composeStory( - ButtonStories.HooksStory.annotations, - ButtonStories.CSF3Primary.meta.annotations + ButtonStories.HooksStory.input, + ButtonStories.CSF3Primary.meta.input ); const projectAnnotations = setProjectAnnotations([]); @@ -43,8 +43,8 @@ afterEach(() => { // example with composeStory, returns a single story composed with args/decorators const Secondary = composeStory( - ButtonStories.CSF2Secondary.annotations, - ButtonStories.CSF3Primary.meta.annotations + ButtonStories.CSF2Secondary.input, + ButtonStories.CSF3Primary.meta.input ); describe('renders', () => { it('renders primary button', () => { @@ -109,8 +109,8 @@ describe('projectAnnotations', () => { }, ]); const WithEnglishText = composeStory( - ButtonStories.CSF2StoryWithLocale.annotations, - ButtonStories.CSF3Primary.meta.annotations + ButtonStories.CSF2StoryWithLocale.input, + ButtonStories.CSF3Primary.meta.input ); const { getByText } = render(); const buttonElement = getByText('Hello!'); @@ -120,8 +120,8 @@ describe('projectAnnotations', () => { it('renders with custom projectAnnotations via composeStory params', () => { const WithPortugueseText = composeStory( - ButtonStories.CSF2StoryWithLocale.annotations, - ButtonStories.CSF3Primary.meta.annotations, + ButtonStories.CSF2StoryWithLocale.input, + ButtonStories.CSF3Primary.meta.input, { initialGlobals: { locale: 'pt' }, } @@ -133,8 +133,8 @@ describe('projectAnnotations', () => { it('has action arg from argTypes when addon-actions annotations are added', () => { const Story = composeStory( - ButtonStories.WithActionArgType.annotations, - ButtonStories.CSF3Primary.meta.annotations, + ButtonStories.WithActionArgType.input, + ButtonStories.CSF3Primary.meta.input, addonActionsPreview as ProjectAnnotations ); @@ -146,8 +146,8 @@ describe('projectAnnotations', () => { describe('CSF3', () => { it('renders with inferred globalRender', () => { const Primary = composeStory( - ButtonStories.CSF3Button.annotations, - ButtonStories.CSF3Primary.meta.annotations + ButtonStories.CSF3Button.input, + ButtonStories.CSF3Primary.meta.input ); render(Hello world); @@ -157,8 +157,8 @@ describe('CSF3', () => { it('renders with custom render function', () => { const Primary = composeStory( - ButtonStories.CSF3ButtonWithRender.annotations, - ButtonStories.CSF3Primary.meta.annotations + ButtonStories.CSF3ButtonWithRender.input, + ButtonStories.CSF3Primary.meta.input ); render(); @@ -167,8 +167,8 @@ describe('CSF3', () => { it('renders with play function without canvas element', async () => { const CSF3InputFieldFilled = composeStory( - ButtonStories.CSF3InputFieldFilled.annotations, - ButtonStories.CSF3Primary.meta.annotations + ButtonStories.CSF3InputFieldFilled.input, + ButtonStories.CSF3Primary.meta.input ); await CSF3InputFieldFilled.run(); @@ -178,8 +178,8 @@ describe('CSF3', () => { it('renders with play function with canvas element', async () => { const CSF3InputFieldFilled = composeStory( - ButtonStories.CSF3InputFieldFilled.annotations, - ButtonStories.CSF3Primary.meta.annotations + ButtonStories.CSF3InputFieldFilled.input, + ButtonStories.CSF3Primary.meta.input ); let divElement; @@ -209,8 +209,8 @@ describe('CSF3', () => { // common in addons that need to communicate between manager and preview it('should pass with decorators that need addons channel', () => { const PrimaryWithChannels = composeStory( - ButtonStories.CSF3Primary.annotations, - ButtonStories.CSF3Primary.meta.annotations, + ButtonStories.CSF3Primary.input, + ButtonStories.CSF3Primary.meta.input, { decorators: [ (StoryFn: any) => { @@ -232,12 +232,12 @@ describe('ComposeStories types', () => { expectTypeOf({ ...ButtonStories, - default: ButtonStories.CSF3Primary.meta.annotations as Meta, + default: ButtonStories.CSF3Primary.meta.input as Meta, }).toMatchTypeOf(); expectTypeOf({ ...ButtonStories, - default: ButtonStories.CSF3Primary.meta.annotations satisfies Meta, + default: ButtonStories.CSF3Primary.meta.input satisfies Meta, }).toMatchTypeOf(); }); }); diff --git a/code/renderers/react/src/csf-factories.test.tsx b/code/renderers/react/src/csf-factories.test.tsx index 2bceb23a53c6..3b1928051eb4 100644 --- a/code/renderers/react/src/csf-factories.test.tsx +++ b/code/renderers/react/src/csf-factories.test.tsx @@ -20,5 +20,5 @@ test('csf factories', () => { }, }); - expect(MyStory.annotations.args?.children).toBe('Hello world'); + expect(MyStory.input.args?.children).toBe('Hello world'); }); diff --git a/code/renderers/react/src/preview.tsx b/code/renderers/react/src/preview.tsx index 9230029d907c..40fdb3307316 100644 --- a/code/renderers/react/src/preview.tsx +++ b/code/renderers/react/src/preview.tsx @@ -29,11 +29,11 @@ interface PreviewConfigData extends ProjectAnnotatio } class PreviewConfig { - readonly annotations: NormalizedProjectAnnotations; + readonly input: NormalizedProjectAnnotations; constructor(data: PreviewConfigData) { const { addons, ...rest } = data; - this.annotations = normalizeProjectAnnotations(composeConfigs([...(addons ?? []), rest])); + this.input = normalizeProjectAnnotations(composeConfigs([...(addons ?? []), rest])); } readonly meta = < @@ -49,12 +49,12 @@ class PreviewConfig { } class Meta { - readonly annotations: ComponentAnnotations; + readonly input: ComponentAnnotations; readonly config: PreviewConfig; constructor(annotations: ComponentAnnotations, config: PreviewConfig) { - this.annotations = annotations; + this.input = annotations; this.config = config; } @@ -65,7 +65,7 @@ class Meta { constructor( - public annotations: StoryAnnotations, + public input: StoryAnnotations, public meta: Meta, public config: PreviewConfig ) {} diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 9c5a40bf2771..563ca02ee478 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -429,7 +429,7 @@ export async function setupVitest(details: TemplateDetails, options: PassedOptio import projectAnnotations from './preview' // setProjectAnnotations still kept to support non-CSF4 story tests - const annotations = setProjectAnnotations(projectAnnotations.annotations) + const annotations = setProjectAnnotations(projectAnnotations.input) beforeAll(annotations.beforeAll) ` ); From 9be57ad2d8db5e217e633ecb0835c984311a135d Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Thu, 23 Jan 2025 15:41:49 +0100 Subject: [PATCH 086/144] update story codemod to use preview instead of config --- .../helpers/story-to-csf-factory.test.ts | 56 +++++++++---------- .../codemod/helpers/story-to-csf-factory.ts | 6 +- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts index 3064115ca976..5fe5202bddf4 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts @@ -25,9 +25,9 @@ describe('stories codemod', () => { export default meta; `) ).resolves.toMatchInlineSnapshot(` - import config from '#.storybook/preview'; + import preview from '#.storybook/preview'; - const meta = config.meta({ title: 'Component' }); + const meta = preview.meta({ title: 'Component' }); `); }); @@ -37,9 +37,9 @@ describe('stories codemod', () => { export default { title: 'Component' }; `) ).resolves.toMatchInlineSnapshot(` - import config from '#.storybook/preview'; + import preview from '#.storybook/preview'; - const meta = config.meta({ + const meta = preview.meta({ title: 'Component', }); `); @@ -52,9 +52,9 @@ describe('stories codemod', () => { export default componentMeta; `) ).resolves.toMatchInlineSnapshot(` - import config from '#.storybook/preview'; + import preview from '#.storybook/preview'; - const meta = config.meta({ title: 'Component' }); + const meta = preview.meta({ title: 'Component' }); `); }); @@ -69,9 +69,9 @@ describe('stories codemod', () => { }; `) ).resolves.toMatchInlineSnapshot(` - import config from '#.storybook/preview'; + import preview from '#.storybook/preview'; - const meta = config.meta({ title: 'Component' }); + const meta = preview.meta({ title: 'Component' }); export const A = meta.story({ args: { primary: true }, render: (args) => , @@ -91,9 +91,9 @@ describe('stories codemod', () => { }; `) ).resolves.toMatchInlineSnapshot(` - import config, { decorators } from '#.storybook/preview'; + import preview, { decorators } from '#.storybook/preview'; - const meta = config.meta({ title: 'Component' }); + const meta = preview.meta({ title: 'Component' }); export const A = meta.story({ args: { primary: true }, render: (args) => , @@ -135,9 +135,9 @@ describe('stories codemod', () => { }; `) ).resolves.toMatchInlineSnapshot(` - import storybookConfig from '#.storybook/preview'; + import preview from '#.storybook/preview'; - const meta = storybookConfig.meta({ title: 'Component' }); + const meta = preview.meta({ title: 'Component' }); const config = {}; export const A = meta.story({ args: { primary: true }, @@ -154,9 +154,9 @@ describe('stories codemod', () => { export const CSF1Story = () =>
Hello
; `) ).resolves.toMatchInlineSnapshot(` - import config from '#.storybook/preview'; + import preview from '#.storybook/preview'; - const meta = config.meta({ title: 'Component' }); + const meta = preview.meta({ title: 'Component' }); export const CSF1Story = meta.story({ render: () =>
Hello
, }); @@ -177,11 +177,11 @@ describe('stories codemod', () => { `; it('meta satisfies syntax', async () => { await expect(transform(inlineMetaSatisfies)).resolves.toMatchInlineSnapshot(` - import config from '#.storybook/preview'; + import preview from '#.storybook/preview'; import { ComponentProps } from './Component'; - const meta = config.meta({ title: 'Component', component: Component }); + const meta = preview.meta({ title: 'Component', component: Component }); export const A = meta.story({ args: { primary: true }, @@ -201,11 +201,11 @@ describe('stories codemod', () => { `; it('meta as syntax', async () => { await expect(transform(inlineMetaAs)).resolves.toMatchInlineSnapshot(` - import config from '#.storybook/preview'; + import preview from '#.storybook/preview'; import { ComponentProps } from './Component'; - const meta = config.meta({ title: 'Component', component: Component }); + const meta = preview.meta({ title: 'Component', component: Component }); export const A = meta.story({ args: { primary: true }, @@ -225,11 +225,11 @@ describe('stories codemod', () => { `; it('meta satisfies syntax', async () => { await expect(transform(metaSatisfies)).resolves.toMatchInlineSnapshot(` - import config from '#.storybook/preview'; + import preview from '#.storybook/preview'; import { ComponentProps } from './Component'; - const meta = config.meta({ title: 'Component', component: Component }); + const meta = preview.meta({ title: 'Component', component: Component }); export const A = meta.story({ args: { primary: true }, @@ -250,11 +250,11 @@ describe('stories codemod', () => { `; it('meta as syntax', async () => { await expect(transform(metaAs)).resolves.toMatchInlineSnapshot(` - import config from '#.storybook/preview'; + import preview from '#.storybook/preview'; import { ComponentProps } from './Component'; - const meta = config.meta({ title: 'Component', component: Component }); + const meta = preview.meta({ title: 'Component', component: Component }); export const A = meta.story({ args: { primary: true }, @@ -275,11 +275,11 @@ describe('stories codemod', () => { `; it('story satisfies syntax', async () => { await expect(transform(storySatisfies)).resolves.toMatchInlineSnapshot(` - import config from '#.storybook/preview'; + import preview from '#.storybook/preview'; import { ComponentProps } from './Component'; - const meta = config.meta({ title: 'Component', component: Component }); + const meta = preview.meta({ title: 'Component', component: Component }); export const A = meta.story({ args: { primary: true }, @@ -300,11 +300,11 @@ describe('stories codemod', () => { `; it('story as syntax', async () => { await expect(transform(storyAs)).resolves.toMatchInlineSnapshot(` - import config from '#.storybook/preview'; + import preview from '#.storybook/preview'; import { ComponentProps } from './Component'; - const meta = config.meta({ title: 'Component', component: Component }); + const meta = preview.meta({ title: 'Component', component: Component }); export const A = meta.story({ args: { primary: true }, @@ -339,11 +339,11 @@ describe('stories codemod', () => { export const A: Story = {};` ) ).resolves.toMatchInlineSnapshot(` - import config from '#.storybook/preview'; + import preview from '#.storybook/preview'; import { ComponentProps } from './Component'; - const meta = config.meta({}); + const meta = preview.meta({}); export const A = meta.story({}); `); diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts index f374eea9df80..0a004aa61910 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts @@ -25,14 +25,14 @@ export async function storyToCsfFactory(info: FileInfo) { const programNode = csf._ast.program; let foundConfigImport = false; - // Check if a root-level constant named 'config' exists + // Check if a root-level constant named 'preview' exists const hasRootLevelConfig = programNode.body.some( (n) => t.isVariableDeclaration(n) && - n.declarations.some((declaration) => t.isIdentifier(declaration.id, { name: 'config' })) + n.declarations.some((declaration) => t.isIdentifier(declaration.id, { name: 'preview' })) ); - let sbConfigImportName = hasRootLevelConfig ? 'storybookConfig' : 'config'; + let sbConfigImportName = hasRootLevelConfig ? 'storybookPreview' : 'preview'; const sbConfigImportSpecifier = t.importDefaultSpecifier(t.identifier(sbConfigImportName)); From 2711770e2e710864c080ce346b421e6899f15436 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Thu, 23 Jan 2025 17:52:03 +0100 Subject: [PATCH 087/144] Codemod: Add Story.xyz to Story.input.xyz migration --- .../helpers/story-to-csf-factory.test.ts | 94 ++++++++++++++++++- .../codemod/helpers/story-to-csf-factory.ts | 66 ++++++++++--- 2 files changed, 144 insertions(+), 16 deletions(-) diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts index 5fe5202bddf4..c5c0973ebe21 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts @@ -123,22 +123,22 @@ describe('stories codemod', () => { `); }); - it('if there is an existing local constant called config, rename storybook config import', async () => { + it('if there is an existing local constant called preview, rename storybook preview import', async () => { await expect( transform(dedent` const componentMeta = { title: 'Component' }; export default componentMeta; - const config = {}; + const preview = {}; export const A = { args: { primary: true }, render: (args) => }; `) ).resolves.toMatchInlineSnapshot(` - import preview from '#.storybook/preview'; + import storybookPreview from '#.storybook/preview'; - const meta = preview.meta({ title: 'Component' }); - const config = {}; + const meta = storybookPreview.meta({ title: 'Component' }); + const preview = {}; export const A = meta.story({ args: { primary: true }, render: (args) => , @@ -146,6 +146,90 @@ describe('stories codemod', () => { `); }); + it('migrate reused properties of other stories from `Story.xyz` to `Story.input.xyz`', async () => { + await expect( + transform(dedent` + export default { title: 'Component' }; + const someData = {}; + + export const A = {}; + + export const B = { + ...A, + args: { + ...A.args, + ...someData, + }, + }; + export const C = { + render: async () => { + return JSON.stringify({ + ...A.argTypes, + ...B, + }) + } + }; + `) + ).resolves.toMatchInlineSnapshot(` + import preview from '#.storybook/preview'; + + const meta = preview.meta({ + title: 'Component', + }); + + const someData = {}; + + export const A = meta.story({}); + + export const B = meta.story({ + ...A.input, + args: { + ...A.input.args, + ...someData, + }, + }); + export const C = meta.story({ + render: async () => { + return JSON.stringify({ + ...A.input.argTypes, + ...B.input, + }); + }, + }); + `); + }); + + it('does not migrate reused properties from disallowed list', async () => { + await expect( + transform(dedent` + export default { title: 'Component' }; + export const A = {}; + export const B = { + play: async () => { + await A.play(); + } + }; + export const C = A.run; + export const D = A.extends({}); + `) + ).resolves.toMatchInlineSnapshot(` + import preview from '#.storybook/preview'; + + const meta = preview.meta({ + title: 'Component', + }); + + export const A = meta.story({}); + export const B = meta.story({ + play: async () => { + await A.play(); + }, + }); + export const C = A.run; + export const D = A.extends({}); + `); + }); + it('converts CSF1 into CSF4 with render', async () => { await expect( transform(dedent` diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts index 0a004aa61910..a71efa248677 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts @@ -1,11 +1,25 @@ /* eslint-disable no-underscore-dangle */ -import { types as t } from 'storybook/internal/babel'; +import { types as t, traverse } from 'storybook/internal/babel'; import { isValidPreviewPath, loadCsf, printCsf } from 'storybook/internal/csf-tools'; import type { FileInfo } from '../../automigrate/codemod'; import { logger } from '../csf-factories'; import { cleanupTypeImports } from './csf-factories-utils'; +// Name of properties that should not be renamed to `Story.input.xyz` +const reuseDisallowList = ['play', 'run', 'extends']; + +// Name of types that should be removed from the import list +const typesDisallowList = [ + 'Story', + 'StoryFn', + 'StoryObj', + 'Meta', + 'MetaObj', + 'ComponentStory', + 'ComponentMeta', +]; + export async function storyToCsfFactory(info: FileInfo) { const csf = loadCsf(info.source, { makeTitle: () => 'FIXME' }); try { @@ -90,6 +104,45 @@ export async function storyToCsfFactory(info: FileInfo) { } }); + const storyExportDecls = new Map( + Object.entries(csf._storyExports).filter(([, decl]) => t.isVariableDeclarator(decl)) + ); + + // For each story, replace any reference of story reuse e.g. + // Story.args -> Story.input.args + traverse(csf._ast, { + Identifier(path) { + const binding = path.scope.getBinding(path.node.name); + + // Check if the identifier corresponds to a story export + if (binding && storyExportDecls.has(binding.identifier.name)) { + const parent = path.parent; + + // Skip if this is the definition of the story export itself + if (t.isVariableDeclarator(parent) && parent.id === path.node) { + return; + } + + // Skip if it's already `Story.input` + if (t.isMemberExpression(parent) && t.isIdentifier(parent.property, { name: 'input' })) { + return; + } + + // Check if the property name is in the disallow list + if ( + t.isMemberExpression(parent) && + t.isIdentifier(parent.property) && + reuseDisallowList.includes(parent.property.name) + ) { + return; + } + + // Replace the identifier with `Story.input` + path.replaceWith(t.memberExpression(t.identifier(path.node.name), t.identifier('input'))); + } + }, + }); + // modify meta if (csf._metaPath) { let declaration = csf._metaPath.node.declaration; @@ -154,16 +207,7 @@ export async function storyToCsfFactory(info: FileInfo) { } // Remove type imports – now inferred – from @storybook/* packages - const disallowList = [ - 'Story', - 'StoryFn', - 'StoryObj', - 'Meta', - 'MetaObj', - 'ComponentStory', - 'ComponentMeta', - ]; - programNode.body = cleanupTypeImports(programNode, disallowList); + programNode.body = cleanupTypeImports(programNode, typesDisallowList); // Remove unused type aliases e.g. `type Story = StoryObj;` programNode.body.forEach((node, index) => { From c585eefaeb05d39f5f4cf704613bdd32b7a519a5 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 24 Jan 2025 11:31:18 +0100 Subject: [PATCH 088/144] Make story factory codemod more resilient --- .../helpers/story-to-csf-factory.test.ts | 25 +++++++++++++ .../codemod/helpers/story-to-csf-factory.ts | 37 ++++++++++++++++--- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts index c5c0973ebe21..cd526f3c17fd 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts @@ -230,6 +230,31 @@ describe('stories codemod', () => { `); }); + it('should support non-conventional formats (INCOMPLETE)', async () => { + const transformed = await transform(dedent` + import { A as Component } from './Button'; + import * as Stories from './Other.stories'; + import someData from './fixtures' + export default { + component: Component, + // not supported yet (story coming from another file) + args: Stories.A.args + }; + const data = {}; + export const A = () => {}; + // not supported yet (story as function) + export function B() { }; + // not supported yet (story redeclared) + const C = { ...A, args: data, }; + export { C }; + `); + + expect(transformed).toContain('A = meta.story'); + // @TODO: when we support these, uncomment these lines + // expect(transformed).toContain('B = meta.story'); + // expect(transformed).toContain('C = meta.story'); + }); + it('converts CSF1 into CSF4 with render', async () => { await expect( transform(dedent` diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts index a71efa248677..96a92869606d 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts @@ -68,6 +68,9 @@ export async function storyToCsfFactory(info: FileInfo) { const hasMeta = !!csf._meta; + // @TODO: Support unconventional formats: + // `export function Story() { };` and `export { Story }; + // These are not part of csf._storyExports but rather csf._storyStatements and are tricky to support. Object.entries(csf._storyExports).forEach(([key, decl]) => { const id = decl.id; const declarator = decl as t.VariableDeclarator; @@ -105,7 +108,12 @@ export async function storyToCsfFactory(info: FileInfo) { }); const storyExportDecls = new Map( - Object.entries(csf._storyExports).filter(([, decl]) => t.isVariableDeclarator(decl)) + Object.entries(csf._storyExports).filter( + ( + entry + ): entry is [string, Exclude<(typeof csf._storyExports)[string], t.FunctionDeclaration>] => + !t.isFunctionDeclaration(entry[1]) + ) ); // For each story, replace any reference of story reuse e.g. @@ -118,16 +126,25 @@ export async function storyToCsfFactory(info: FileInfo) { if (binding && storyExportDecls.has(binding.identifier.name)) { const parent = path.parent; - // Skip if this is the definition of the story export itself + // Skip declarations (e.g., `const Story = {};`) if (t.isVariableDeclarator(parent) && parent.id === path.node) { return; } + // Skip import statements e.g.`import { X as Story }` + if (t.isImportSpecifier(parent)) { + return; + } + + // Skip export statements e.g.`export const Story` or `export { Story }` + if (t.isExportSpecifier(parent)) { + return; + } + // Skip if it's already `Story.input` if (t.isMemberExpression(parent) && t.isIdentifier(parent.property, { name: 'input' })) { return; } - // Check if the property name is in the disallow list if ( t.isMemberExpression(parent) && @@ -137,8 +154,18 @@ export async function storyToCsfFactory(info: FileInfo) { return; } - // Replace the identifier with `Story.input` - path.replaceWith(t.memberExpression(t.identifier(path.node.name), t.identifier('input'))); + try { + // Replace the identifier with `Story.input` + path.replaceWith(t.memberExpression(t.identifier(path.node.name), t.identifier('input'))); + } catch (err: any) { + // This is a tough one to support, we just skip for now. + // Relates to `Stories.Story.args` where Stories is coming from another file. We can't know whether it should be transformed or not. + if (err.message.includes(`instead got "MemberExpression"`)) { + console.log({ path }); + } else { + throw err; + } + } } }, }); From e69943b65825ab70555928528eb824098f229291 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 24 Jan 2025 16:58:31 +0100 Subject: [PATCH 089/144] Add default export as function annotation from core addons --- code/addons/a11y/src/index.ts | 6 ++++ code/addons/actions/src/index.ts | 6 ++++ code/addons/backgrounds/src/index.ts | 7 +++-- code/addons/controls/src/index.ts | 4 +++ code/addons/docs/src/index.ts | 6 ++++ code/addons/essentials/src/preview.ts | 28 +++++++++-------- code/addons/highlight/src/index.ts | 7 +++-- code/addons/interactions/src/index.ts | 7 +++-- code/addons/links/src/index.ts | 6 ++++ code/addons/measure/src/index.ts | 7 +++-- code/addons/outline/src/index.ts | 7 +++-- code/addons/test/src/index.ts | 6 ++-- code/addons/themes/src/index.ts | 8 +++-- code/addons/viewport/src/index.ts | 6 ++++ .../src/common/utils/get-addon-annotations.ts | 14 +++++++-- .../utils/sync-main-preview-addons.test.ts | 30 +++++++++++++++++++ .../common/utils/sync-main-preview-addons.ts | 14 +++++++-- code/core/src/preview-api/index.ts | 3 ++ .../modules/addons/definePreview.ts | 5 ++++ .../src/preview-api/modules/addons/index.ts | 1 + 20 files changed, 148 insertions(+), 30 deletions(-) create mode 100644 code/core/src/preview-api/modules/addons/definePreview.ts diff --git a/code/addons/a11y/src/index.ts b/code/addons/a11y/src/index.ts index ce28f952df01..1cc5889d179f 100644 --- a/code/addons/a11y/src/index.ts +++ b/code/addons/a11y/src/index.ts @@ -1,2 +1,8 @@ +import { definePreview } from 'storybook/internal/preview-api'; + +import * as addonAnnotations from './preview'; + export { PARAM_KEY } from './constants'; export * from './params'; + +export default () => definePreview(addonAnnotations); diff --git a/code/addons/actions/src/index.ts b/code/addons/actions/src/index.ts index d2d3261dc960..4bd7323699a2 100644 --- a/code/addons/actions/src/index.ts +++ b/code/addons/actions/src/index.ts @@ -1,3 +1,9 @@ +import { definePreview } from 'storybook/internal/preview-api'; + +import * as addonAnnotations from './preview'; + export * from './constants'; export * from './models'; export * from './runtime'; + +export default () => definePreview(addonAnnotations); diff --git a/code/addons/backgrounds/src/index.ts b/code/addons/backgrounds/src/index.ts index dafa948eda6c..0e536df78dad 100644 --- a/code/addons/backgrounds/src/index.ts +++ b/code/addons/backgrounds/src/index.ts @@ -1,2 +1,5 @@ -// make it work with --isolatedModules -export default {}; +import { definePreview } from 'storybook/internal/preview-api'; + +import * as addonAnnotations from './preview'; + +export default () => definePreview(addonAnnotations); diff --git a/code/addons/controls/src/index.ts b/code/addons/controls/src/index.ts index 0fe41f8142f4..544b59da0caf 100644 --- a/code/addons/controls/src/index.ts +++ b/code/addons/controls/src/index.ts @@ -1 +1,5 @@ +import { definePreview } from 'storybook/internal/preview-api'; + export { PARAM_KEY } from './constants'; + +export default () => definePreview({}); diff --git a/code/addons/docs/src/index.ts b/code/addons/docs/src/index.ts index b74399955f12..f556ef0c6d8a 100644 --- a/code/addons/docs/src/index.ts +++ b/code/addons/docs/src/index.ts @@ -1,2 +1,8 @@ +import { definePreview } from 'storybook/internal/preview-api'; + +import * as addonAnnotations from './preview'; + export * from '@storybook/blocks'; export { DocsRenderer } from './DocsRenderer'; + +export default () => definePreview(addonAnnotations); diff --git a/code/addons/essentials/src/preview.ts b/code/addons/essentials/src/preview.ts index ed7839830d83..5298651f4d19 100644 --- a/code/addons/essentials/src/preview.ts +++ b/code/addons/essentials/src/preview.ts @@ -1,15 +1,19 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-nocheck - -/* eslint-disable import/namespace */ import { composeConfigs } from 'storybook/internal/preview-api'; -import * as actions from '@storybook/addon-actions/preview'; -import * as backgrounds from '@storybook/addon-backgrounds/preview'; -import * as docs from '@storybook/addon-docs/preview'; -import * as highlight from '@storybook/addon-highlight/preview'; -import * as measure from '@storybook/addon-measure/preview'; -import * as outline from '@storybook/addon-outline/preview'; -import * as viewport from '@storybook/addon-viewport/preview'; +import actionsAddon from '@storybook/addon-actions'; +import backgroundsAddon from '@storybook/addon-backgrounds'; +import docsAddon from '@storybook/addon-docs'; +import highlightAddon from '@storybook/addon-highlight'; +import measureAddon from '@storybook/addon-measure'; +import outlineAddon from '@storybook/addon-outline'; +import viewportAddon from '@storybook/addon-viewport'; -export default composeConfigs([actions, docs, backgrounds, viewport, measure, outline, highlight]); +export default composeConfigs([ + actionsAddon(), + docsAddon(), + backgroundsAddon(), + viewportAddon(), + measureAddon(), + outlineAddon(), + highlightAddon(), +]); diff --git a/code/addons/highlight/src/index.ts b/code/addons/highlight/src/index.ts index 6849e07e6184..486cfd694f56 100644 --- a/code/addons/highlight/src/index.ts +++ b/code/addons/highlight/src/index.ts @@ -1,4 +1,7 @@ +import { definePreview } from 'storybook/internal/preview-api'; + +import './preview'; + export { HIGHLIGHT, RESET_HIGHLIGHT } from './constants'; -// make it work with --isolatedModules -export default {}; +export default () => definePreview({}); diff --git a/code/addons/interactions/src/index.ts b/code/addons/interactions/src/index.ts index dafa948eda6c..0e536df78dad 100644 --- a/code/addons/interactions/src/index.ts +++ b/code/addons/interactions/src/index.ts @@ -1,2 +1,5 @@ -// make it work with --isolatedModules -export default {}; +import { definePreview } from 'storybook/internal/preview-api'; + +import * as addonAnnotations from './preview'; + +export default () => definePreview(addonAnnotations); diff --git a/code/addons/links/src/index.ts b/code/addons/links/src/index.ts index 524558abc6c6..4bb40898a9ac 100644 --- a/code/addons/links/src/index.ts +++ b/code/addons/links/src/index.ts @@ -1 +1,7 @@ +import { definePreview } from 'storybook/internal/preview-api'; + +import * as addonAnnotations from './preview'; + export { linkTo, hrefTo, withLinks, navigate } from './utils'; + +export default () => definePreview(addonAnnotations); diff --git a/code/addons/measure/src/index.ts b/code/addons/measure/src/index.ts index dafa948eda6c..0e536df78dad 100644 --- a/code/addons/measure/src/index.ts +++ b/code/addons/measure/src/index.ts @@ -1,2 +1,5 @@ -// make it work with --isolatedModules -export default {}; +import { definePreview } from 'storybook/internal/preview-api'; + +import * as addonAnnotations from './preview'; + +export default () => definePreview(addonAnnotations); diff --git a/code/addons/outline/src/index.ts b/code/addons/outline/src/index.ts index dafa948eda6c..0e536df78dad 100644 --- a/code/addons/outline/src/index.ts +++ b/code/addons/outline/src/index.ts @@ -1,2 +1,5 @@ -// make it work with --isolatedModules -export default {}; +import { definePreview } from 'storybook/internal/preview-api'; + +import * as addonAnnotations from './preview'; + +export default () => definePreview(addonAnnotations); diff --git a/code/addons/test/src/index.ts b/code/addons/test/src/index.ts index db487be11695..bdbecfa7e798 100644 --- a/code/addons/test/src/index.ts +++ b/code/addons/test/src/index.ts @@ -1,7 +1,9 @@ +import { definePreview } from 'storybook/internal/preview-api'; + +import * as addonAnnotations from './preview'; import type { storybookTest as storybookTestImport } from './vitest-plugin'; -// make it work with --isolatedModules -export default {}; +export default () => definePreview(addonAnnotations); // @ts-expect-error - this is a hack to make the module's sub-path augmentable declare module '@storybook/experimental-addon-test/vitest-plugin' { diff --git a/code/addons/themes/src/index.ts b/code/addons/themes/src/index.ts index b89aa12deba9..41cc415df798 100644 --- a/code/addons/themes/src/index.ts +++ b/code/addons/themes/src/index.ts @@ -1,3 +1,7 @@ -// make it work with --isolatedModules -export default {}; +import { definePreview } from 'storybook/internal/preview-api'; + +import * as addonAnnotations from './preview'; + +export default () => definePreview(addonAnnotations); + export * from './decorators'; diff --git a/code/addons/viewport/src/index.ts b/code/addons/viewport/src/index.ts index fac593a2f2dd..3deffc965b6d 100644 --- a/code/addons/viewport/src/index.ts +++ b/code/addons/viewport/src/index.ts @@ -1,2 +1,8 @@ +import { definePreview } from 'storybook/internal/preview-api'; + +import * as addonAnnotations from './preview'; + export * from './defaults'; export type * from './types'; + +export default () => definePreview(addonAnnotations); diff --git a/code/core/src/common/utils/get-addon-annotations.ts b/code/core/src/common/utils/get-addon-annotations.ts index e9d562326915..04ac71771606 100644 --- a/code/core/src/common/utils/get-addon-annotations.ts +++ b/code/core/src/common/utils/get-addon-annotations.ts @@ -1,5 +1,7 @@ import path from 'node:path'; +import { isCorePackage } from './cli'; + /** * Get the name of the annotations object for a given addon. * @@ -18,15 +20,23 @@ export function getAnnotationsName(addonName: string): string { .join('') .replace(/^./, (char) => char.toLowerCase()); - return `${cleanedUpName}Annotations`; + return cleanedUpName; } export async function getAddonAnnotations(addon: string) { try { const data = { - importPath: `${addon}/preview`, + // core addons will have a function as default export in index entrypoint + importPath: addon, importName: getAnnotationsName(addon), + isCoreAddon: isCorePackage(addon), }; + + // for backwards compatibility, if it's not a core addon we use /preview entrypoint + if (!data.isCoreAddon) { + data.importPath = `@storybook/${addon}/preview`; + } + // TODO: current workaround needed only for essentials, fix this once we change the preview entry-point for that package if (addon === '@storybook/addon-essentials') { data.importPath = '@storybook/addon-essentials/entry-preview'; diff --git a/code/core/src/common/utils/sync-main-preview-addons.test.ts b/code/core/src/common/utils/sync-main-preview-addons.test.ts index 223b33d10970..774ace2af674 100644 --- a/code/core/src/common/utils/sync-main-preview-addons.test.ts +++ b/code/core/src/common/utils/sync-main-preview-addons.test.ts @@ -49,6 +49,36 @@ describe('getSyncedStorybookAddons', () => { `); }); + it('should sync addons as functions when they are core packages', async () => { + const preview = loadConfig(` + import * as myAddonAnnotations from "custom-addon/preview"; + import { definePreview } from "@storybook/react/preview"; + + export default definePreview({ + addons: [myAddonAnnotations], + }); + `).parse(); + + (getAddonAnnotations as Mock).mockImplementation(() => { + return { + importName: 'addonA11yAnnotations', + importPath: '@storybook/addon-a11y', + isCoreAddon: true, + }; + }); + + const result = await getSyncedStorybookAddons(mainConfig, preview); + expect(printConfig(result).code).toMatchInlineSnapshot(` + import addonA11yAnnotations from "@storybook/addon-a11y"; + import * as myAddonAnnotations from "custom-addon/preview"; + import { definePreview } from "@storybook/react/preview"; + + export default definePreview({ + addons: [myAddonAnnotations, addonA11yAnnotations()], + }); + `); + }); + it('should add addons if the preview has no addons field', async () => { const originalCode = dedent` import { definePreview } from "@storybook/react/preview"; diff --git a/code/core/src/common/utils/sync-main-preview-addons.ts b/code/core/src/common/utils/sync-main-preview-addons.ts index c0618705836a..340956da19b9 100644 --- a/code/core/src/common/utils/sync-main-preview-addons.ts +++ b/code/core/src/common/utils/sync-main-preview-addons.ts @@ -71,8 +71,18 @@ export async function getSyncedStorybookAddons( )) ) { syncedAddons.push(addon); - previewConfig.setImport({ namespace: annotations.importName }, annotations.importPath); - previewConfig.appendNodeToArray(['addons'], t.identifier(annotations.importName)); + if (annotations.isCoreAddon) { + // import addonName from 'addon'; + addonName() + previewConfig.setImport(annotations.importName, annotations.importPath); + previewConfig.appendNodeToArray( + ['addons'], + t.callExpression(t.identifier(annotations.importName), []) + ); + } else { + // import * as addonName from 'addon/preview'; + addonName + previewConfig.setImport({ namespace: annotations.importName }, annotations.importPath); + previewConfig.appendNodeToArray(['addons'], t.identifier(annotations.importName)); + } } } }); diff --git a/code/core/src/preview-api/index.ts b/code/core/src/preview-api/index.ts index 1b46b91ecd75..c39685742e6f 100644 --- a/code/core/src/preview-api/index.ts +++ b/code/core/src/preview-api/index.ts @@ -27,6 +27,9 @@ export { makeDecorator } from './addons'; */ export { addons, mockChannel } from './addons'; +/** ADDON ANNOTATIONS TYPE HELPER */ +export { definePreview } from './addons'; + /** DOCS API */ export { DocsContext } from './preview-web'; diff --git a/code/core/src/preview-api/modules/addons/definePreview.ts b/code/core/src/preview-api/modules/addons/definePreview.ts new file mode 100644 index 000000000000..3a861671f393 --- /dev/null +++ b/code/core/src/preview-api/modules/addons/definePreview.ts @@ -0,0 +1,5 @@ +import type { ProjectAnnotations, Renderer } from '@storybook/types'; + +export function definePreview(config: ProjectAnnotations) { + return config; +} diff --git a/code/core/src/preview-api/modules/addons/index.ts b/code/core/src/preview-api/modules/addons/index.ts index b32933b0c1c9..db494490d7c1 100644 --- a/code/core/src/preview-api/modules/addons/index.ts +++ b/code/core/src/preview-api/modules/addons/index.ts @@ -1,4 +1,5 @@ export * from './main'; +export * from './definePreview'; export * from './hooks'; export * from './make-decorator'; export * from './storybook-channel-mock'; From 8d1c77f88a0b9aec22997e1f155da42dc2408e74 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 24 Jan 2025 16:59:43 +0100 Subject: [PATCH 090/144] fix types --- code/addons/backgrounds/src/decorator.ts | 11 ++--------- .../backgrounds/src/legacy/withBackgroundLegacy.ts | 11 ++--------- code/addons/backgrounds/src/legacy/withGridLegacy.ts | 8 ++------ code/addons/backgrounds/src/preview.ts | 4 +--- code/addons/interactions/src/preview.ts | 7 ++++--- code/addons/links/src/preview.ts | 4 +--- code/addons/measure/src/preview.tsx | 4 +--- code/addons/measure/src/withMeasure.ts | 8 ++------ code/addons/outline/package.json | 2 +- code/addons/outline/src/{preview.tsx => preview.ts} | 4 +--- code/addons/outline/src/withOutline.ts | 8 ++------ 11 files changed, 19 insertions(+), 52 deletions(-) rename code/addons/outline/src/{preview.tsx => preview.ts} (50%) diff --git a/code/addons/backgrounds/src/decorator.ts b/code/addons/backgrounds/src/decorator.ts index 9fa8419d8eb0..9806cdd546f6 100644 --- a/code/addons/backgrounds/src/decorator.ts +++ b/code/addons/backgrounds/src/decorator.ts @@ -1,9 +1,5 @@ import { useEffect } from 'storybook/internal/preview-api'; -import type { - Renderer, - StoryContext, - PartialStoryFn as StoryFunction, -} from 'storybook/internal/types'; +import type { DecoratorFunction } from 'storybook/internal/types'; import { PARAM_KEY as KEY } from './constants'; import { DEFAULT_BACKGROUNDS } from './defaults'; @@ -21,10 +17,7 @@ const GRID_SELECTOR_BASE = 'addon-backgrounds-grid'; const transitionStyle = isReduceMotionEnabled() ? '' : 'transition: background-color 0.3s;'; -export const withBackgroundAndGrid = ( - StoryFn: StoryFunction, - context: StoryContext -) => { +export const withBackgroundAndGrid: DecoratorFunction = (StoryFn, context) => { const { globals, parameters, viewMode, id } = context; const { options = DEFAULT_BACKGROUNDS, diff --git a/code/addons/backgrounds/src/legacy/withBackgroundLegacy.ts b/code/addons/backgrounds/src/legacy/withBackgroundLegacy.ts index a7d42e9d46b0..0223f2bd0e59 100644 --- a/code/addons/backgrounds/src/legacy/withBackgroundLegacy.ts +++ b/code/addons/backgrounds/src/legacy/withBackgroundLegacy.ts @@ -1,18 +1,11 @@ import { useEffect, useMemo } from 'storybook/internal/preview-api'; -import type { - Renderer, - StoryContext, - PartialStoryFn as StoryFunction, -} from 'storybook/internal/types'; +import type { DecoratorFunction } from 'storybook/internal/types'; import { PARAM_KEY as BACKGROUNDS_PARAM_KEY } from '../constants'; import { addBackgroundStyle, clearStyles, isReduceMotionEnabled } from '../utils'; import { getBackgroundColorByName } from './getBackgroundColorByName'; -export const withBackground = ( - StoryFn: StoryFunction, - context: StoryContext -) => { +export const withBackground: DecoratorFunction = (StoryFn, context) => { const { globals, parameters } = context; const globalsBackgroundColor = globals[BACKGROUNDS_PARAM_KEY]?.value; const backgroundsConfig = parameters[BACKGROUNDS_PARAM_KEY]; diff --git a/code/addons/backgrounds/src/legacy/withGridLegacy.ts b/code/addons/backgrounds/src/legacy/withGridLegacy.ts index 3fb711c772e2..ed98d1985854 100644 --- a/code/addons/backgrounds/src/legacy/withGridLegacy.ts +++ b/code/addons/backgrounds/src/legacy/withGridLegacy.ts @@ -1,14 +1,10 @@ import { useEffect, useMemo } from 'storybook/internal/preview-api'; -import type { - Renderer, - StoryContext, - PartialStoryFn as StoryFunction, -} from 'storybook/internal/types'; +import type { DecoratorFunction } from 'storybook/internal/types'; import { PARAM_KEY as BACKGROUNDS_PARAM_KEY } from '../constants'; import { addGridStyle, clearStyles } from '../utils'; -export const withGrid = (StoryFn: StoryFunction, context: StoryContext) => { +export const withGrid: DecoratorFunction = (StoryFn, context) => { const { globals, parameters } = context; const gridParameters = parameters[BACKGROUNDS_PARAM_KEY].grid; const isActive = globals[BACKGROUNDS_PARAM_KEY]?.grid === true && gridParameters.disable !== true; diff --git a/code/addons/backgrounds/src/preview.ts b/code/addons/backgrounds/src/preview.ts index 43d590a8f55b..27f9e258a7d8 100644 --- a/code/addons/backgrounds/src/preview.ts +++ b/code/addons/backgrounds/src/preview.ts @@ -1,5 +1,3 @@ -import type { Addon_DecoratorFunction } from 'storybook/internal/types'; - import { PARAM_KEY as KEY } from './constants'; import { withBackgroundAndGrid } from './decorator'; import { DEFAULT_BACKGROUNDS } from './defaults'; @@ -7,7 +5,7 @@ import { withBackground } from './legacy/withBackgroundLegacy'; import { withGrid } from './legacy/withGridLegacy'; import type { Config, GlobalState } from './types'; -export const decorators: Addon_DecoratorFunction[] = globalThis.FEATURES?.backgroundsStoryGlobals +export const decorators = globalThis.FEATURES?.backgroundsStoryGlobals ? [withBackgroundAndGrid] : [withGrid, withBackground]; diff --git a/code/addons/interactions/src/preview.ts b/code/addons/interactions/src/preview.ts index 482b6933279f..8342a162797f 100644 --- a/code/addons/interactions/src/preview.ts +++ b/code/addons/interactions/src/preview.ts @@ -1,11 +1,11 @@ -import type { PlayFunction, StepLabel, StoryContext } from 'storybook/internal/types'; +import type { PlayFunction, StepLabel, StepRunner, StoryContext } from 'storybook/internal/types'; import { instrument } from '@storybook/instrumenter'; // This makes sure that storybook test loaders are always loaded when addon-interactions is used // For 9.0 we want to merge storybook/test and addon-interactions into one addon. import '@storybook/test'; -export const { step: runStep } = instrument( +export const runStep = instrument( { // It seems like the label is unused, but the instrumenter has access to it // The context will be bounded later in StoryRender, so that the user can write just: @@ -15,7 +15,8 @@ export const { step: runStep } = instrument( step: (label: StepLabel, play: PlayFunction, context: StoryContext) => play(context), }, { intercept: true } -); + // perhaps csf types need to be updated? StepRunner expects Promise and not Promise | void +).step as StepRunner; export const parameters = { throwPlayFunctionExceptions: false, diff --git a/code/addons/links/src/preview.ts b/code/addons/links/src/preview.ts index 6270d133ab77..1527786e81af 100644 --- a/code/addons/links/src/preview.ts +++ b/code/addons/links/src/preview.ts @@ -1,5 +1,3 @@ -import type { Addon_DecoratorFunction } from 'storybook/internal/types'; - import { withLinks } from './index'; -export const decorators: Addon_DecoratorFunction[] = [withLinks]; +export const decorators = [withLinks]; diff --git a/code/addons/measure/src/preview.tsx b/code/addons/measure/src/preview.tsx index 8898eb58dd6b..a97aefdff659 100644 --- a/code/addons/measure/src/preview.tsx +++ b/code/addons/measure/src/preview.tsx @@ -1,9 +1,7 @@ -import type { Addon_DecoratorFunction } from 'storybook/internal/types'; - import { PARAM_KEY } from './constants'; import { withMeasure } from './withMeasure'; -export const decorators: Addon_DecoratorFunction[] = [withMeasure]; +export const decorators = [withMeasure]; export const initialGlobals = { [PARAM_KEY]: false, diff --git a/code/addons/measure/src/withMeasure.ts b/code/addons/measure/src/withMeasure.ts index 8524a7f71fce..bc94d6ce2529 100644 --- a/code/addons/measure/src/withMeasure.ts +++ b/code/addons/measure/src/withMeasure.ts @@ -1,10 +1,6 @@ /* eslint-env browser */ import { useEffect } from 'storybook/internal/preview-api'; -import type { - Renderer, - StoryContext, - PartialStoryFn as StoryFunction, -} from 'storybook/internal/types'; +import type { DecoratorFunction } from 'storybook/internal/types'; import { destroy, init, rescale } from './box-model/canvas'; import { drawSelectedElement } from './box-model/visualizer'; @@ -18,7 +14,7 @@ function findAndDrawElement(x: number, y: number) { drawSelectedElement(nodeAtPointerRef); } -export const withMeasure = (StoryFn: StoryFunction, context: StoryContext) => { +export const withMeasure: DecoratorFunction = (StoryFn, context) => { const { measureEnabled } = context.globals; useEffect(() => { diff --git a/code/addons/outline/package.json b/code/addons/outline/package.json index 1822d08fd346..656d04eb73f4 100644 --- a/code/addons/outline/package.json +++ b/code/addons/outline/package.json @@ -80,7 +80,7 @@ "./src/manager.tsx" ], "previewEntries": [ - "./src/preview.tsx" + "./src/preview.ts" ] }, "gitHead": "e6a7fd8a655c69780bc20b9749c2699e44beae16", diff --git a/code/addons/outline/src/preview.tsx b/code/addons/outline/src/preview.ts similarity index 50% rename from code/addons/outline/src/preview.tsx rename to code/addons/outline/src/preview.ts index 19deb3a9afe0..a1c337467c8b 100644 --- a/code/addons/outline/src/preview.tsx +++ b/code/addons/outline/src/preview.ts @@ -1,9 +1,7 @@ -import type { Addon_DecoratorFunction } from 'storybook/internal/types'; - import { PARAM_KEY } from './constants'; import { withOutline } from './withOutline'; -export const decorators: Addon_DecoratorFunction[] = [withOutline]; +export const decorators = [withOutline]; export const initialGlobals = { [PARAM_KEY]: false, diff --git a/code/addons/outline/src/withOutline.ts b/code/addons/outline/src/withOutline.ts index 219b93a3acd2..0bc29c106f87 100644 --- a/code/addons/outline/src/withOutline.ts +++ b/code/addons/outline/src/withOutline.ts @@ -1,15 +1,11 @@ import { useEffect, useMemo } from 'storybook/internal/preview-api'; -import type { - Renderer, - StoryContext, - PartialStoryFn as StoryFunction, -} from 'storybook/internal/types'; +import type { DecoratorFunction } from 'storybook/internal/types'; import { PARAM_KEY } from './constants'; import { addOutlineStyles, clearStyles } from './helpers'; import outlineCSS from './outlineCSS'; -export const withOutline = (StoryFn: StoryFunction, context: StoryContext) => { +export const withOutline: DecoratorFunction = (StoryFn, context) => { const { globals } = context; const isActive = [true, 'true'].includes(globals[PARAM_KEY]); const isInDocs = context.viewMode === 'docs'; From 44e01cb509a3c4ee9804e53280dd534942aa58d8 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 24 Jan 2025 19:09:37 +0100 Subject: [PATCH 091/144] fix types --- code/.storybook/preview.tsx | 21 ++++++++++------- code/addons/backgrounds/package.json | 10 ++++++++ code/addons/essentials/package.json | 10 ++++++++ .../essentials/src/backgrounds/preview.ts | 1 - .../essentials/src/highlight/preview.ts | 1 - code/addons/essentials/src/outline/preview.ts | 1 - code/addons/essentials/src/preview.ts | 23 +++++++++++-------- .../addons/essentials/src/viewport/preview.ts | 1 - code/addons/highlight/package.json | 10 ++++++++ code/addons/interactions/package.json | 10 ++++++++ code/addons/links/package.json | 3 +++ code/addons/outline/package.json | 10 ++++++++ code/addons/test/package.json | 10 ++++++++ code/addons/themes/package.json | 10 ++++++++ code/addons/viewport/package.json | 10 ++++++++ 15 files changed, 109 insertions(+), 22 deletions(-) diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index de55a044b2ae..48d3252642df 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -1,4 +1,3 @@ -/* eslint-disable import/namespace */ import * as React from 'react'; import { Fragment, useEffect } from 'react'; @@ -23,12 +22,11 @@ import { definePreview } from '@storybook/react'; // TODO add empty preview // import * as storysource from '@storybook/addon-storysource'; // import * as designs from '@storybook/addon-designs/preview'; -// import * as test from '@storybook/experimental-addon-test/preview'; -import * as a11y from '@storybook/addon-a11y/preview'; -// @ts-expect-error Must be typed -import * as essentials from '@storybook/addon-essentials/entry-preview'; -// @ts-expect-error Must be typed -import * as addonThemes from '@storybook/addon-themes/preview'; +import addonTest from '@storybook/experimental-addon-test'; + +import addonA11y from '@storybook/addon-a11y'; +import addonEssentials from '@storybook/addon-essentials/entry-preview'; +import addonThemes from '@storybook/addon-themes'; import * as addonsPreview from '../addons/toolbars/template/stories/preview'; import * as templatePreview from '../core/template/stories/preview'; @@ -376,7 +374,14 @@ const parameters = { }; export const config = definePreview({ - addons: [addonThemes, essentials, a11y, addonsPreview, templatePreview], + addons: [ + addonThemes(), + addonEssentials(), + addonA11y(), + addonTest(), + addonsPreview, + templatePreview, + ], parameters, decorators, loaders, diff --git a/code/addons/backgrounds/package.json b/code/addons/backgrounds/package.json index 440f4890379e..7a51c7eb3864 100644 --- a/code/addons/backgrounds/package.json +++ b/code/addons/backgrounds/package.json @@ -43,6 +43,16 @@ "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "dist/index.d.ts" + ], + "preview": [ + "dist/preview.d.ts" + ] + } + }, "files": [ "dist/**/*", "README.md", diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json index 0e9ae8a8d100..6a6222d6f41d 100644 --- a/code/addons/essentials/package.json +++ b/code/addons/essentials/package.json @@ -82,6 +82,16 @@ "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "dist/index.d.ts" + ], + "entry-preview": [ + "dist/preview.d.ts" + ] + } + }, "files": [ "dist/**/*", "README.md", diff --git a/code/addons/essentials/src/backgrounds/preview.ts b/code/addons/essentials/src/backgrounds/preview.ts index cf24112788f3..2d01bf61bb6a 100644 --- a/code/addons/essentials/src/backgrounds/preview.ts +++ b/code/addons/essentials/src/backgrounds/preview.ts @@ -1,2 +1 @@ -// @ts-expect-error (no types needed for this) export * from '@storybook/addon-backgrounds/preview'; diff --git a/code/addons/essentials/src/highlight/preview.ts b/code/addons/essentials/src/highlight/preview.ts index e124e7a1374a..c57b34aafd63 100644 --- a/code/addons/essentials/src/highlight/preview.ts +++ b/code/addons/essentials/src/highlight/preview.ts @@ -1,2 +1 @@ -// @ts-expect-error (no types needed for this) export * from '@storybook/addon-highlight/preview'; diff --git a/code/addons/essentials/src/outline/preview.ts b/code/addons/essentials/src/outline/preview.ts index 3fe09381fe8f..16cc2faa0397 100644 --- a/code/addons/essentials/src/outline/preview.ts +++ b/code/addons/essentials/src/outline/preview.ts @@ -1,2 +1 @@ -// @ts-expect-error (no types needed for this) export * from '@storybook/addon-outline/preview'; diff --git a/code/addons/essentials/src/preview.ts b/code/addons/essentials/src/preview.ts index 5298651f4d19..e39952299d9e 100644 --- a/code/addons/essentials/src/preview.ts +++ b/code/addons/essentials/src/preview.ts @@ -1,4 +1,4 @@ -import { composeConfigs } from 'storybook/internal/preview-api'; +import { composeConfigs, definePreview } from 'storybook/internal/preview-api'; import actionsAddon from '@storybook/addon-actions'; import backgroundsAddon from '@storybook/addon-backgrounds'; @@ -8,12 +8,15 @@ import measureAddon from '@storybook/addon-measure'; import outlineAddon from '@storybook/addon-outline'; import viewportAddon from '@storybook/addon-viewport'; -export default composeConfigs([ - actionsAddon(), - docsAddon(), - backgroundsAddon(), - viewportAddon(), - measureAddon(), - outlineAddon(), - highlightAddon(), -]); +export default () => + definePreview( + composeConfigs([ + actionsAddon(), + docsAddon(), + backgroundsAddon(), + viewportAddon(), + measureAddon(), + outlineAddon(), + highlightAddon(), + ]) + ); diff --git a/code/addons/essentials/src/viewport/preview.ts b/code/addons/essentials/src/viewport/preview.ts index 34ee7de45614..b039b3bfa870 100644 --- a/code/addons/essentials/src/viewport/preview.ts +++ b/code/addons/essentials/src/viewport/preview.ts @@ -1,2 +1 @@ -// @ts-expect-error (no types needed for this) export * from '@storybook/addon-viewport/preview'; diff --git a/code/addons/highlight/package.json b/code/addons/highlight/package.json index 604b9f43aafa..2aa597c9fdb5 100644 --- a/code/addons/highlight/package.json +++ b/code/addons/highlight/package.json @@ -39,6 +39,16 @@ "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "dist/index.d.ts" + ], + "preview": [ + "dist/preview.d.ts" + ] + } + }, "files": [ "dist/**/*", "README.md", diff --git a/code/addons/interactions/package.json b/code/addons/interactions/package.json index b99f1771bc3c..fa017448fa5e 100644 --- a/code/addons/interactions/package.json +++ b/code/addons/interactions/package.json @@ -40,6 +40,16 @@ "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "dist/index.d.ts" + ], + "preview": [ + "dist/preview.d.ts" + ] + } + }, "files": [ "dist/**/*", "README.md", diff --git a/code/addons/links/package.json b/code/addons/links/package.json index 62da503f7767..a2b97e0d8a9f 100644 --- a/code/addons/links/package.json +++ b/code/addons/links/package.json @@ -48,6 +48,9 @@ "*": [ "dist/index.d.ts" ], + "preview": [ + "dist/preview.d.ts" + ], "react": [ "dist/react/index.d.ts" ] diff --git a/code/addons/outline/package.json b/code/addons/outline/package.json index 656d04eb73f4..6a41e6460d2e 100644 --- a/code/addons/outline/package.json +++ b/code/addons/outline/package.json @@ -45,6 +45,16 @@ "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "dist/index.d.ts" + ], + "preview": [ + "dist/preview.d.ts" + ] + } + }, "files": [ "dist/**/*", "README.md", diff --git a/code/addons/test/package.json b/code/addons/test/package.json index e57d08187c23..4599ea860ca8 100644 --- a/code/addons/test/package.json +++ b/code/addons/test/package.json @@ -66,6 +66,16 @@ "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "dist/index.d.ts" + ], + "preview": [ + "dist/preview.d.ts" + ] + } + }, "files": [ "dist/**/*", "!dist/dummy.*", diff --git a/code/addons/themes/package.json b/code/addons/themes/package.json index 8bac0962d35f..42a789c1ef01 100644 --- a/code/addons/themes/package.json +++ b/code/addons/themes/package.json @@ -44,6 +44,16 @@ "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "dist/index.d.ts" + ], + "preview": [ + "dist/preview.d.ts" + ] + } + }, "files": [ "dist/**/*", "README.md", diff --git a/code/addons/viewport/package.json b/code/addons/viewport/package.json index 73c7aea30004..4f3e1f8e5c4c 100644 --- a/code/addons/viewport/package.json +++ b/code/addons/viewport/package.json @@ -39,6 +39,16 @@ "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "dist/index.d.ts" + ], + "preview": [ + "dist/preview.d.ts" + ] + } + }, "files": [ "dist/**/*", "README.md", From e71bade8e2ecd1d3498c2723a5b6ace327d6e949 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 24 Jan 2025 19:28:12 +0100 Subject: [PATCH 092/144] fix test --- .../src/common/utils/get-addon-annotations.test.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/code/core/src/common/utils/get-addon-annotations.test.ts b/code/core/src/common/utils/get-addon-annotations.test.ts index c523b2cf74f2..b60f9f512947 100644 --- a/code/core/src/common/utils/get-addon-annotations.test.ts +++ b/code/core/src/common/utils/get-addon-annotations.test.ts @@ -4,22 +4,20 @@ import { getAnnotationsName } from './get-addon-annotations'; describe('getAnnotationsName', () => { it('should handle @storybook namespace and camel case conversion', () => { - expect(getAnnotationsName('@storybook/addon-essentials')).toBe('addonEssentialsAnnotations'); + expect(getAnnotationsName('@storybook/addon-essentials')).toBe('addonEssentials'); }); it('should handle other namespaces and camel case conversion', () => { expect(getAnnotationsName('@kudos-components/testing/module')).toBe( - 'kudosComponentsTestingModuleAnnotations' + 'kudosComponentsTestingModule' ); }); it('should handle strings without namespaces', () => { - expect(getAnnotationsName('plain-text/example')).toBe('plainTextExampleAnnotations'); + expect(getAnnotationsName('plain-text/example')).toBe('plainTextExample'); }); it('should handle strings with multiple special characters', () => { - expect(getAnnotationsName('@storybook/multi-part/example-test')).toBe( - 'multiPartExampleTestAnnotations' - ); + expect(getAnnotationsName('@storybook/multi-part/example-test')).toBe('multiPartExampleTest'); }); }); From 1cd8644e727c446e87068bbb2f668cafd8133f65 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 27 Jan 2025 12:08:49 +0100 Subject: [PATCH 093/144] Preview API: Add csf factory utilities --- .../test/src/vitest-plugin/test-utils.ts | 13 ++++-- .../src/codegen-modern-iframe-script.ts | 6 +-- code/core/src/preview-api/index.ts | 8 +++- .../preview-web/docs-context/DocsContext.ts | 5 ++- .../modules/store/csf/csf-factory-utils.ts | 42 +++++++++++++++++++ .../preview-api/modules/store/csf/index.ts | 1 + .../modules/store/csf/portable-stories.ts | 19 ++------- .../modules/store/csf/processCSFFile.ts | 1 + 8 files changed, 68 insertions(+), 27 deletions(-) create mode 100644 code/core/src/preview-api/modules/store/csf/csf-factory-utils.ts diff --git a/code/addons/test/src/vitest-plugin/test-utils.ts b/code/addons/test/src/vitest-plugin/test-utils.ts index 4d757a3404ba..765ab38d5a1b 100644 --- a/code/addons/test/src/vitest-plugin/test-utils.ts +++ b/code/addons/test/src/vitest-plugin/test-utils.ts @@ -3,7 +3,11 @@ /* eslint-disable no-underscore-dangle */ import { type RunnerTask, type TaskMeta, type TestContext } from 'vitest'; -import { type Report, composeStory } from 'storybook/internal/preview-api'; +import { + type Report, + composeStory, + getCsfFactoryAnnotations, +} from 'storybook/internal/preview-api'; import type { ComponentAnnotations, ComposedStoryFn } from 'storybook/internal/types'; import { server } from '@vitest/browser/context'; @@ -26,11 +30,12 @@ export const testStory = ( skipTags: string[] ) => { return async (context: TestContext & { story: ComposedStoryFn }) => { + const annotations = getCsfFactoryAnnotations(story, meta); const composedStory = composeStory( - 'isCSFFactory' in story ? (story as any).input : story, - 'isCSFFactory' in story ? (meta as any).input : meta, + annotations.story, + annotations.meta, { initialGlobals: (await getInitialGlobals?.()) ?? {}, tags: await getTags?.() }, - 'isCSFFactory' in story ? (story as any).config?.input : undefined, + annotations.preview, exportName ); diff --git a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts index 02316bf05582..69b05dc5e744 100644 --- a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts +++ b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts @@ -24,9 +24,7 @@ export async function generateModernIframeScriptCode(options: Options, projectRo const getPreviewAnnotationsFunction = ` const getProjectAnnotations = async (hmrPreviewAnnotationModules = []) => { const preview = await import('${previewFileUrl}'); - const csfFactoryPreview = Object.values(preview).find(module => { - return 'isCSFFactoryPreview' in module - }); + const csfFactoryPreview = getCsfFactoryPreview(preview); if (csfFactoryPreview) { return csfFactoryPreview.input; @@ -81,7 +79,7 @@ export async function generateModernIframeScriptCode(options: Options, projectRo setup(); - import { composeConfigs, PreviewWeb, ClientApi } from 'storybook/internal/preview-api'; + import { composeConfigs, PreviewWeb, ClientApi, getCsfFactoryPreview } from 'storybook/internal/preview-api'; import { importFn } from '${SB_VIRTUAL_FILES.VIRTUAL_STORIES_FILE}'; diff --git a/code/core/src/preview-api/index.ts b/code/core/src/preview-api/index.ts index 1b46b91ecd75..59fe9b4cddc7 100644 --- a/code/core/src/preview-api/index.ts +++ b/code/core/src/preview-api/index.ts @@ -56,7 +56,13 @@ export { normalizeProjectAnnotations, } from './store'; -export { createPlaywrightTest } from './modules/store/csf/portable-stories'; +/** CSF API */ +export { + createPlaywrightTest, + getCsfFactoryPreview, + getCsfFactoryAnnotations, + isCsfFactory, +} from './modules/store/csf'; export type { PropDescriptor } from './store'; diff --git a/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts b/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts index b5fc421e5364..c84bf2e16e12 100644 --- a/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts +++ b/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts @@ -13,7 +13,7 @@ import type { import { dedent } from 'ts-dedent'; -import type { StoryStore } from '../../store'; +import { type StoryStore, isCsfFactory } from '../../store'; import type { DocsContextProps } from './DocsContextProps'; export class DocsContext implements DocsContextProps { @@ -163,7 +163,8 @@ export class DocsContext implements DocsContextProps } const story = this.exportToStory.get( - 'isCSFFactory' in moduleExportOrType ? moduleExportOrType.input : moduleExportOrType + // TODO: @kasperpeulen will fix this once csf factory types are defined + isCsfFactory(moduleExportOrType) ? (moduleExportOrType as any).input : moduleExportOrType ); if (story) { diff --git a/code/core/src/preview-api/modules/store/csf/csf-factory-utils.ts b/code/core/src/preview-api/modules/store/csf/csf-factory-utils.ts new file mode 100644 index 000000000000..def96170fca1 --- /dev/null +++ b/code/core/src/preview-api/modules/store/csf/csf-factory-utils.ts @@ -0,0 +1,42 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +/* eslint-disable no-underscore-dangle */ +import type { + Args, + ComponentAnnotations, + LegacyStoryAnnotationsOrFn, + ModuleExports, + ProjectAnnotations, + Renderer, + StoryAnnotations, +} from '@storybook/types'; + +export function getCsfFactoryPreview(preview: ModuleExports): ProjectAnnotations | null { + return Object.values(preview).find(isCsfFactory) ?? null; +} + +export function isCsfFactory(target: StoryAnnotations | ProjectAnnotations) { + return ( + target != null && + typeof target === 'object' && + ('isCSFFactory' in target || 'isCSFFactoryPreview' in target) + ); +} + +export function getCsfFactoryAnnotations< + TRenderer extends Renderer = Renderer, + TArgs extends Args = Args, +>( + story: LegacyStoryAnnotationsOrFn, + meta?: ComponentAnnotations, + projectAnnotations?: ProjectAnnotations +) { + const _isCsfFactory = isCsfFactory(story); + + return { + // TODO: @kasperpeulen will fix this once csf factory types are defined + story: _isCsfFactory ? (story as any)?.input : story, + meta: _isCsfFactory ? (story as any)?.meta?.input : meta, + preview: _isCsfFactory ? (story as any)?.config?.input : projectAnnotations, + }; +} diff --git a/code/core/src/preview-api/modules/store/csf/index.ts b/code/core/src/preview-api/modules/store/csf/index.ts index 3ce7ca25109e..6d997c90903a 100644 --- a/code/core/src/preview-api/modules/store/csf/index.ts +++ b/code/core/src/preview-api/modules/store/csf/index.ts @@ -8,3 +8,4 @@ export * from './getValuesFromArgTypes'; export * from './composeConfigs'; export * from './stepRunners'; export * from './portable-stories'; +export * from './csf-factory-utils'; 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 43b3f385e7f7..5db320501502 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 @@ -28,6 +28,7 @@ import { dedent } from 'ts-dedent'; import { HooksContext } from '../../../addons'; import { ReporterAPI } from '../reporter-api'; import { composeConfigs } from './composeConfigs'; +import { getCsfFactoryAnnotations } from './csf-factory-utils'; import { getValuesFromArgTypes } from './getValuesFromArgTypes'; import { normalizeComponentAnnotations } from './normalizeComponentAnnotations'; import { normalizeProjectAnnotations } from './normalizeProjectAnnotations'; @@ -89,21 +90,6 @@ export function setProjectAnnotations( const cleanups: CleanupCallback[] = []; -export function getAnnotations( - story: LegacyStoryAnnotationsOrFn, - meta?: ComponentAnnotations, - projectAnnotations?: ProjectAnnotations -) { - const isCsfFactory = - (typeof story === 'function' || typeof story === 'object') && 'isCSFFactory' in story; - - return { - storyAnnotations: isCsfFactory ? (story as any)?.input : story, - componentAnnotations: isCsfFactory ? (story as any)?.meta?.input : meta, - projectAnnotations: isCsfFactory ? (story as any)?.config?.input : projectAnnotations, - }; -} - export function composeStory( storyAnnotations: LegacyStoryAnnotationsOrFn, componentAnnotations: ComponentAnnotations, @@ -295,7 +281,8 @@ export function composeStories( const composedStories = Object.entries(stories).reduce( (storiesMap, [exportsName, story]: [string, any]) => { - const { storyAnnotations, componentAnnotations } = getAnnotations(story); + const { story: storyAnnotations, meta: componentAnnotations } = + getCsfFactoryAnnotations(story); if (!meta && componentAnnotations) { meta = componentAnnotations; } diff --git a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts index 6c47c2d69dec..9242d7cf8d18 100644 --- a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts +++ b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts @@ -48,6 +48,7 @@ export function processCSFFile( const firstStory: any = Object.values(namedExports)[0]; // CSF4 + // TODO: @kasperpeulen will fix this once csf factory types are defined if (!defaultExport && 'isCSFFactory' in firstStory) { const meta: NormalizedComponentAnnotations = normalizeComponentAnnotations(firstStory.meta.input, title, importPath); From 3f6a53e6724a5236a6203235f59e5eb494c06ff3 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 27 Jan 2025 12:40:26 +0100 Subject: [PATCH 094/144] split essentials in index/preset --- code/.storybook/preview.tsx | 2 +- code/addons/essentials/package.json | 15 +-- code/addons/essentials/src/index.ts | 125 +++--------------- code/addons/essentials/src/preset.ts | 107 +++++++++++++++ code/addons/essentials/src/preview.ts | 22 --- .../src/common/utils/get-addon-annotations.ts | 7 +- 6 files changed, 134 insertions(+), 144 deletions(-) create mode 100644 code/addons/essentials/src/preset.ts delete mode 100644 code/addons/essentials/src/preview.ts diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index 48d3252642df..f26260d48339 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -25,7 +25,7 @@ import { definePreview } from '@storybook/react'; import addonTest from '@storybook/experimental-addon-test'; import addonA11y from '@storybook/addon-a11y'; -import addonEssentials from '@storybook/addon-essentials/entry-preview'; +import addonEssentials from '@storybook/addon-essentials'; import addonThemes from '@storybook/addon-themes'; import * as addonsPreview from '../addons/toolbars/template/stories/preview'; diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json index 6a6222d6f41d..1bfbb8aa4e46 100644 --- a/code/addons/essentials/package.json +++ b/code/addons/essentials/package.json @@ -27,11 +27,6 @@ "import": "./dist/index.mjs", "require": "./dist/index.js" }, - "./entry-preview": { - "types": "./dist/preview.d.ts", - "import": "./dist/preview.mjs", - "require": "./dist/preview.js" - }, "./actions/preview": { "types": "./dist/actions/preview.d.ts", "import": "./dist/actions/preview.mjs", @@ -77,6 +72,7 @@ "import": "./dist/viewport/preview.mjs", "require": "./dist/viewport/preview.js" }, + "./preset": "./dist/preset.js", "./package.json": "./package.json" }, "main": "dist/index.js", @@ -86,9 +82,6 @@ "*": { "*": [ "dist/index.d.ts" - ], - "entry-preview": [ - "dist/preview.d.ts" ] } }, @@ -126,10 +119,13 @@ }, "bundler": { "nodeEntries": [ - "./src/index.ts", + "./src/preset.ts", "./src/docs/preset.ts", "./src/docs/mdx-react-shim.ts" ], + "exportEntries": [ + "./src/index.ts" + ], "entries": [ "./src/docs/manager.ts" ], @@ -144,7 +140,6 @@ "./src/viewport/manager.ts" ], "previewEntries": [ - "./src/preview.ts", "./src/actions/preview.ts", "./src/backgrounds/preview.ts", "./src/docs/preview.ts", diff --git a/code/addons/essentials/src/index.ts b/code/addons/essentials/src/index.ts index a72554227ba2..e39952299d9e 100644 --- a/code/addons/essentials/src/index.ts +++ b/code/addons/essentials/src/index.ts @@ -1,107 +1,22 @@ -import { isAbsolute, join } from 'node:path'; +import { composeConfigs, definePreview } from 'storybook/internal/preview-api'; -import { serverRequire } from 'storybook/internal/common'; -import { logger } from 'storybook/internal/node-logger'; +import actionsAddon from '@storybook/addon-actions'; +import backgroundsAddon from '@storybook/addon-backgrounds'; +import docsAddon from '@storybook/addon-docs'; +import highlightAddon from '@storybook/addon-highlight'; +import measureAddon from '@storybook/addon-measure'; +import outlineAddon from '@storybook/addon-outline'; +import viewportAddon from '@storybook/addon-viewport'; -interface PresetOptions { - /** - * Allow to use @storybook/addon-actions - * - * @default true - * @see https://storybook.js.org/addons/@storybook/addon-actions - */ - actions?: boolean; - /** - * Allow to use @storybook/addon-backgrounds - * - * @default true - * @see https://storybook.js.org/addons/@storybook/addon-backgrounds - */ - backgrounds?: boolean; - configDir: string; - /** - * Allow to use @storybook/addon-controls - * - * @default true - * @see https://storybook.js.org/addons/@storybook/addon-controls - */ - controls?: boolean; - /** - * Allow to use @storybook/addon-docs - * - * @default true - * @see https://storybook.js.org/addons/@storybook/addon-docs - */ - docs?: boolean; - /** - * Allow to use @storybook/addon-measure - * - * @default true - * @see https://storybook.js.org/addons/@storybook/addon-measure - */ - measure?: boolean; - /** - * Allow to use @storybook/addon-outline - * - * @default true - * @see https://storybook.js.org/addons/@storybook/addon-outline - */ - outline?: boolean; - themes?: boolean; - /** - * Allow to use @storybook/addon-toolbars - * - * @default true - * @see https://storybook.js.org/addons/@storybook/addon-toolbars - */ - toolbars?: boolean; - /** - * Allow to use @storybook/addon-viewport - * - * @default true - * @see https://storybook.js.org/addons/@storybook/addon-viewport - */ - viewport?: boolean; -} - -const requireMain = (configDir: string) => { - const absoluteConfigDir = isAbsolute(configDir) ? configDir : join(process.cwd(), configDir); - const mainFile = join(absoluteConfigDir, 'main'); - - return serverRequire(mainFile) ?? {}; -}; - -export function addons(options: PresetOptions) { - const checkInstalled = (addonName: string, main: any) => { - const addon = `@storybook/addon-${addonName}`; - const existingAddon = main.addons?.find((entry: string | { name: string }) => { - const name = typeof entry === 'string' ? entry : entry.name; - return name?.startsWith(addon); - }); - if (existingAddon) { - logger.info(`Found existing addon ${JSON.stringify(existingAddon)}, skipping.`); - } - return !!existingAddon; - }; - - const main = requireMain(options.configDir); - - // NOTE: The order of these addons is important. - return [ - 'controls', - 'actions', - 'docs', - 'backgrounds', - 'viewport', - 'toolbars', - 'measure', - 'outline', - 'highlight', - ] - .filter((key) => (options as any)[key] !== false) - .filter((addon) => !checkInstalled(addon, main)) - .map((addon) => { - // We point to the re-export from addon-essentials to support yarn pnp and pnpm. - return `@storybook/addon-essentials/${addon}`; - }); -} +export default () => + definePreview( + composeConfigs([ + actionsAddon(), + docsAddon(), + backgroundsAddon(), + viewportAddon(), + measureAddon(), + outlineAddon(), + highlightAddon(), + ]) + ); diff --git a/code/addons/essentials/src/preset.ts b/code/addons/essentials/src/preset.ts new file mode 100644 index 000000000000..a72554227ba2 --- /dev/null +++ b/code/addons/essentials/src/preset.ts @@ -0,0 +1,107 @@ +import { isAbsolute, join } from 'node:path'; + +import { serverRequire } from 'storybook/internal/common'; +import { logger } from 'storybook/internal/node-logger'; + +interface PresetOptions { + /** + * Allow to use @storybook/addon-actions + * + * @default true + * @see https://storybook.js.org/addons/@storybook/addon-actions + */ + actions?: boolean; + /** + * Allow to use @storybook/addon-backgrounds + * + * @default true + * @see https://storybook.js.org/addons/@storybook/addon-backgrounds + */ + backgrounds?: boolean; + configDir: string; + /** + * Allow to use @storybook/addon-controls + * + * @default true + * @see https://storybook.js.org/addons/@storybook/addon-controls + */ + controls?: boolean; + /** + * Allow to use @storybook/addon-docs + * + * @default true + * @see https://storybook.js.org/addons/@storybook/addon-docs + */ + docs?: boolean; + /** + * Allow to use @storybook/addon-measure + * + * @default true + * @see https://storybook.js.org/addons/@storybook/addon-measure + */ + measure?: boolean; + /** + * Allow to use @storybook/addon-outline + * + * @default true + * @see https://storybook.js.org/addons/@storybook/addon-outline + */ + outline?: boolean; + themes?: boolean; + /** + * Allow to use @storybook/addon-toolbars + * + * @default true + * @see https://storybook.js.org/addons/@storybook/addon-toolbars + */ + toolbars?: boolean; + /** + * Allow to use @storybook/addon-viewport + * + * @default true + * @see https://storybook.js.org/addons/@storybook/addon-viewport + */ + viewport?: boolean; +} + +const requireMain = (configDir: string) => { + const absoluteConfigDir = isAbsolute(configDir) ? configDir : join(process.cwd(), configDir); + const mainFile = join(absoluteConfigDir, 'main'); + + return serverRequire(mainFile) ?? {}; +}; + +export function addons(options: PresetOptions) { + const checkInstalled = (addonName: string, main: any) => { + const addon = `@storybook/addon-${addonName}`; + const existingAddon = main.addons?.find((entry: string | { name: string }) => { + const name = typeof entry === 'string' ? entry : entry.name; + return name?.startsWith(addon); + }); + if (existingAddon) { + logger.info(`Found existing addon ${JSON.stringify(existingAddon)}, skipping.`); + } + return !!existingAddon; + }; + + const main = requireMain(options.configDir); + + // NOTE: The order of these addons is important. + return [ + 'controls', + 'actions', + 'docs', + 'backgrounds', + 'viewport', + 'toolbars', + 'measure', + 'outline', + 'highlight', + ] + .filter((key) => (options as any)[key] !== false) + .filter((addon) => !checkInstalled(addon, main)) + .map((addon) => { + // We point to the re-export from addon-essentials to support yarn pnp and pnpm. + return `@storybook/addon-essentials/${addon}`; + }); +} diff --git a/code/addons/essentials/src/preview.ts b/code/addons/essentials/src/preview.ts deleted file mode 100644 index e39952299d9e..000000000000 --- a/code/addons/essentials/src/preview.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { composeConfigs, definePreview } from 'storybook/internal/preview-api'; - -import actionsAddon from '@storybook/addon-actions'; -import backgroundsAddon from '@storybook/addon-backgrounds'; -import docsAddon from '@storybook/addon-docs'; -import highlightAddon from '@storybook/addon-highlight'; -import measureAddon from '@storybook/addon-measure'; -import outlineAddon from '@storybook/addon-outline'; -import viewportAddon from '@storybook/addon-viewport'; - -export default () => - definePreview( - composeConfigs([ - actionsAddon(), - docsAddon(), - backgroundsAddon(), - viewportAddon(), - measureAddon(), - outlineAddon(), - highlightAddon(), - ]) - ); diff --git a/code/core/src/common/utils/get-addon-annotations.ts b/code/core/src/common/utils/get-addon-annotations.ts index 04ac71771606..eb99bdcd110d 100644 --- a/code/core/src/common/utils/get-addon-annotations.ts +++ b/code/core/src/common/utils/get-addon-annotations.ts @@ -37,12 +37,7 @@ export async function getAddonAnnotations(addon: string) { data.importPath = `@storybook/${addon}/preview`; } - // TODO: current workaround needed only for essentials, fix this once we change the preview entry-point for that package - if (addon === '@storybook/addon-essentials') { - data.importPath = '@storybook/addon-essentials/entry-preview'; - } else { - require.resolve(path.join(addon, 'preview')); - } + require.resolve(path.join(addon, 'preview')); return data; } catch (err) {} From 2c13a6652d187c2c5fb69bbcd396b6a76b966095 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Mon, 27 Jan 2025 14:35:44 +0100 Subject: [PATCH 095/144] Introduce AnyRenderer --- code/core/src/preview-api/modules/addons/definePreview.ts | 4 ++-- code/core/src/types/modules/story.ts | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/code/core/src/preview-api/modules/addons/definePreview.ts b/code/core/src/preview-api/modules/addons/definePreview.ts index 3a861671f393..b6f68214e685 100644 --- a/code/core/src/preview-api/modules/addons/definePreview.ts +++ b/code/core/src/preview-api/modules/addons/definePreview.ts @@ -1,5 +1,5 @@ -import type { ProjectAnnotations, Renderer } from '@storybook/types'; +import type { AnyRenderer, ProjectAnnotations, Renderer } from '@storybook/core/types'; -export function definePreview(config: ProjectAnnotations) { +export function definePreview(config: ProjectAnnotations) { return config; } diff --git a/code/core/src/types/modules/story.ts b/code/core/src/types/modules/story.ts index a823863defaf..4e4d341cb9ab 100644 --- a/code/core/src/types/modules/story.ts +++ b/code/core/src/types/modules/story.ts @@ -28,6 +28,12 @@ import type { StrictGlobalTypes, } from './csf'; +export interface AnyRenderer extends Renderer { + component: any; + storyResult: any; + canvasElement: any; +} + // Store Types export interface WebRenderer extends Renderer { canvasElement: HTMLElement; From 7967016e4ff8b9796bc6b91a978dba40cc731da1 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 27 Jan 2025 17:24:42 +0100 Subject: [PATCH 096/144] add /preview to addon-essentials --- code/addons/essentials/package.json | 9 ++++++++ code/addons/essentials/src/index.ts | 23 +++---------------- code/addons/essentials/src/preview.ts | 19 +++++++++++++++ .../builders/builder-vite/src/optimizeDeps.ts | 1 + 4 files changed, 32 insertions(+), 20 deletions(-) create mode 100644 code/addons/essentials/src/preview.ts diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json index 1bfbb8aa4e46..3192da3366ea 100644 --- a/code/addons/essentials/package.json +++ b/code/addons/essentials/package.json @@ -27,6 +27,11 @@ "import": "./dist/index.mjs", "require": "./dist/index.js" }, + "./preview": { + "types": "./dist/preview.d.ts", + "import": "./dist/preview.mjs", + "require": "./dist/preview.js" + }, "./actions/preview": { "types": "./dist/actions/preview.d.ts", "import": "./dist/actions/preview.mjs", @@ -82,6 +87,9 @@ "*": { "*": [ "dist/index.d.ts" + ], + "preview": [ + "dist/preview.d.ts" ] } }, @@ -140,6 +148,7 @@ "./src/viewport/manager.ts" ], "previewEntries": [ + "./src/preview.ts", "./src/actions/preview.ts", "./src/backgrounds/preview.ts", "./src/docs/preview.ts", diff --git a/code/addons/essentials/src/index.ts b/code/addons/essentials/src/index.ts index e39952299d9e..3ccfb15a26aa 100644 --- a/code/addons/essentials/src/index.ts +++ b/code/addons/essentials/src/index.ts @@ -1,22 +1,5 @@ -import { composeConfigs, definePreview } from 'storybook/internal/preview-api'; +import { definePreview } from 'storybook/internal/preview-api'; -import actionsAddon from '@storybook/addon-actions'; -import backgroundsAddon from '@storybook/addon-backgrounds'; -import docsAddon from '@storybook/addon-docs'; -import highlightAddon from '@storybook/addon-highlight'; -import measureAddon from '@storybook/addon-measure'; -import outlineAddon from '@storybook/addon-outline'; -import viewportAddon from '@storybook/addon-viewport'; +import addonAnnotations from './preview'; -export default () => - definePreview( - composeConfigs([ - actionsAddon(), - docsAddon(), - backgroundsAddon(), - viewportAddon(), - measureAddon(), - outlineAddon(), - highlightAddon(), - ]) - ); +export default () => definePreview(addonAnnotations); diff --git a/code/addons/essentials/src/preview.ts b/code/addons/essentials/src/preview.ts new file mode 100644 index 000000000000..5298651f4d19 --- /dev/null +++ b/code/addons/essentials/src/preview.ts @@ -0,0 +1,19 @@ +import { composeConfigs } from 'storybook/internal/preview-api'; + +import actionsAddon from '@storybook/addon-actions'; +import backgroundsAddon from '@storybook/addon-backgrounds'; +import docsAddon from '@storybook/addon-docs'; +import highlightAddon from '@storybook/addon-highlight'; +import measureAddon from '@storybook/addon-measure'; +import outlineAddon from '@storybook/addon-outline'; +import viewportAddon from '@storybook/addon-viewport'; + +export default composeConfigs([ + actionsAddon(), + docsAddon(), + backgroundsAddon(), + viewportAddon(), + measureAddon(), + outlineAddon(), + highlightAddon(), +]); diff --git a/code/builders/builder-vite/src/optimizeDeps.ts b/code/builders/builder-vite/src/optimizeDeps.ts index 74dd0090be42..696d523086df 100644 --- a/code/builders/builder-vite/src/optimizeDeps.ts +++ b/code/builders/builder-vite/src/optimizeDeps.ts @@ -17,6 +17,7 @@ const INCLUDE_CANDIDATES = [ '@storybook/addon-backgrounds/preview', '@storybook/addon-designs/blocks', '@storybook/addon-docs/preview', + '@storybook/addon-essentials/preview', '@storybook/addon-essentials/actions/preview', '@storybook/addon-essentials/actions/preview', '@storybook/addon-essentials/backgrounds/preview', From 488fd4f3db753c76b64ff32136c02eb6832d7215 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 28 Jan 2025 12:02:02 +0100 Subject: [PATCH 097/144] CLI: Run csf-2-to-3 as part of csf-factories codemod --- .../src/codemod/csf-factories.ts | 24 +++++++++++++++---- .../codemod/helpers/story-to-csf-factory.ts | 14 +++++------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/code/lib/cli-storybook/src/codemod/csf-factories.ts b/code/lib/cli-storybook/src/codemod/csf-factories.ts index d5d3a73108af..da2ca82aa81f 100644 --- a/code/lib/cli-storybook/src/codemod/csf-factories.ts +++ b/code/lib/cli-storybook/src/codemod/csf-factories.ts @@ -1,4 +1,4 @@ -import { syncStorybookAddons } from 'storybook/internal/common'; +import { type JsPackageManager, syncStorybookAddons } from 'storybook/internal/common'; import prompts from 'prompts'; @@ -10,7 +10,11 @@ import { storyToCsfFactory } from './helpers/story-to-csf-factory'; export const logger = console; -async function runStoriesCodemod(dryRun: boolean | undefined) { +async function runStoriesCodemod(options: { + dryRun: boolean | undefined; + packageManager: JsPackageManager; +}) { + const { dryRun, packageManager } = options; try { let globString = 'src/stories/*.stories.*'; if (!process.env.IN_STORYBOOK_SANDBOX) { @@ -24,12 +28,23 @@ async function runStoriesCodemod(dryRun: boolean | undefined) { }) ).glob; } + + logger.log('Applying codemod on your stories, this might take some time...'); + + // TODO: Move the csf-2-to-3 codemod into automigrations + await packageManager.executeCommand({ + command: `${packageManager.getRemoteRunCommand()} storybook migrate csf-2-to-3 --glob=${globString}`, + args: [], + stdio: 'ignore', + ignoreError: true, + }); + await runCodemod(globString, storyToCsfFactory, { dryRun }); } catch (err: any) { console.log('err message', err.message); if (err.message === 'No files matched') { console.log('going to run again'); - await runStoriesCodemod(dryRun); + await runStoriesCodemod(options); } else { throw err; } @@ -55,8 +70,7 @@ export const csfFactories: CommandFix = { }; await packageManager.writePackageJson(packageJson); - logger.log('Applying codemod on your stories...'); - await runStoriesCodemod(dryRun); + await runStoriesCodemod({ dryRun, packageManager }); logger.log('Applying codemod on your main config...'); const frameworkPackage = diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts index 96a92869606d..61ba048e8970 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts @@ -77,17 +77,17 @@ export async function storyToCsfFactory(info: FileInfo) { let init = t.isVariableDeclarator(declarator) ? declarator.init : undefined; if (t.isIdentifier(id) && init) { + // Remove type annotations e.g. A in `const Story: A = {};` + if (id.typeAnnotation) { + id.typeAnnotation = null; + } + + // Remove type annotations e.g. A in `const Story = {} satisfies A;` if (t.isTSSatisfiesExpression(init) || t.isTSAsExpression(init)) { init = init.expression; } if (t.isObjectExpression(init)) { - const typeAnnotation = id.typeAnnotation; - // Remove type annotation as it's now inferred - if (typeAnnotation) { - id.typeAnnotation = null; - } - // Wrap the object in `meta.story()` declarator.init = t.callExpression( t.memberExpression(t.identifier(metaVariableName), t.identifier('story')), @@ -161,7 +161,7 @@ export async function storyToCsfFactory(info: FileInfo) { // This is a tough one to support, we just skip for now. // Relates to `Stories.Story.args` where Stories is coming from another file. We can't know whether it should be transformed or not. if (err.message.includes(`instead got "MemberExpression"`)) { - console.log({ path }); + return; } else { throw err; } From 43e3c4e67022d82829489d9312d3ac5cd652457e Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 28 Jan 2025 14:41:04 +0100 Subject: [PATCH 098/144] Fix type issues --- code/addons/links/package.json | 2 +- code/addons/test/package.json | 2 +- code/core/package.json | 2 +- .../modules/addons/definePreview.ts | 4 +-- code/core/src/types/modules/story.ts | 6 ----- code/lib/blocks/package.json | 2 +- code/lib/codemod/package.json | 2 +- code/lib/source-loader/package.json | 2 +- code/lib/test/package.json | 2 +- code/package.json | 2 +- code/renderers/server/package.json | 2 +- code/yarn.lock | 26 +++++++++---------- scripts/prepare/addon-bundle.ts | 1 + 13 files changed, 25 insertions(+), 30 deletions(-) diff --git a/code/addons/links/package.json b/code/addons/links/package.json index a2b97e0d8a9f..9220a09055a5 100644 --- a/code/addons/links/package.json +++ b/code/addons/links/package.json @@ -68,7 +68,7 @@ "prep": "jiti ../../../scripts/prepare/addon-bundle.ts" }, "dependencies": { - "@storybook/csf": "0.1.12", + "@storybook/csf": "0.1.14--canary.f3da709.0", "@storybook/global": "^5.0.0", "ts-dedent": "^2.0.0" }, diff --git a/code/addons/test/package.json b/code/addons/test/package.json index 4599ea860ca8..de8be08be10d 100644 --- a/code/addons/test/package.json +++ b/code/addons/test/package.json @@ -91,7 +91,7 @@ "prep": "jiti ../../../scripts/prepare/addon-bundle.ts" }, "dependencies": { - "@storybook/csf": "0.1.12", + "@storybook/csf": "0.1.14--canary.f3da709.0", "@storybook/global": "^5.0.0", "@storybook/icons": "^1.2.12", "@storybook/instrumenter": "workspace:*", diff --git a/code/core/package.json b/code/core/package.json index 0163c206581c..76008e354161 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -274,7 +274,7 @@ "prep": "jiti ./scripts/prep.ts" }, "dependencies": { - "@storybook/csf": "0.1.12", + "@storybook/csf": "0.1.14--canary.f3da709.0", "@storybook/theming": "workspace:*", "better-opn": "^3.0.2", "browser-assert": "^1.2.1", diff --git a/code/core/src/preview-api/modules/addons/definePreview.ts b/code/core/src/preview-api/modules/addons/definePreview.ts index b6f68214e685..da17e62f9f48 100644 --- a/code/core/src/preview-api/modules/addons/definePreview.ts +++ b/code/core/src/preview-api/modules/addons/definePreview.ts @@ -1,5 +1,5 @@ -import type { AnyRenderer, ProjectAnnotations, Renderer } from '@storybook/core/types'; +import type { ProjectAnnotations, Renderer } from '@storybook/core/types'; -export function definePreview(config: ProjectAnnotations) { +export function definePreview(config: ProjectAnnotations): ProjectAnnotations { return config; } diff --git a/code/core/src/types/modules/story.ts b/code/core/src/types/modules/story.ts index 4e4d341cb9ab..a823863defaf 100644 --- a/code/core/src/types/modules/story.ts +++ b/code/core/src/types/modules/story.ts @@ -28,12 +28,6 @@ import type { StrictGlobalTypes, } from './csf'; -export interface AnyRenderer extends Renderer { - component: any; - storyResult: any; - canvasElement: any; -} - // Store Types export interface WebRenderer extends Renderer { canvasElement: HTMLElement; diff --git a/code/lib/blocks/package.json b/code/lib/blocks/package.json index 01745d4358f4..b437b9eadef2 100644 --- a/code/lib/blocks/package.json +++ b/code/lib/blocks/package.json @@ -43,7 +43,7 @@ "prep": "jiti ../../../scripts/prepare/bundle.ts" }, "dependencies": { - "@storybook/csf": "0.1.12", + "@storybook/csf": "0.1.14--canary.f3da709.0", "@storybook/icons": "^1.2.12", "ts-dedent": "^2.0.0" }, diff --git a/code/lib/codemod/package.json b/code/lib/codemod/package.json index 86774227d866..5dd7c9bb98d7 100644 --- a/code/lib/codemod/package.json +++ b/code/lib/codemod/package.json @@ -58,7 +58,7 @@ "@babel/preset-env": "^7.24.4", "@babel/types": "^7.24.0", "@storybook/core": "workspace:*", - "@storybook/csf": "0.1.12", + "@storybook/csf": "0.1.14--canary.f3da709.0", "@types/cross-spawn": "^6.0.2", "cross-spawn": "^7.0.3", "es-toolkit": "^1.22.0", diff --git a/code/lib/source-loader/package.json b/code/lib/source-loader/package.json index cf20356e14c8..f32ee4cc585a 100644 --- a/code/lib/source-loader/package.json +++ b/code/lib/source-loader/package.json @@ -44,7 +44,7 @@ "prep": "jiti ../../../scripts/prepare/bundle.ts" }, "dependencies": { - "@storybook/csf": "0.1.12", + "@storybook/csf": "0.1.14--canary.f3da709.0", "es-toolkit": "^1.22.0", "estraverse": "^5.2.0", "prettier": "^3.1.1" diff --git a/code/lib/test/package.json b/code/lib/test/package.json index 6cd4d757fdd6..1b3845456984 100644 --- a/code/lib/test/package.json +++ b/code/lib/test/package.json @@ -43,7 +43,7 @@ "prep": "jiti ../../../scripts/prepare/bundle.ts" }, "dependencies": { - "@storybook/csf": "0.1.12", + "@storybook/csf": "0.1.14--canary.f3da709.0", "@storybook/global": "^5.0.0", "@storybook/instrumenter": "workspace:*", "@testing-library/dom": "10.4.0", diff --git a/code/package.json b/code/package.json index dbe37c930e52..7dee604abe42 100644 --- a/code/package.json +++ b/code/package.json @@ -123,7 +123,7 @@ "@storybook/codemod": "workspace:*", "@storybook/core": "workspace:*", "@storybook/core-webpack": "workspace:*", - "@storybook/csf": "0.1.12", + "@storybook/csf": "0.1.14--canary.f3da709.0", "@storybook/csf-plugin": "workspace:*", "@storybook/ember": "workspace:*", "@storybook/eslint-config-storybook": "^4.0.0", diff --git a/code/renderers/server/package.json b/code/renderers/server/package.json index da1c6c459ba8..8c4122a5e3f3 100644 --- a/code/renderers/server/package.json +++ b/code/renderers/server/package.json @@ -46,7 +46,7 @@ }, "dependencies": { "@storybook/components": "workspace:*", - "@storybook/csf": "0.1.12", + "@storybook/csf": "0.1.14--canary.f3da709.0", "@storybook/global": "^5.0.0", "@storybook/manager-api": "workspace:*", "@storybook/preview-api": "workspace:*", diff --git a/code/yarn.lock b/code/yarn.lock index ef48ed028cb8..79fae049a29b 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -7236,7 +7236,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/addon-links@workspace:addons/links" dependencies: - "@storybook/csf": "npm:0.1.12" + "@storybook/csf": "npm:0.1.14--canary.f3da709.0" "@storybook/global": "npm:^5.0.0" ts-dedent: "npm:^2.0.0" typescript: "npm:^5.3.2" @@ -7465,7 +7465,7 @@ __metadata: resolution: "@storybook/blocks@workspace:lib/blocks" dependencies: "@storybook/addon-actions": "workspace:*" - "@storybook/csf": "npm:0.1.12" + "@storybook/csf": "npm:0.1.14--canary.f3da709.0" "@storybook/icons": "npm:^1.2.12" "@storybook/react": "workspace:*" "@storybook/test": "workspace:*" @@ -7629,7 +7629,7 @@ __metadata: "@babel/preset-env": "npm:^7.24.4" "@babel/types": "npm:^7.24.0" "@storybook/core": "workspace:*" - "@storybook/csf": "npm:0.1.12" + "@storybook/csf": "npm:0.1.14--canary.f3da709.0" "@types/cross-spawn": "npm:^6.0.2" "@types/jscodeshift": "npm:^0.11.10" ansi-regex: "npm:^6.0.1" @@ -7725,7 +7725,7 @@ __metadata: "@radix-ui/react-dialog": "npm:^1.0.5" "@radix-ui/react-scroll-area": "npm:1.2.0-rc.7" "@radix-ui/react-slot": "npm:^1.0.2" - "@storybook/csf": "npm:0.1.12" + "@storybook/csf": "npm:0.1.14--canary.f3da709.0" "@storybook/docs-mdx": "npm:4.0.0-next.1" "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^1.2.12" @@ -7865,12 +7865,12 @@ __metadata: languageName: unknown linkType: soft -"@storybook/csf@npm:0.1.12": - version: 0.1.12 - resolution: "@storybook/csf@npm:0.1.12" +"@storybook/csf@npm:0.1.14--canary.f3da709.0": + version: 0.1.14--canary.f3da709.0 + resolution: "@storybook/csf@npm:0.1.14--canary.f3da709.0" dependencies: type-fest: "npm:^2.19.0" - checksum: 10c0/3d96a976ada67eb683279338d1eb6aa730b228107d4c4f6616ea7b94061899c1fdc11957a756e7bc0708d18cb39af0010c865d124efd84559cd82dcb2d8bc959 + checksum: 10c0/6b139d195f6696432e12265146d51ed113396ae1ca5b8fba798b6bf4d09be9f16ecbeb0aa55af17a354963f64893b14c166d8626dd6f5e40949ecd2a524eea2f languageName: node linkType: hard @@ -7937,7 +7937,7 @@ __metadata: resolution: "@storybook/experimental-addon-test@workspace:addons/test" dependencies: "@devtools-ds/object-inspector": "npm:^1.1.2" - "@storybook/csf": "npm:0.1.12" + "@storybook/csf": "npm:0.1.14--canary.f3da709.0" "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^1.2.12" "@storybook/instrumenter": "workspace:*" @@ -8586,7 +8586,7 @@ __metadata: "@storybook/codemod": "workspace:*" "@storybook/core": "workspace:*" "@storybook/core-webpack": "workspace:*" - "@storybook/csf": "npm:0.1.12" + "@storybook/csf": "npm:0.1.14--canary.f3da709.0" "@storybook/csf-plugin": "workspace:*" "@storybook/ember": "workspace:*" "@storybook/eslint-config-storybook": "npm:^4.0.0" @@ -8734,7 +8734,7 @@ __metadata: resolution: "@storybook/server@workspace:renderers/server" dependencies: "@storybook/components": "workspace:*" - "@storybook/csf": "npm:0.1.12" + "@storybook/csf": "npm:0.1.14--canary.f3da709.0" "@storybook/global": "npm:^5.0.0" "@storybook/manager-api": "workspace:*" "@storybook/preview-api": "workspace:*" @@ -8751,7 +8751,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/source-loader@workspace:lib/source-loader" dependencies: - "@storybook/csf": "npm:0.1.12" + "@storybook/csf": "npm:0.1.14--canary.f3da709.0" es-toolkit: "npm:^1.22.0" estraverse: "npm:^5.2.0" prettier: "npm:^3.1.1" @@ -8856,7 +8856,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/test@workspace:lib/test" dependencies: - "@storybook/csf": "npm:0.1.12" + "@storybook/csf": "npm:0.1.14--canary.f3da709.0" "@storybook/global": "npm:^5.0.0" "@storybook/instrumenter": "workspace:*" "@testing-library/dom": "npm:10.4.0" diff --git a/scripts/prepare/addon-bundle.ts b/scripts/prepare/addon-bundle.ts index 21e7f1b0bbc8..b5d2b97b3b5e 100755 --- a/scripts/prepare/addon-bundle.ts +++ b/scripts/prepare/addon-bundle.ts @@ -130,6 +130,7 @@ const run = async ({ cwd, flags }: { cwd: string; flags: string[] }) => { }; const commonExternals = [ + '@storybook/csf', name, ...extraExternals, ...Object.keys(dependencies || {}), From 5b68c6a64495cf5b6ae21418d3195f49c25586eb Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 28 Jan 2025 16:15:05 +0100 Subject: [PATCH 099/144] fix addon docs issue with --test flag --- code/addons/docs/package.json | 10 ++++++++++ code/addons/essentials/src/preview.ts | 7 +++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/code/addons/docs/package.json b/code/addons/docs/package.json index c77e72929920..61b2037c6c5f 100644 --- a/code/addons/docs/package.json +++ b/code/addons/docs/package.json @@ -81,6 +81,16 @@ "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "dist/index.d.ts" + ], + "preview": [ + "dist/preview.d.ts" + ] + } + }, "files": [ "dist/**/*", "angular/**/*", diff --git a/code/addons/essentials/src/preview.ts b/code/addons/essentials/src/preview.ts index 5298651f4d19..1f624f279c53 100644 --- a/code/addons/essentials/src/preview.ts +++ b/code/addons/essentials/src/preview.ts @@ -2,7 +2,9 @@ import { composeConfigs } from 'storybook/internal/preview-api'; import actionsAddon from '@storybook/addon-actions'; import backgroundsAddon from '@storybook/addon-backgrounds'; -import docsAddon from '@storybook/addon-docs'; +// We can't use docs as function yet because of the --test flag. Once we figure out disabling docs properly in CSF4, we can change this +// eslint-disable-next-line import/namespace +import * as docsAddon from '@storybook/addon-docs/preview'; import highlightAddon from '@storybook/addon-highlight'; import measureAddon from '@storybook/addon-measure'; import outlineAddon from '@storybook/addon-outline'; @@ -10,7 +12,8 @@ import viewportAddon from '@storybook/addon-viewport'; export default composeConfigs([ actionsAddon(), - docsAddon(), + // TODO: we can't use this as function because of the --test flag + docsAddon, backgroundsAddon(), viewportAddon(), measureAddon(), From c81b6a0a36a54524d6b779ed3702a7ff44cc9014 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 29 Jan 2025 15:22:32 +0100 Subject: [PATCH 100/144] Add types for Parameters and Globals for all addons --- code/addons/a11y/src/index.ts | 1 + code/addons/a11y/src/types.ts | 44 ++++ code/addons/actions/src/index.ts | 2 + code/addons/actions/src/types.ts | 34 +++ code/addons/backgrounds/src/index.ts | 2 + code/addons/backgrounds/src/types.ts | 30 +++ code/addons/controls/src/index.ts | 2 + code/addons/controls/src/types.ts | 51 +++++ code/addons/docs/src/index.ts | 1 + code/addons/docs/src/types.ts | 223 +++++++++++++++++++ code/addons/essentials/src/types.ts | 16 ++ code/addons/highlight/src/index.ts | 1 + code/addons/highlight/src/types.ts | 11 + code/addons/interactions/src/preview.ts | 4 +- code/addons/interactions/src/types.ts | 17 ++ code/addons/jest/src/shared.ts | 10 +- code/addons/jest/src/types.ts | 8 + code/addons/measure/src/index.ts | 2 + code/addons/measure/src/types.ts | 11 + code/addons/outline/src/index.ts | 2 + code/addons/outline/src/types.ts | 11 + code/addons/storysource/src/index.ts | 1 + code/addons/storysource/src/types.ts | 38 ++++ code/addons/test/src/index.ts | 2 + code/addons/test/src/types.ts | 17 ++ code/addons/themes/src/constants.ts | 14 +- code/addons/themes/src/decorators/helpers.ts | 8 +- code/addons/themes/src/index.ts | 2 + code/addons/themes/src/theme-switcher.tsx | 8 +- code/addons/themes/src/types.ts | 23 ++ code/addons/viewport/src/types.ts | 60 ++++- 31 files changed, 632 insertions(+), 24 deletions(-) create mode 100644 code/addons/actions/src/types.ts create mode 100644 code/addons/controls/src/types.ts create mode 100644 code/addons/docs/src/types.ts create mode 100644 code/addons/essentials/src/types.ts create mode 100644 code/addons/highlight/src/types.ts create mode 100644 code/addons/interactions/src/types.ts create mode 100644 code/addons/jest/src/types.ts create mode 100644 code/addons/measure/src/types.ts create mode 100644 code/addons/outline/src/types.ts create mode 100644 code/addons/storysource/src/types.ts create mode 100644 code/addons/test/src/types.ts create mode 100644 code/addons/themes/src/types.ts diff --git a/code/addons/a11y/src/index.ts b/code/addons/a11y/src/index.ts index 1cc5889d179f..775cfe3181d2 100644 --- a/code/addons/a11y/src/index.ts +++ b/code/addons/a11y/src/index.ts @@ -4,5 +4,6 @@ import * as addonAnnotations from './preview'; export { PARAM_KEY } from './constants'; export * from './params'; +export type { A11yParameters } from './types'; export default () => definePreview(addonAnnotations); diff --git a/code/addons/a11y/src/types.ts b/code/addons/a11y/src/types.ts index 9e116e5aab2b..56e33372c0a6 100644 --- a/code/addons/a11y/src/types.ts +++ b/code/addons/a11y/src/types.ts @@ -1,3 +1,47 @@ import type { AxeResults } from 'axe-core'; export type A11YReport = AxeResults | { error: Error }; + +export interface A11yParameters { + /** + * Accessibility configuration + * + * @see https://storybook.js.org/docs/writing-tests/accessibility-testing + */ + a11y?: { + /** Manual configuration for specific elements */ + element?: string | string[]; + + /** Configuration for the accessibility rules */ + config?: { + /** Rules to run against the matching elements */ + rules?: Array<{ + id: string; + enabled?: boolean; + selector?: string; + }>; + /** Elements to exclude from accessibility checks */ + exclude?: string[]; + }; + + /** + * Options for the accessibility checks To learn more about the available options, + * + * @see https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter + */ + options?: Record; + + /** Turn off this addon's behavior */ + disable?: boolean; + }; +} + +export interface A11yGlobals { + a11y: { + /** + * Prevent the addon to execute automatic accessibility checks upon visiting a story. You can + * still trigger the checks from the addon panel. + */ + manual?: boolean; + }; +} diff --git a/code/addons/actions/src/index.ts b/code/addons/actions/src/index.ts index 4bd7323699a2..567dd618392b 100644 --- a/code/addons/actions/src/index.ts +++ b/code/addons/actions/src/index.ts @@ -7,3 +7,5 @@ export * from './models'; export * from './runtime'; export default () => definePreview(addonAnnotations); + +export type { ActionsParameters } from './types'; diff --git a/code/addons/actions/src/types.ts b/code/addons/actions/src/types.ts new file mode 100644 index 000000000000..2e2f249eb407 --- /dev/null +++ b/code/addons/actions/src/types.ts @@ -0,0 +1,34 @@ +export interface ActionsParameters { + /** + * Actions configuration + * + * @see https://storybook.js.org/docs/essentials/actions#parameters + */ + actions: { + /** + * Create actions for each arg that matches the regex. + * + * This is quite useful when your component has dozens (or hundreds) of methods and you do not + * want to manually apply the fn utility for each of those methods. However, this is not the + * recommended way of writing actions. That's because automatically inferred args are not + * available as spies in your play function. If you use argTypesRegex and your stories have play + * functions, you will need to also define args with the fn utility to test them in your play + * function. + * + * @example ArgTypesRegex: '^on.*' + */ + argTypesRegex?: string; + + /** Turn off this addon's behavior */ + disable?: boolean; + + /** + * Binds a standard HTML event handler to the outermost HTML element rendered by your component + * and triggers an action when the event is called for a given selector. The format is + * ` `. The selector is optional; it defaults to all elements. + * + * @example Handles: ['mouseover', 'click .btn'] + */ + handles?: string[]; + }; +} diff --git a/code/addons/backgrounds/src/index.ts b/code/addons/backgrounds/src/index.ts index 0e536df78dad..1d169dff54f5 100644 --- a/code/addons/backgrounds/src/index.ts +++ b/code/addons/backgrounds/src/index.ts @@ -3,3 +3,5 @@ import { definePreview } from 'storybook/internal/preview-api'; import * as addonAnnotations from './preview'; export default () => definePreview(addonAnnotations); + +export type { BackgroundsParameters, BackgroundsGlobals } from './types'; diff --git a/code/addons/backgrounds/src/types.ts b/code/addons/backgrounds/src/types.ts index 8f6c66b20a85..067d5b83720b 100644 --- a/code/addons/backgrounds/src/types.ts +++ b/code/addons/backgrounds/src/types.ts @@ -21,3 +21,33 @@ export interface Config { export type GlobalState = { value: string | undefined; grid: boolean }; export type GlobalStateUpdate = Partial; + +export interface BackgroundsParameters { + /** + * Backgrounds configuration + * + * @see https://storybook.js.org/docs/essentials/backgrounds#parameters + */ + backgrounds: { + /** Default background color */ + default?: string; + + /** Turn off this addon's behavior */ + disable?: boolean; + + /** Configuration for the background grid */ + grid?: Partial; + + /** Available background colors */ + values?: Array; + }; +} + +export interface BackgroundsGlobals { + /** + * Backgrounds configuration + * + * @see https://storybook.js.org/docs/essentials/backgrounds#globals + */ + backgrounds: GlobalState; +} diff --git a/code/addons/controls/src/index.ts b/code/addons/controls/src/index.ts index 544b59da0caf..50e8392a4da5 100644 --- a/code/addons/controls/src/index.ts +++ b/code/addons/controls/src/index.ts @@ -3,3 +3,5 @@ import { definePreview } from 'storybook/internal/preview-api'; export { PARAM_KEY } from './constants'; export default () => definePreview({}); + +export type { ControlsParameters } from './types'; diff --git a/code/addons/controls/src/types.ts b/code/addons/controls/src/types.ts new file mode 100644 index 000000000000..65743ed269b7 --- /dev/null +++ b/code/addons/controls/src/types.ts @@ -0,0 +1,51 @@ +export interface ControlsParameters { + /** + * Controls configuration + * + * @see https://storybook.js.org/docs/essentials/controls#parameters-1 + */ + controls: { + /** Turn off this addon's behavior */ + disable?: boolean; + + /** Disable the ability to create or edit stories from the Controls panel */ + disableSaveFromUI?: boolean; + + /** + * Exclude specific properties from the Controls panel + * + * @see https://storybook.js.org/docs/essentials/controls#filtering-controls + */ + exclude?: string[] | RegExp; + + /** + * Show the full documentation for each property in the Controls addon panel, including the + * description and default value. + */ + expanded?: boolean; + + /** + * Exclude only specific properties in the Controls panel + * + * @see https://storybook.js.org/docs/essentials/controls#filtering-controls + */ + include?: string[] | RegExp; + + /** + * Preset color swatches for the color picker control + * + * @example PresetColors: [{ color: '#ff4785', title: 'Coral' }, 'rgba(0, 159, 183, 1)', + * '#fe4a49'] + * + * @see https://storybook.js.org/docs/essentials/controls#specify-initial-preset-color-swatches + */ + presetColors?: Array; + + /** + * Controls sorting order + * + * @see https://storybook.js.org/docs/essentials/controls#sorting-controls + */ + sort?: 'none' | 'alpha' | 'requiredFirst'; + }; +} diff --git a/code/addons/docs/src/index.ts b/code/addons/docs/src/index.ts index f556ef0c6d8a..a37140d58493 100644 --- a/code/addons/docs/src/index.ts +++ b/code/addons/docs/src/index.ts @@ -4,5 +4,6 @@ import * as addonAnnotations from './preview'; export * from '@storybook/blocks'; export { DocsRenderer } from './DocsRenderer'; +export type { DocsParameters } from './types'; export default () => definePreview(addonAnnotations); diff --git a/code/addons/docs/src/types.ts b/code/addons/docs/src/types.ts new file mode 100644 index 000000000000..563f30328fd5 --- /dev/null +++ b/code/addons/docs/src/types.ts @@ -0,0 +1,223 @@ +import type { ModuleExport, ModuleExports } from '@storybook/types'; + +type StoryBlockParameters = { + /** Whether a story's play function runs when shown in docs page */ + autoplay?: boolean; + /** + * Set a minimum height (note for an iframe this is the actual height) when rendering a story in + * an iframe or inline. This overrides parameters.docs.story.iframeHeight for iframes. + */ + height?: string; + /** IFrame configuration */ + iframeHeight?: string; + /** + * Whether the story is rendered inline (in the same browser frame as the other docs content) or + * in an iframe + */ + inline?: boolean; + /** Specifies the CSF file to which the story is associated */ + meta: ModuleExports; + /** + * Specifies which story is rendered by the Story block. If no of is defined and the MDX file is + * attached, the primary (first) story will be rendered. + */ + of: ModuleExport; +}; + +type ControlsBlockParameters = { + /** Exclude specific properties from the Controls panel */ + exclude?: string[] | RegExp; + + /** Exclude only specific properties in the Controls panel */ + include?: string[] | RegExp; + + /** + * Controls sorting order + * + * @see https://storybook.js.org/docs/api/doc-blocks/doc-block-controls#sort + */ + sort?: 'none' | 'alpha' | 'requiredFirst'; +}; + +type ArgTypesBlockParameters = { + /** Exclude specific arg types from the args table */ + exclude?: string[] | RegExp; + + /** Exclude only specific arg types from the args table */ + include?: string[] | RegExp; + + /** + * Specifies which story to get the arg types from. If a CSF file exports is provided, it will use + * the primary (first) story in the file. + */ + of: ModuleExport | ModuleExports; + + /** + * Controls arg types order + * + * @see https://storybook.js.org/docs/api/doc-blocks/doc-block-argtypes#sort + */ + sort?: 'none' | 'alpha' | 'requiredFirst'; +}; + +type CanvasBlockParameters = { + /** + * Provides any additional custom actions to show in the bottom right corner. These are simple + * buttons that do anything you specify in the onClick function. + */ + additionalActions?: { + className?: string; + disabled?: boolean; + onClick: () => void; + title: string | JSX.Element; + }[]; + /** Provide HTML class(es) to the preview element, for custom styling. */ + className?: string; + /** + * Specify how the canvas should layout the story. + * + * @see https://storybook.js.org/docs/api/doc-blocks/doc-block-canvas#layout + */ + layout?: 'centered' | 'fullscreen' | 'padded'; + /** Specifies which story is rendered */ + of: ModuleExport; + /** Show story source code */ + sourceState?: 'hidden' | 'shown'; + /** + * Story configuration + * + * @see https://storybook.js.org/docs/api/doc-blocks/doc-block-canvas#story + */ + story?: StoryBlockParameters; + /** Disable story source code */ + withSource?: 'open' | 'closed' | 'none'; + /** Whether to render a toolbar containing tools to interact with the story. */ + withToolbar?: 'open' | 'closed' | 'none'; +}; + +type DescriptionBlockParameters = { + /** Component description */ + component?: string; + /** Story description */ + story?: string; +}; + +type SourceBlockParameters = { + /** The source code to be rendered. Will be inferred if not passed */ + code?: string; + /** Whether to render the code in dark mode */ + dark?: boolean; + /** Determines if decorators are rendered in the source code snippet. */ + excludeDecorators?: boolean; + /** + * The formatting used on source code. Both true and 'dedent' have the same effect of removing any + * extraneous indentation. Supports all valid prettier parser names. + * + * @see https://storybook.js.org/docs/api/doc-blocks/doc-block-source#format + */ + format?: boolean | 'dedent' | string; + // TODO: We could try to extract types from 'SupportedLanguages' in SyntaxHighlihter, but for now we inline them + /** Source code language */ + language?: + | 'bash' + | 'css' + | 'graphql' + | 'html' + | 'json' + | 'jsextra' + | 'jsx' + | 'md' + | 'text' + | 'tsx' + | 'typescript' + | 'yml'; + /** + * Specifies which story is rendered by the Source block. If no of is defined and the MDX file is + * attached, the primary (first) story will be rendered. + */ + of: ModuleExport; + /** Source code transformations */ + transform?: (code: string, storyContext: any) => string; + /** + * Specifies how the source code is rendered. + * + * @default 'auto' + * @see https://storybook.js.org/docs/api/doc-blocks/doc-block-source#type + */ + type?: 'auto' | 'code' | 'dynamic'; +}; + +export interface DocsParameters { + /** + * Docs configuration + * + * @see https://storybook.js.org/docs/writing-docs + */ + docs?: { + /** + * The subtitle displayed when shown in docs page + * + * @see https://storybook.js.org/docs/api/doc-blocks/doc-block-argtypes + */ + argTypes?: ArgTypesBlockParameters; + + /** + * Canvas configuration when shown in docs page + * + * @see https://storybook.js.org/docs/api/doc-blocks/doc-block-canvas + */ + canvas?: CanvasBlockParameters; + + /** + * Controls block configuration + * + * @see https://storybook.js.org/docs/api/doc-blocks/doc-block-controls + */ + controls?: ControlsBlockParameters; + + /** + * Component/story description when shown in docs page + * + * @see https://storybook.js.org/docs/api/doc-blocks/doc-block-description#writing-descriptions + */ + description?: DescriptionBlockParameters; + + /** Turn off this addon's behavior */ + disable?: boolean; + + /** + * Replace the default documentation template used by Storybook with your own + * + * @see https://storybook.js.org/docs/writing-docs/autodocs#write-a-custom-template + */ + page?: unknown; + + /** + * Source code configuration when shown in docs page + * + * @see https://storybook.js.org/docs/api/doc-blocks/doc-block-source + */ + source?: SourceBlockParameters; + + /** + * Story configuration + * + * @see https://storybook.js.org/docs/api/doc-blocks/doc-block-story + */ + story?: StoryBlockParameters; + + /** + * The subtitle displayed when shown in docs page + * + * @see https://storybook.js.org/docs/api/doc-blocks/doc-block-subtitle + */ + subtitle?: string; + + /** + * The title displayed when shown in docs page + * + * @see https://storybook.js.org/docs/api/doc-blocks/doc-block-title + */ + title?: string; + }; +} diff --git a/code/addons/essentials/src/types.ts b/code/addons/essentials/src/types.ts new file mode 100644 index 000000000000..23b33ce66750 --- /dev/null +++ b/code/addons/essentials/src/types.ts @@ -0,0 +1,16 @@ +import type { ActionsParameters } from '@storybook/addon-actions'; +import type { BackgroundsParameters } from '@storybook/addon-backgrounds'; +import type { DocsParameters } from '@storybook/addon-docs'; +import type { HighlightParameters } from '@storybook/addon-highlight'; +import type { MeasureParameters } from '@storybook/addon-measure'; +import type { OutlineParameters } from '@storybook/addon-outline'; +import type { ViewportParameters } from '@storybook/addon-viewport'; + +export interface EssentialsParameters + extends ActionsParameters, + BackgroundsParameters, + DocsParameters, + HighlightParameters, + MeasureParameters, + OutlineParameters, + ViewportParameters {} diff --git a/code/addons/highlight/src/index.ts b/code/addons/highlight/src/index.ts index 486cfd694f56..16ab5dbd64a3 100644 --- a/code/addons/highlight/src/index.ts +++ b/code/addons/highlight/src/index.ts @@ -3,5 +3,6 @@ import { definePreview } from 'storybook/internal/preview-api'; import './preview'; export { HIGHLIGHT, RESET_HIGHLIGHT } from './constants'; +export type { HighlightParameters } from './types'; export default () => definePreview({}); diff --git a/code/addons/highlight/src/types.ts b/code/addons/highlight/src/types.ts new file mode 100644 index 000000000000..98a801d923ea --- /dev/null +++ b/code/addons/highlight/src/types.ts @@ -0,0 +1,11 @@ +export interface HighlightParameters { + /** + * Highlight configuration + * + * @see https://storybook.js.org/docs/essentials/highlight#parameters + */ + highlight: { + /** Turn off this addon's behavior */ + disable?: boolean; + }; +} diff --git a/code/addons/interactions/src/preview.ts b/code/addons/interactions/src/preview.ts index 8342a162797f..8239442bf912 100644 --- a/code/addons/interactions/src/preview.ts +++ b/code/addons/interactions/src/preview.ts @@ -5,6 +5,8 @@ import { instrument } from '@storybook/instrumenter'; // For 9.0 we want to merge storybook/test and addon-interactions into one addon. import '@storybook/test'; +import type { InteractionsParameters } from './types'; + export const runStep = instrument( { // It seems like the label is unused, but the instrumenter has access to it @@ -18,6 +20,6 @@ export const runStep = instrument( // perhaps csf types need to be updated? StepRunner expects Promise and not Promise | void ).step as StepRunner; -export const parameters = { +export const parameters: InteractionsParameters['interactions'] = { throwPlayFunctionExceptions: false, }; diff --git a/code/addons/interactions/src/types.ts b/code/addons/interactions/src/types.ts new file mode 100644 index 000000000000..c21a192b713b --- /dev/null +++ b/code/addons/interactions/src/types.ts @@ -0,0 +1,17 @@ +export interface InteractionsParameters { + /** + * Interactions configuration + * + * @see https://storybook.js.org/docs/essentials/interactions + */ + interactions: { + /** Turn off this addon's behavior */ + disable?: boolean; + + /** Ignore unhandled errors during test execution */ + dangerouslyIgnoreUnhandledErrors?: boolean; + + /** Whether to throw exceptions coming from the play function */ + throwPlayFunctionExceptions?: boolean; + }; +} diff --git a/code/addons/jest/src/shared.ts b/code/addons/jest/src/shared.ts index 32107bdf23ef..24a705e39724 100644 --- a/code/addons/jest/src/shared.ts +++ b/code/addons/jest/src/shared.ts @@ -2,6 +2,8 @@ import type { StorybookInternalParameters } from 'storybook/internal/types'; import invariant from 'tiny-invariant'; +import type { JestParameters } from './types'; + // addons, panels and events get unique names using a prefix export const PARAM_KEY = 'test'; export const ADDON_ID = 'storybookjs/test'; @@ -9,11 +11,9 @@ export const PANEL_ID = `${ADDON_ID}/panel`; export const ADD_TESTS = `${ADDON_ID}/add_tests`; -interface AddonParameters extends StorybookInternalParameters { - jest?: string | string[] | { disabled: true }; -} - -export function defineJestParameter(parameters: AddonParameters): string[] | null { +export function defineJestParameter( + parameters: JestParameters & StorybookInternalParameters +): string[] | null { const { jest, fileName: filePath } = parameters; if (typeof jest === 'string') { diff --git a/code/addons/jest/src/types.ts b/code/addons/jest/src/types.ts new file mode 100644 index 000000000000..f88cfdc861f6 --- /dev/null +++ b/code/addons/jest/src/types.ts @@ -0,0 +1,8 @@ +export interface JestParameters { + /** + * Jest configuration + * + * @see https://github.com/storybookjs/storybook/blob/next/code/addons/jest/README.md#usage + */ + jest?: string | string[] | { jest: { disabled: true } }; +} diff --git a/code/addons/measure/src/index.ts b/code/addons/measure/src/index.ts index 0e536df78dad..40009898f0db 100644 --- a/code/addons/measure/src/index.ts +++ b/code/addons/measure/src/index.ts @@ -2,4 +2,6 @@ import { definePreview } from 'storybook/internal/preview-api'; import * as addonAnnotations from './preview'; +export type { MeasureParameters } from './types'; + export default () => definePreview(addonAnnotations); diff --git a/code/addons/measure/src/types.ts b/code/addons/measure/src/types.ts new file mode 100644 index 000000000000..8aaf6ecf0d7d --- /dev/null +++ b/code/addons/measure/src/types.ts @@ -0,0 +1,11 @@ +export interface MeasureParameters { + /** + * Measure configuration + * + * @see https://storybook.js.org/docs/essentials/measure-and-outline#parameters + */ + measure: { + /** Turn off this addon's behavior */ + disable?: boolean; + }; +} diff --git a/code/addons/outline/src/index.ts b/code/addons/outline/src/index.ts index 0e536df78dad..459b096ff9b9 100644 --- a/code/addons/outline/src/index.ts +++ b/code/addons/outline/src/index.ts @@ -2,4 +2,6 @@ import { definePreview } from 'storybook/internal/preview-api'; import * as addonAnnotations from './preview'; +export type { OutlineParameters } from './types'; + export default () => definePreview(addonAnnotations); diff --git a/code/addons/outline/src/types.ts b/code/addons/outline/src/types.ts new file mode 100644 index 000000000000..b4eacf61a798 --- /dev/null +++ b/code/addons/outline/src/types.ts @@ -0,0 +1,11 @@ +export interface OutlineParameters { + /** + * Outline configuration + * + * @see https://storybook.js.org/docs/essentials/measure-and-outline#parameters + */ + outline: { + /** Turn off this addon's behavior */ + disable?: boolean; + }; +} diff --git a/code/addons/storysource/src/index.ts b/code/addons/storysource/src/index.ts index 55221ffd2535..3daf3fe3d64e 100644 --- a/code/addons/storysource/src/index.ts +++ b/code/addons/storysource/src/index.ts @@ -1,3 +1,4 @@ import { ADDON_ID, PANEL_ID } from './events'; export { ADDON_ID, PANEL_ID }; +export type { StorySourceParameters } from './types'; diff --git a/code/addons/storysource/src/types.ts b/code/addons/storysource/src/types.ts new file mode 100644 index 000000000000..83ee083c3e56 --- /dev/null +++ b/code/addons/storysource/src/types.ts @@ -0,0 +1,38 @@ +export interface StorySourceParameters { + /** + * Storysource addon configuration + * + * @see https://github.com/storybookjs/storybook/tree/next/code/addons/storysource + */ + storySource?: { + /** Dark mode for source code */ + dark?: boolean; + + /** Turn off this addon's behavior */ + disable?: boolean; + + /** Source code formatting options */ + format?: 'jsx' | 'typescript' | 'javascript'; + + /** Source code language */ + language?: string; + + /** Source code loader options */ + loaderOptions?: { + /** Ignore specific patterns */ + ignore?: string[]; + /** Include specific patterns */ + include?: string[]; + /** Parser options */ + parser?: string; + /** Pretty print source code */ + prettierConfig?: object; + }; + + /** Show story source code */ + showCode?: boolean; + + /** Source code transformations */ + transformSource?: (source: string, storyContext: any) => string; + }; +} diff --git a/code/addons/test/src/index.ts b/code/addons/test/src/index.ts index bdbecfa7e798..e8a2d259667f 100644 --- a/code/addons/test/src/index.ts +++ b/code/addons/test/src/index.ts @@ -5,6 +5,8 @@ import type { storybookTest as storybookTestImport } from './vitest-plugin'; export default () => definePreview(addonAnnotations); +export type { TestParameters } from './types'; + // @ts-expect-error - this is a hack to make the module's sub-path augmentable declare module '@storybook/experimental-addon-test/vitest-plugin' { export const storybookTest: typeof storybookTestImport; diff --git a/code/addons/test/src/types.ts b/code/addons/test/src/types.ts new file mode 100644 index 000000000000..6b9dd033a3b4 --- /dev/null +++ b/code/addons/test/src/types.ts @@ -0,0 +1,17 @@ +export interface TestParameters { + /** + * Interactions configuration + * + * @see https://storybook.js.org/docs/writing-tests/test-addon + */ + test: { + /** Turn off this addon's behavior */ + disable?: boolean; + + /** Ignore unhandled errors during test execution */ + dangerouslyIgnoreUnhandledErrors?: boolean; + + /** Whether to throw exceptions coming from the play function */ + throwPlayFunctionExceptions?: boolean; + }; +} diff --git a/code/addons/themes/src/constants.ts b/code/addons/themes/src/constants.ts index 654cc57ea893..b1f84fea67b6 100644 --- a/code/addons/themes/src/constants.ts +++ b/code/addons/themes/src/constants.ts @@ -1,24 +1,16 @@ +import type { ThemeAddonState, ThemesParameters } from './types'; + export const PARAM_KEY = 'themes' as const; export const ADDON_ID = `storybook/${PARAM_KEY}` as const; export const GLOBAL_KEY = 'theme' as const; export const THEME_SWITCHER_ID = `${ADDON_ID}/theme-switcher` as const; -export interface ThemeAddonState { - themesList: string[]; - themeDefault?: string; -} - export const DEFAULT_ADDON_STATE: ThemeAddonState = { themesList: [], themeDefault: undefined, }; -export interface ThemeParameters { - themeOverride?: string; - disable?: boolean; -} - -export const DEFAULT_THEME_PARAMETERS: ThemeParameters = {}; +export const DEFAULT_THEME_PARAMETERS: ThemesParameters['themes'] = {}; export const THEMING_EVENTS = { REGISTER_THEMES: `${ADDON_ID}/REGISTER_THEMES`, diff --git a/code/addons/themes/src/decorators/helpers.ts b/code/addons/themes/src/decorators/helpers.ts index 97c70dd1f0a4..b3c6a6eff1ed 100644 --- a/code/addons/themes/src/decorators/helpers.ts +++ b/code/addons/themes/src/decorators/helpers.ts @@ -4,8 +4,10 @@ import type { StoryContext } from 'storybook/internal/types'; import dedent from 'ts-dedent'; -import type { ThemeParameters } from '../constants'; import { DEFAULT_THEME_PARAMETERS, GLOBAL_KEY, PARAM_KEY, THEMING_EVENTS } from '../constants'; +import type { ThemesParameters as Parameters } from '../types'; + +type ThemesParameters = Parameters['themes']; /** * @param StoryContext @@ -15,7 +17,7 @@ export function pluckThemeFromContext({ globals }: StoryContext): string { return globals[GLOBAL_KEY] || ''; } -export function useThemeParameters(context?: StoryContext): ThemeParameters { +export function useThemeParameters(context?: StoryContext): ThemesParameters { deprecate( dedent`The useThemeParameters function is deprecated. Please access parameters via the context directly instead e.g. - const { themeOverride } = context.parameters.themes ?? {}; @@ -23,7 +25,7 @@ export function useThemeParameters(context?: StoryContext): ThemeParameters { ); if (!context) { - return useParameter(PARAM_KEY, DEFAULT_THEME_PARAMETERS) as ThemeParameters; + return useParameter(PARAM_KEY, DEFAULT_THEME_PARAMETERS) as ThemesParameters; } return context.parameters[PARAM_KEY] ?? DEFAULT_THEME_PARAMETERS; diff --git a/code/addons/themes/src/index.ts b/code/addons/themes/src/index.ts index 41cc415df798..44aac9406bae 100644 --- a/code/addons/themes/src/index.ts +++ b/code/addons/themes/src/index.ts @@ -2,6 +2,8 @@ import { definePreview } from 'storybook/internal/preview-api'; import * as addonAnnotations from './preview'; +export type { ThemesGlobals, ThemesParameters } from './types'; + export default () => definePreview(addonAnnotations); export * from './decorators'; diff --git a/code/addons/themes/src/theme-switcher.tsx b/code/addons/themes/src/theme-switcher.tsx index d8e24f4afc08..9ca44ba016be 100644 --- a/code/addons/themes/src/theme-switcher.tsx +++ b/code/addons/themes/src/theme-switcher.tsx @@ -13,7 +13,6 @@ import { styled } from 'storybook/internal/theming'; import { PaintBrushIcon } from '@storybook/icons'; -import type { ThemeAddonState, ThemeParameters } from './constants'; import { DEFAULT_ADDON_STATE, DEFAULT_THEME_PARAMETERS, @@ -22,6 +21,9 @@ import { THEME_SWITCHER_ID, THEMING_EVENTS, } from './constants'; +import type { ThemesParameters as Parameters, ThemeAddonState } from './types'; + +type ThemesParameters = Parameters['themes']; const IconButtonLabel = styled.div(({ theme }) => ({ fontSize: theme.typography.size.s2 - 1, @@ -31,10 +33,10 @@ const hasMultipleThemes = (themesList: ThemeAddonState['themesList']) => themesL const hasTwoThemes = (themesList: ThemeAddonState['themesList']) => themesList.length === 2; export const ThemeSwitcher = React.memo(function ThemeSwitcher() { - const { themeOverride, disable } = useParameter( + const { themeOverride, disable } = useParameter( PARAM_KEY, DEFAULT_THEME_PARAMETERS - ) as ThemeParameters; + ) as ThemesParameters; const [{ theme: selected }, updateGlobals, storyGlobals] = useGlobals(); const channel = addons.getChannel(); diff --git a/code/addons/themes/src/types.ts b/code/addons/themes/src/types.ts new file mode 100644 index 000000000000..018c38e916e0 --- /dev/null +++ b/code/addons/themes/src/types.ts @@ -0,0 +1,23 @@ +export interface ThemeAddonState { + themesList: string[]; + themeDefault?: string; +} + +export interface ThemesParameters { + /** + * Themes configuration + * + * @see https://github.com/storybookjs/storybook/blob/next/code/addons/themes/README.md + */ + themes: { + /** Turn off this addon's behavior */ + disable?: boolean; + /** Which theme to override for the story */ + themeOverride?: string; + }; +} + +export interface ThemesGlobals { + /** Which theme to override for the story */ + theme?: string; +} diff --git a/code/addons/viewport/src/types.ts b/code/addons/viewport/src/types.ts index 33f186c2c3c1..fff8216d13b1 100644 --- a/code/addons/viewport/src/types.ts +++ b/code/addons/viewport/src/types.ts @@ -24,5 +24,63 @@ export interface Config { disable: boolean; } -export type GlobalState = { value: string | undefined; isRotated: boolean }; +export type GlobalState = { + /** + * When set, the viewport is applied and cannot be changed using the toolbar. Must match the key + * of one of the available viewports. + */ + value: string | undefined; + + /** + * When true the viewport applied will be rotated 90°, e.g. it will rotate from portrait to + * landscape orientation. + */ + isRotated: boolean; +}; export type GlobalStateUpdate = Partial; + +export interface ViewportParameters { + /** + * Viewport configuration + * + * @see https://storybook.js.org/docs/essentials/viewport#parameters + */ + viewport: { + /** + * Specifies the default orientation used when viewing a story. Only available if you haven't + * enabled the globals API. + */ + defaultOrientation?: 'landscape' | 'portrait'; + + /** + * Specifies the default viewport used when viewing a story. Must match a key in the viewports + * (or options) object. + */ + defaultViewport?: string; + + /** + * Turn off this addon's behavior. If you wish to turn off this addon for the entire Storybook, + * you should do so when registering addon-essentials + * + * @see https://storybook.js.org/docs/essentials/index#disabling-addons + */ + disabled?: boolean; + + /** + * Specify the available viewports. The width and height values must include the unit, e.g. + * '320px'. + */ + viewports?: Viewport; // TODO: use ModernViewport in 9.0 + }; +} + +export interface ViewportGlobals { + /** + * Viewport configuration + * + * @see https://storybook.js.org/docs/essentials/viewport#globals + */ + viewport: { + [key: string]: GlobalState; + }; +} From 8ceedc17dde6b299b3c6d6ac6757b6bc02806248 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 29 Jan 2025 16:55:45 +0100 Subject: [PATCH 101/144] Base definePreview --- code/.eslintrc.js | 1 + code/core/src/types/index.ts | 1 + code/core/src/types/modules/csf-factories.ts | 92 ++++++++++++++++++++ code/core/src/types/modules/story.ts | 1 + code/frameworks/nextjs/src/index.ts | 14 +++ code/renderers/react/src/index.ts | 2 +- code/renderers/react/src/preview.tsx | 91 ++++++++----------- code/renderers/react/src/public-types.ts | 2 +- 8 files changed, 146 insertions(+), 58 deletions(-) create mode 100644 code/core/src/types/modules/csf-factories.ts diff --git a/code/.eslintrc.js b/code/.eslintrc.js index 4268ec993dd6..06c7c4fb137e 100644 --- a/code/.eslintrc.js +++ b/code/.eslintrc.js @@ -15,6 +15,7 @@ module.exports = { 'error', { devDependencies: true, peerDependencies: true }, ], + 'no-underscore-dangle': 'off', 'import/no-unresolved': 'off', // covered by typescript 'eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }], 'eslint-comments/no-unused-disable': 'error', diff --git a/code/core/src/types/index.ts b/code/core/src/types/index.ts index 1941cbe86872..b6a3088a0d64 100644 --- a/code/core/src/types/index.ts +++ b/code/core/src/types/index.ts @@ -1,6 +1,7 @@ /// export * from './modules/csf'; +export * from './modules/csf-factories'; export * from './modules/addons'; export * from './modules/story'; export * from './modules/core-common'; diff --git a/code/core/src/types/modules/csf-factories.ts b/code/core/src/types/modules/csf-factories.ts new file mode 100644 index 000000000000..d997ac7fe9ff --- /dev/null +++ b/code/core/src/types/modules/csf-factories.ts @@ -0,0 +1,92 @@ +import { composeConfigs, normalizeProjectAnnotations } from '@storybook/core/preview-api'; + +import type { Args, ComponentAnnotations, Renderer, StoryAnnotations } from './csf'; +import type { + NormalizedComponentAnnotations, + NormalizedProjectAnnotations, + NormalizedStoryAnnotations, + ProjectAnnotations, +} from './story'; + +export interface Preview { + readonly _tag: 'Preview'; + input: ProjectAnnotations; + composed: NormalizedProjectAnnotations; + + meta(input: ComponentAnnotations): Meta; +} + +export function definePreview( + preview: Preview['input'] +): Preview { + return { + _tag: 'Preview', + input: preview, + get composed() { + const { addons, ...rest } = preview; + return normalizeProjectAnnotations(composeConfigs([...(addons ?? []), rest])); + }, + meta(meta: ComponentAnnotations) { + return defineMeta(meta, this); + }, + }; +} + +function isPreview(input: unknown): input is Preview { + return input != null && typeof input === 'object' && '_tag' in input && input?._tag === 'Preview'; +} + +export interface Meta { + readonly _tag: 'Meta'; + input: ComponentAnnotations; + composed: NormalizedComponentAnnotations; + preview: Preview; + + story(input: ComponentAnnotations): Story; +} + +function isMeta(input: unknown): input is Meta { + return input != null && typeof input === 'object' && '_tag' in input && input?._tag === 'Meta'; +} + +function defineMeta( + input: ComponentAnnotations, + preview: Preview +): Meta { + return { + _tag: 'Meta', + input, + preview, + get composed(): never { + throw new Error('Not implemented'); + }, + story(story: StoryAnnotations) { + return defineStory(story, this); + }, + }; +} + +export interface Story { + readonly _tag: 'Story'; + input: StoryAnnotations; + composed: NormalizedStoryAnnotations; + meta: Meta; +} + +function defineStory( + input: ComponentAnnotations, + meta: Meta +): Story { + return { + _tag: 'Story', + input, + meta, + get composed(): never { + throw new Error('Not implemented'); + }, + }; +} + +function isStory(input: unknown): input is Meta { + return input != null && typeof input === 'object' && '_tag' in input && input?._tag === 'Story'; +} diff --git a/code/core/src/types/modules/story.ts b/code/core/src/types/modules/story.ts index a823863defaf..6cb1ef2e874e 100644 --- a/code/core/src/types/modules/story.ts +++ b/code/core/src/types/modules/story.ts @@ -46,6 +46,7 @@ export type RenderToCanvas = ( export interface ProjectAnnotations extends CsfProjectAnnotations { + addons?: ProjectAnnotations[]; testingLibraryRender?: (...args: never[]) => { unmount: () => void }; renderToCanvas?: RenderToCanvas; /* @deprecated use renderToCanvas */ diff --git a/code/frameworks/nextjs/src/index.ts b/code/frameworks/nextjs/src/index.ts index a904f93ec89d..41f13da3975f 100644 --- a/code/frameworks/nextjs/src/index.ts +++ b/code/frameworks/nextjs/src/index.ts @@ -1,2 +1,16 @@ +import type { ReactPreview } from '@storybook/react'; +import { definePreview as definePreviewBase } from '@storybook/react'; + +import * as nextPreview from './preview'; + export * from './types'; export * from './portable-stories'; + +export function definePreview(preview: NextPreview['input']) { + return definePreviewBase({ + ...preview, + addons: [nextPreview, ...(preview.addons ?? [])], + }) as NextPreview; +} + +interface NextPreview extends ReactPreview {} diff --git a/code/renderers/react/src/index.ts b/code/renderers/react/src/index.ts index 66c491981bce..fa1b92e92149 100644 --- a/code/renderers/react/src/index.ts +++ b/code/renderers/react/src/index.ts @@ -5,7 +5,7 @@ export * from './public-types'; export * from './portable-stories'; -export { definePreview } from './preview'; +export * from './preview'; // optimization: stop HMR propagation in webpack diff --git a/code/renderers/react/src/preview.tsx b/code/renderers/react/src/preview.tsx index 40fdb3307316..a6b7fb61b44d 100644 --- a/code/renderers/react/src/preview.tsx +++ b/code/renderers/react/src/preview.tsx @@ -1,74 +1,53 @@ -import type { ComponentProps, ComponentType } from 'react'; +import type { ComponentType } from 'react'; -import { composeConfigs } from 'storybook/internal/preview-api'; -import { normalizeProjectAnnotations } from 'storybook/internal/preview-api'; import type { Args, ComponentAnnotations, - NormalizedProjectAnnotations, - ProjectAnnotations, - Renderer, + Meta, + Preview, + Story, StoryAnnotations, } from 'storybook/internal/types'; +import { definePreview as definePreviewBase } from 'storybook/internal/types'; -import type { SetOptional } from 'type-fest'; +import type { ArgsStoryFn } from '@storybook/csf'; + +import type { AddMocks } from 'src/public-types'; +import type { Exact, SetOptional } from 'type-fest'; import * as reactAnnotations from './entry-preview'; import * as reactDocsAnnotations from './entry-preview-docs'; import type { ReactRenderer } from './types'; -export function definePreview(config: PreviewConfigData) { - return new PreviewConfig({ - ...config, - addons: [reactAnnotations, reactDocsAnnotations, ...(config.addons ?? [])], - }); +export function definePreview(preview: ReactPreview['input']) { + return definePreviewBase({ + ...preview, + addons: [reactAnnotations, reactDocsAnnotations, ...(preview.addons ?? [])], + }) as ReactPreview; } -interface PreviewConfigData extends ProjectAnnotations { - addons?: ProjectAnnotations[]; +export interface ReactPreview extends Preview { + meta, TMetaArgs>>( + meta: { + render?: ArgsStoryFn; + component?: ComponentType; + args?: TMetaArgs; + } & ComponentAnnotations + ): ReactMeta<{ args: TArgs }, { args: TMetaArgs }>; } -class PreviewConfig { - readonly input: NormalizedProjectAnnotations; - - constructor(data: PreviewConfigData) { - const { addons, ...rest } = data; - this.input = normalizeProjectAnnotations(composeConfigs([...(addons ?? []), rest])); - } - - readonly meta = < - TComponent extends ComponentType, - TMetaArgs extends Partial>, - >( - meta: ComponentAnnotations & { component: TComponent; args: TMetaArgs } - ) => { - return new Meta, TMetaArgs>(meta, this); - }; - - readonly isCSFFactoryPreview = true; +interface ReactMeta< + Context extends { args: Args }, + MetaInput extends ComponentAnnotations, +> extends Meta { + story( + story: StoryAnnotations< + ReactRenderer, + // TODO: infer mocks from story itself as well + AddMocks, + SetOptional + > + ): ReactStory; } -class Meta { - readonly input: ComponentAnnotations; - - readonly config: PreviewConfig; - - constructor(annotations: ComponentAnnotations, config: PreviewConfig) { - this.input = annotations; - this.config = config; - } - - readonly story = ( - story: StoryAnnotations> - ) => new Story(story as any, this, this.config); -} - -class Story { - constructor( - public input: StoryAnnotations, - public meta: Meta, - public config: PreviewConfig - ) {} - - readonly isCSFFactory = true; -} +interface ReactStory extends Story {} diff --git a/code/renderers/react/src/public-types.ts b/code/renderers/react/src/public-types.ts index 8a2f7003ec94..2f2b2d1de816 100644 --- a/code/renderers/react/src/public-types.ts +++ b/code/renderers/react/src/public-types.ts @@ -66,7 +66,7 @@ export type StoryObj = [TMetaOrCmpOrArgs] extends [ : StoryAnnotations; // This performs a downcast to function types that are mocks, when a mock fn is given to meta args. -type AddMocks = Simplify<{ +export type AddMocks = Simplify<{ [T in keyof TArgs]: T extends keyof DefaultArgs ? // eslint-disable-next-line @typescript-eslint/ban-types DefaultArgs[T] extends (...args: any) => any & { mock: {} } // allow any function with a mock object From 02a4069ba3c93c99542321033e996dfbdfdc547c Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 29 Jan 2025 16:57:12 +0100 Subject: [PATCH 102/144] Add types for renderers and frameworks --- .../experimental-nextjs-vite/src/types.ts | 26 +++++++++++++++++++ code/frameworks/nextjs/src/types.ts | 25 ++++++++++++++++++ code/renderers/react/src/index.ts | 2 ++ code/renderers/react/src/types.ts | 13 ++++++++++ 4 files changed, 66 insertions(+) diff --git a/code/frameworks/experimental-nextjs-vite/src/types.ts b/code/frameworks/experimental-nextjs-vite/src/types.ts index 0221787dccb6..f9e5659d423a 100644 --- a/code/frameworks/experimental-nextjs-vite/src/types.ts +++ b/code/frameworks/experimental-nextjs-vite/src/types.ts @@ -3,6 +3,8 @@ import type { CompatibleString } from 'storybook/internal/types'; import type { BuilderOptions } from '@storybook/builder-vite'; import type { StorybookConfig as StorybookConfigReactVite } from '@storybook/react-vite'; +import type { NextRouter } from 'next/router'; + type FrameworkName = CompatibleString<'@storybook/experimental-nextjs-vite'>; type BuilderName = CompatibleString<'@storybook/builder-vite'>; @@ -32,3 +34,27 @@ type StorybookConfigFramework = { /** The interface for Storybook configuration in `main.ts` files. */ export type StorybookConfig = Omit & StorybookConfigFramework; + +export interface NextJsParameters { + /** + * Next.js framework configuration + * + * @see https://storybook.js.org/docs/get-started/frameworks/nextjs + */ + nextjs?: { + /** + * Enable App Directory features If your story imports components that use next/navigation, you + * need to set this parameter to true + */ + appDirectory?: boolean; + + /** + * Next.js navigation configuration when using `next/navigation`. Please note that it can only + * be used in components/pages in the app directory. + */ + navigation?: NextRouter; + + /** Next.js router configuration */ + router?: NextRouter; + }; +} diff --git a/code/frameworks/nextjs/src/types.ts b/code/frameworks/nextjs/src/types.ts index 42148ef8517b..986c1a542746 100644 --- a/code/frameworks/nextjs/src/types.ts +++ b/code/frameworks/nextjs/src/types.ts @@ -12,6 +12,7 @@ import type { } from '@storybook/preset-react-webpack'; import type * as NextImage from 'next/image'; +import type { NextRouter } from 'next/router'; type FrameworkName = CompatibleString<'@storybook/nextjs'>; type BuilderName = CompatibleString<'@storybook/builder-webpack5'>; @@ -48,3 +49,27 @@ export type StorybookConfig = Omit< > & StorybookConfigWebpack & StorybookConfigFramework; + +export interface NextJsParameters { + /** + * Next.js framework configuration + * + * @see https://storybook.js.org/docs/get-started/frameworks/nextjs + */ + nextjs?: { + /** + * Enable App Directory features If your story imports components that use next/navigation, you + * need to set this parameter to true + */ + appDirectory?: boolean; + + /** + * Next.js navigation configuration when using `next/navigation`. Please note that it can only + * be used in components/pages in the app directory. + */ + navigation?: NextRouter; + + /** Next.js router configuration */ + router?: NextRouter; + }; +} diff --git a/code/renderers/react/src/index.ts b/code/renderers/react/src/index.ts index 66c491981bce..6f3f803c6d1b 100644 --- a/code/renderers/react/src/index.ts +++ b/code/renderers/react/src/index.ts @@ -7,6 +7,8 @@ export * from './portable-stories'; export { definePreview } from './preview'; +export type { ReactParameters } from './types'; + // optimization: stop HMR propagation in webpack // optimization: stop HMR propagation in webpack diff --git a/code/renderers/react/src/types.ts b/code/renderers/react/src/types.ts index 7b3eeb648afe..ef8154b89f59 100644 --- a/code/renderers/react/src/types.ts +++ b/code/renderers/react/src/types.ts @@ -15,4 +15,17 @@ export interface ShowErrorArgs { description: string; } +export interface ReactParameters { + /** React renderer configuration */ + react?: { + /** Whether to enable React Server Components */ + rsc?: boolean; + /** Options passed to React root creation */ + rootOptions?: { + /** Custom error handler for caught errors */ + onCaughtError?: (error: unknown) => void; + }; + }; +} + export type StoryFnReactReturnType = JSX.Element; From 23c49db8ab83563aadfcb8e880f549016773aa06 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Thu, 30 Jan 2025 11:41:19 +0100 Subject: [PATCH 103/144] update a11y types --- code/addons/a11y/src/params.ts | 2 +- code/addons/a11y/src/types.ts | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/code/addons/a11y/src/params.ts b/code/addons/a11y/src/params.ts index e66a0813a42c..72b3dd39dde6 100644 --- a/code/addons/a11y/src/params.ts +++ b/code/addons/a11y/src/params.ts @@ -1,4 +1,4 @@ -import type { ElementContext, ImpactValue, RunOptions, Spec } from 'axe-core'; +import type { ElementContext, RunOptions, Spec } from 'axe-core'; export interface Setup { element?: ElementContext; diff --git a/code/addons/a11y/src/types.ts b/code/addons/a11y/src/types.ts index 56e33372c0a6..f13d913c1503 100644 --- a/code/addons/a11y/src/types.ts +++ b/code/addons/a11y/src/types.ts @@ -1,4 +1,4 @@ -import type { AxeResults } from 'axe-core'; +import type { AxeResults, ElementContext, RunOptions, Spec } from 'axe-core'; export type A11YReport = AxeResults | { error: Error }; @@ -10,26 +10,21 @@ export interface A11yParameters { */ a11y?: { /** Manual configuration for specific elements */ - element?: string | string[]; + element?: ElementContext; - /** Configuration for the accessibility rules */ - config?: { - /** Rules to run against the matching elements */ - rules?: Array<{ - id: string; - enabled?: boolean; - selector?: string; - }>; - /** Elements to exclude from accessibility checks */ - exclude?: string[]; - }; + /** + * Configuration for the accessibility rules + * + * @see https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter + */ + config?: Spec; /** * Options for the accessibility checks To learn more about the available options, * * @see https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter */ - options?: Record; + options?: RunOptions; /** Turn off this addon's behavior */ disable?: boolean; @@ -37,6 +32,11 @@ export interface A11yParameters { } export interface A11yGlobals { + /** + * Accessibility configuration + * + * @see https://storybook.js.org/docs/writing-tests/accessibility-testing + */ a11y: { /** * Prevent the addon to execute automatic accessibility checks upon visiting a story. You can From 2085d4b6434a214599d8e9f35de391efd6faf905 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 30 Jan 2025 12:26:48 +0100 Subject: [PATCH 104/144] Add type tests --- code/core/src/manager/globals/exports.ts | 6 +- .../react/src/csf-factories.test.tsx | 238 +++++++++++++++++- code/renderers/react/src/preview.tsx | 26 +- 3 files changed, 258 insertions(+), 12 deletions(-) diff --git a/code/core/src/manager/globals/exports.ts b/code/core/src/manager/globals/exports.ts index 4e34bb61244c..037bf2504c57 100644 --- a/code/core/src/manager/globals/exports.ts +++ b/code/core/src/manager/globals/exports.ts @@ -963,9 +963,9 @@ export default { 'UPDATE_QUERY_PARAMS', 'UPDATE_STORY_ARGS', ], - 'storybook/internal/types': ['Addon_TypesEnum'], - '@storybook/types': ['Addon_TypesEnum'], - '@storybook/core/types': ['Addon_TypesEnum'], + 'storybook/internal/types': ['Addon_TypesEnum', 'definePreview'], + '@storybook/types': ['Addon_TypesEnum', 'definePreview'], + '@storybook/core/types': ['Addon_TypesEnum', 'definePreview'], 'storybook/internal/manager-errors': [ 'Category', 'ProviderDoesNotExtendBaseProviderError', diff --git a/code/renderers/react/src/csf-factories.test.tsx b/code/renderers/react/src/csf-factories.test.tsx index 3b1928051eb4..ccaf6d405421 100644 --- a/code/renderers/react/src/csf-factories.test.tsx +++ b/code/renderers/react/src/csf-factories.test.tsx @@ -1,7 +1,26 @@ +// @vitest-environment happy-dom +// this file tests Typescript types that's why there are no assertions +import { describe, it } from 'vitest'; import { expect, test } from 'vitest'; -import { Button } from './__test__/Button'; +import type { KeyboardEventHandler, ReactElement, ReactNode } from 'react'; +import React from 'react'; + +import type { Args, StrictArgs } from 'storybook/internal/types'; + +import type { Canvas } from '@storybook/csf'; +import type { Mock } from '@storybook/test'; +import { fn } from '@storybook/test'; + +import { expectTypeOf } from 'expect-type'; + import { definePreview } from './preview'; +import type { Decorator } from './public-types'; + +type ButtonProps = { label: string; disabled: boolean }; +const Button: (props: ButtonProps) => ReactElement = () => <>; + +const preview = definePreview({}); test('csf factories', () => { const config = definePreview({ @@ -12,13 +31,224 @@ test('csf factories', () => { ], }); - const meta = config.meta({ component: Button, args: { primary: true } }); + const meta = config.meta({ component: Button, args: { disabled: true } }); const MyStory = meta.story({ args: { - children: 'Hello world', + label: 'Hello world', + }, + }); + + expect(MyStory.input.args?.label).toBe('Hello world'); +}); + +describe('Args can be provided in multiple ways', () => { + it('✅ All required args may be provided in meta', () => { + const meta = preview.meta({ + component: Button, + args: { label: 'good', disabled: false }, + }); + + const Basic = meta.story({}); + }); + + it('✅ Required args may be provided partial in meta and the story', () => { + const meta = preview.meta({ + component: Button, + args: { label: 'good' }, + }); + const Basic = meta.story({ + args: { disabled: false }, + }); + }); + + it('❌ The combined shape of meta args and story args must match the required args.', () => { + { + const meta = preview.meta({ component: Button }); + const Basic = meta.story({ + // @ts-expect-error disabled not provided ❌ + args: { label: 'good' }, + }); + } + { + const meta = preview.meta({ + component: Button, + args: { label: 'good' }, + }); + // @ts-expect-error disabled not provided ❌ + const Basic = meta.story({}); + } + { + const meta = preview.meta({ component: Button }); + const Basic = meta.story({ + // @ts-expect-error disabled not provided ❌ + args: { label: 'good' }, + }); + } + }); +}); + +it('✅ Void functions are not changed', () => { + interface CmpProps { + label: string; + disabled: boolean; + onClick(): void; + onKeyDown: KeyboardEventHandler; + onLoading: (s: string) => ReactElement; + submitAction(): void; + } + + const Cmp: (props: CmpProps) => ReactElement = () => <>; + + const meta = preview.meta({ + component: Cmp, + args: { label: 'good' }, + }); + + const Basic = meta.story({ + args: { + disabled: false, + onLoading: () =>
Loading...
, + onKeyDown: fn(), + onClick: fn(), + submitAction: fn(), }, }); +}); + +type ThemeData = 'light' | 'dark'; +declare const Theme: (props: { theme: ThemeData; children?: ReactNode }) => ReactElement; + +describe('Story args can be inferred', () => { + it('Correct args are inferred when type is widened for render function', () => { + const meta = preview.meta({ + component: Button, + args: { disabled: false }, + render: (args: ButtonProps & { theme: ThemeData }, { component }) => { + // component is not null as it is provided in meta - expect(MyStory.input.args?.children).toBe('Hello world'); + const Component = component!; + return ( + + + + ); + }, + }); + + const Basic = meta.story({ args: { theme: 'light', label: 'good' } }); + }); + + const withDecorator: Decorator<{ decoratorArg: number }> = (Story, { args }) => ( + <> + Decorator: {args.decoratorArg} + + + ); + + it('Correct args are inferred when type is widened for decorators', () => { + const meta = preview.meta({ + component: Button, + args: { disabled: false }, + decorators: [withDecorator], + }); + + const Basic = meta.story({ args: { decoratorArg: 0, label: 'good' } }); + }); + + it('Correct args are inferred when type is widened for multiple decorators', () => { + type Props = ButtonProps & { decoratorArg: number; decoratorArg2: string }; + + const secondDecorator: Decorator<{ decoratorArg2: string }> = (Story, { args }) => ( + <> + Decorator: {args.decoratorArg2} + + + ); + + // decorator is not using args + const thirdDecorator: Decorator = (Story) => ( + <> + + + ); + + // decorator is not using args + const fourthDecorator: Decorator = (Story) => ( + <> + + + ); + + const meta = preview.meta({ + component: Button, + args: { disabled: false }, + decorators: [withDecorator, secondDecorator, thirdDecorator, fourthDecorator], + }); + + const Basic = meta.story({ + args: { decoratorArg: 0, decoratorArg2: '', label: 'good' }, + }); + }); +}); + +it('Components without Props can be used, issue #21768', () => { + const Component = () => <>Foo; + const withDecorator: Decorator = (Story) => ( + <> + + + ); + + const meta = preview.meta({ + component: Component, + decorators: [withDecorator], + }); + + const Basic = meta.story({}); +}); + +it('Meta is broken when using discriminating types, issue #23629', () => { + type TestButtonProps = { + text: string; + } & ( + | { + id?: string; + onClick?: (e: unknown, id: string | undefined) => void; + } + | { + id: string; + onClick: (e: unknown, id: string) => void; + } + ); + const TestButton: React.FC = ({ text }) => { + return

{text}

; + }; + + preview.meta({ + title: 'Components/Button', + component: TestButton, + args: { + text: 'Button', + }, + }); +}); + +it('Infer mock function given to args in meta.', () => { + type Props = { label: string; onClick: () => void; onRender: () => JSX.Element }; + const TestButton = (props: Props) => <>; + + const meta = preview.meta({ + component: TestButton, + args: { label: 'label', onClick: fn(), onRender: () => <>some jsx }, + }); + + const Basic = meta.story({ + play: async ({ args, mount }) => { + const canvas = await mount(); + expectTypeOf(canvas).toEqualTypeOf(); + expectTypeOf(args.onClick).toEqualTypeOf(); + expectTypeOf(args.onRender).toEqualTypeOf<() => JSX.Element>(); + }, + }); }); diff --git a/code/renderers/react/src/preview.tsx b/code/renderers/react/src/preview.tsx index a6b7fb61b44d..ba53e054ff5b 100644 --- a/code/renderers/react/src/preview.tsx +++ b/code/renderers/react/src/preview.tsx @@ -10,10 +10,10 @@ import type { } from 'storybook/internal/types'; import { definePreview as definePreviewBase } from 'storybook/internal/types'; -import type { ArgsStoryFn } from '@storybook/csf'; +import type { ArgsStoryFn, DecoratorFunction, LoaderFunction, Renderer } from '@storybook/csf'; import type { AddMocks } from 'src/public-types'; -import type { Exact, SetOptional } from 'type-fest'; +import type { RemoveIndexSignature, SetOptional, Simplify, UnionToIntersection } from 'type-fest'; import * as reactAnnotations from './entry-preview'; import * as reactDocsAnnotations from './entry-preview-docs'; @@ -27,15 +27,31 @@ export function definePreview(preview: ReactPreview['input']) { } export interface ReactPreview extends Preview { - meta, TMetaArgs>>( + meta< + TArgs extends Args, + Decorators extends DecoratorFunction, + // Try to make Exact, TMetaArgs> work + TMetaArgs extends Partial, + >( meta: { render?: ArgsStoryFn; component?: ComponentType; + decorators?: Decorators | Decorators[]; args?: TMetaArgs; - } & ComponentAnnotations - ): ReactMeta<{ args: TArgs }, { args: TMetaArgs }>; + } & Omit, 'decorators'> + ): ReactMeta< + { + args: Simplify< + TArgs & Simplify>> + >; + }, + { args: Partial extends TMetaArgs ? {} : TMetaArgs } + >; } +type DecoratorsArgs = UnionToIntersection< + Decorators extends DecoratorFunction ? TArgs : unknown +>; interface ReactMeta< Context extends { args: Args }, MetaInput extends ComponentAnnotations, From b93d96a575b08cafc20979840eca598308e3f0b1 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 30 Jan 2025 12:53:04 +0100 Subject: [PATCH 105/144] Make storybook/internal/csf --- code/core/package.json | 8 ++++++++ code/core/scripts/entries.ts | 1 + .../{types/modules/csf-factories.ts => csf/index.ts} | 11 +++++++---- code/core/src/manager/globals/exports.ts | 6 +++--- code/core/src/types/index.ts | 1 - code/lib/cli/core/csf/index.cjs | 1 + code/lib/cli/core/csf/index.d.ts | 2 ++ code/lib/cli/core/csf/index.js | 1 + code/lib/cli/package.json | 8 ++++++++ code/renderers/react/src/preview.tsx | 11 +++++------ 10 files changed, 36 insertions(+), 14 deletions(-) rename code/core/src/{types/modules/csf-factories.ts => csf/index.ts} (96%) create mode 100644 code/lib/cli/core/csf/index.cjs create mode 100644 code/lib/cli/core/csf/index.d.ts create mode 100644 code/lib/cli/core/csf/index.js diff --git a/code/core/package.json b/code/core/package.json index 76008e354161..e6139d3f7667 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -97,6 +97,11 @@ "import": "./dist/csf-tools/index.js", "require": "./dist/csf-tools/index.cjs" }, + "./csf": { + "types": "./dist/csf/index.d.ts", + "import": "./dist/csf/index.js", + "require": "./dist/csf/index.cjs" + }, "./common": { "types": "./dist/common/index.d.ts", "import": "./dist/common/index.js", @@ -219,6 +224,9 @@ "csf-tools": [ "./dist/csf-tools/index.d.ts" ], + "csf": [ + "./dist/csf/index.d.ts" + ], "common": [ "./dist/common/index.d.ts" ], diff --git a/code/core/scripts/entries.ts b/code/core/scripts/entries.ts index a8588f13fd86..2b90e2c4b8a4 100644 --- a/code/core/scripts/entries.ts +++ b/code/core/scripts/entries.ts @@ -25,6 +25,7 @@ export const getEntries = (cwd: string) => { define('src/channels/index.ts', ['browser', 'node'], true), define('src/types/index.ts', ['browser', 'node'], true, ['react']), define('src/csf-tools/index.ts', ['node'], true), + define('src/csf/index.ts', ['browser', 'node'], true), define('src/common/index.ts', ['node'], true), define('src/builder-manager/index.ts', ['node'], true), define('src/telemetry/index.ts', ['node'], true), diff --git a/code/core/src/types/modules/csf-factories.ts b/code/core/src/csf/index.ts similarity index 96% rename from code/core/src/types/modules/csf-factories.ts rename to code/core/src/csf/index.ts index d997ac7fe9ff..20d0643b1b39 100644 --- a/code/core/src/types/modules/csf-factories.ts +++ b/code/core/src/csf/index.ts @@ -1,12 +1,15 @@ -import { composeConfigs, normalizeProjectAnnotations } from '@storybook/core/preview-api'; - -import type { Args, ComponentAnnotations, Renderer, StoryAnnotations } from './csf'; import type { + Args, + ComponentAnnotations, NormalizedComponentAnnotations, NormalizedProjectAnnotations, NormalizedStoryAnnotations, ProjectAnnotations, -} from './story'; + Renderer, + StoryAnnotations, +} from '@storybook/core/types'; + +import { composeConfigs, normalizeProjectAnnotations } from '@storybook/core/preview-api'; export interface Preview { readonly _tag: 'Preview'; diff --git a/code/core/src/manager/globals/exports.ts b/code/core/src/manager/globals/exports.ts index 037bf2504c57..4e34bb61244c 100644 --- a/code/core/src/manager/globals/exports.ts +++ b/code/core/src/manager/globals/exports.ts @@ -963,9 +963,9 @@ export default { 'UPDATE_QUERY_PARAMS', 'UPDATE_STORY_ARGS', ], - 'storybook/internal/types': ['Addon_TypesEnum', 'definePreview'], - '@storybook/types': ['Addon_TypesEnum', 'definePreview'], - '@storybook/core/types': ['Addon_TypesEnum', 'definePreview'], + 'storybook/internal/types': ['Addon_TypesEnum'], + '@storybook/types': ['Addon_TypesEnum'], + '@storybook/core/types': ['Addon_TypesEnum'], 'storybook/internal/manager-errors': [ 'Category', 'ProviderDoesNotExtendBaseProviderError', diff --git a/code/core/src/types/index.ts b/code/core/src/types/index.ts index b6a3088a0d64..1941cbe86872 100644 --- a/code/core/src/types/index.ts +++ b/code/core/src/types/index.ts @@ -1,7 +1,6 @@ /// export * from './modules/csf'; -export * from './modules/csf-factories'; export * from './modules/addons'; export * from './modules/story'; export * from './modules/core-common'; diff --git a/code/lib/cli/core/csf/index.cjs b/code/lib/cli/core/csf/index.cjs new file mode 100644 index 000000000000..19b144387019 --- /dev/null +++ b/code/lib/cli/core/csf/index.cjs @@ -0,0 +1 @@ +module.exports = require('@storybook/core/csf'); diff --git a/code/lib/cli/core/csf/index.d.ts b/code/lib/cli/core/csf/index.d.ts new file mode 100644 index 000000000000..2cc38939bb85 --- /dev/null +++ b/code/lib/cli/core/csf/index.d.ts @@ -0,0 +1,2 @@ +export * from '@storybook/core/csf'; +export type * from '@storybook/core/csf'; diff --git a/code/lib/cli/core/csf/index.js b/code/lib/cli/core/csf/index.js new file mode 100644 index 000000000000..ed1380489e93 --- /dev/null +++ b/code/lib/cli/core/csf/index.js @@ -0,0 +1 @@ +export * from '@storybook/core/csf'; diff --git a/code/lib/cli/package.json b/code/lib/cli/package.json index 53cf6294030e..09e35b685f74 100644 --- a/code/lib/cli/package.json +++ b/code/lib/cli/package.json @@ -103,6 +103,11 @@ "import": "./core/types/index.js", "require": "./core/types/index.cjs" }, + "./internal/csf": { + "types": "./core/csf/index.d.ts", + "import": "./core/csf/index.js", + "require": "./core/csf/index.cjs" + }, "./internal/csf-tools": { "types": "./core/csf-tools/index.d.ts", "import": "./core/csf-tools/index.js", @@ -233,6 +238,9 @@ "internal/core-server": [ "./core/core-server/index.d.ts" ], + "internal/csf": [ + "./core/csf/index.d.ts" + ], "internal/csf-tools": [ "./core/csf-tools/index.d.ts" ], diff --git a/code/renderers/react/src/preview.tsx b/code/renderers/react/src/preview.tsx index ba53e054ff5b..d2c435bf121c 100644 --- a/code/renderers/react/src/preview.tsx +++ b/code/renderers/react/src/preview.tsx @@ -1,16 +1,15 @@ import type { ComponentType } from 'react'; +import { definePreview as definePreviewBase } from 'storybook/internal/csf'; +import type { Meta, Preview, Story } from 'storybook/internal/csf'; import type { Args, + ArgsStoryFn, ComponentAnnotations, - Meta, - Preview, - Story, + DecoratorFunction, + Renderer, StoryAnnotations, } from 'storybook/internal/types'; -import { definePreview as definePreviewBase } from 'storybook/internal/types'; - -import type { ArgsStoryFn, DecoratorFunction, LoaderFunction, Renderer } from '@storybook/csf'; import type { AddMocks } from 'src/public-types'; import type { RemoveIndexSignature, SetOptional, Simplify, UnionToIntersection } from 'type-fest'; From fccc0df808bb449ab8f894d614b07f9fa201ceb2 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 30 Jan 2025 14:42:49 +0100 Subject: [PATCH 106/144] Adjust runtime --- .../src/codegen-modern-iframe-script.ts | 7 ++-- .../templates/virtualModuleModernEntry.js | 11 +++--- code/core/src/csf/index.ts | 8 ++--- .../preview-web/docs-context/DocsContext.ts | 6 ++-- .../modules/store/csf/csf-factory-utils.ts | 35 +++++-------------- 5 files changed, 23 insertions(+), 44 deletions(-) diff --git a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts index 69b05dc5e744..31e1e93f3468 100644 --- a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts +++ b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts @@ -24,10 +24,9 @@ export async function generateModernIframeScriptCode(options: Options, projectRo const getPreviewAnnotationsFunction = ` const getProjectAnnotations = async (hmrPreviewAnnotationModules = []) => { const preview = await import('${previewFileUrl}'); - const csfFactoryPreview = getCsfFactoryPreview(preview); - - if (csfFactoryPreview) { - return csfFactoryPreview.input; + + if (isPreview(preview.default)) { + return preview.default.composed; } const configs = await Promise.all([${previewAnnotationURLs diff --git a/code/builders/builder-webpack5/templates/virtualModuleModernEntry.js b/code/builders/builder-webpack5/templates/virtualModuleModernEntry.js index 423bee0c36d0..0ed2fe15fa85 100644 --- a/code/builders/builder-webpack5/templates/virtualModuleModernEntry.js +++ b/code/builders/builder-webpack5/templates/virtualModuleModernEntry.js @@ -1,4 +1,5 @@ import { createBrowserChannel } from 'storybook/internal/channels'; +import { isPreview } from 'storybook/internal/csf'; import { PreviewWeb, addons, composeConfigs } from 'storybook/internal/preview-api'; import { global } from '@storybook/global'; @@ -8,14 +9,10 @@ import { importFn } from '{{storiesFilename}}'; const getProjectAnnotations = () => { const previewAnnotations = ['{{previewAnnotations_requires}}']; // the last one in this array is the user preview - const preview = previewAnnotations[previewAnnotations.length - 1]; + const userPreview = previewAnnotations[previewAnnotations.length - 1]?.default; - const csfFactoryPreview = Object.values(preview).find((module) => { - return 'isCSFFactoryPreview' in module; - }); - - if (csfFactoryPreview) { - return csfFactoryPreview.annotations; + if (isPreview(userPreview)) { + return userPreview.composed; } return composeConfigs(previewAnnotations); diff --git a/code/core/src/csf/index.ts b/code/core/src/csf/index.ts index 20d0643b1b39..f913c8b205ae 100644 --- a/code/core/src/csf/index.ts +++ b/code/core/src/csf/index.ts @@ -11,7 +11,7 @@ import type { import { composeConfigs, normalizeProjectAnnotations } from '@storybook/core/preview-api'; -export interface Preview { +export interface Preview { readonly _tag: 'Preview'; input: ProjectAnnotations; composed: NormalizedProjectAnnotations; @@ -35,7 +35,7 @@ export function definePreview( }; } -function isPreview(input: unknown): input is Preview { +export function isPreview(input: unknown): input is Preview { return input != null && typeof input === 'object' && '_tag' in input && input?._tag === 'Preview'; } @@ -48,7 +48,7 @@ export interface Meta { story(input: ComponentAnnotations): Story; } -function isMeta(input: unknown): input is Meta { +export function isMeta(input: unknown): input is Meta { return input != null && typeof input === 'object' && '_tag' in input && input?._tag === 'Meta'; } @@ -90,6 +90,6 @@ function defineStory( }; } -function isStory(input: unknown): input is Meta { +export function isStory(input: unknown): input is Story { return input != null && typeof input === 'object' && '_tag' in input && input?._tag === 'Story'; } diff --git a/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts b/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts index c84bf2e16e12..d70abc89646a 100644 --- a/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts +++ b/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts @@ -11,9 +11,10 @@ import type { StoryName, } from '@storybook/core/types'; +import { isStory } from 'core/src/csf'; import { dedent } from 'ts-dedent'; -import { type StoryStore, isCsfFactory } from '../../store'; +import { type StoryStore } from '../../store'; import type { DocsContextProps } from './DocsContextProps'; export class DocsContext implements DocsContextProps { @@ -163,8 +164,7 @@ export class DocsContext implements DocsContextProps } const story = this.exportToStory.get( - // TODO: @kasperpeulen will fix this once csf factory types are defined - isCsfFactory(moduleExportOrType) ? (moduleExportOrType as any).input : moduleExportOrType + isStory(moduleExportOrType) ? moduleExportOrType.input : moduleExportOrType ); if (story) { diff --git a/code/core/src/preview-api/modules/store/csf/csf-factory-utils.ts b/code/core/src/preview-api/modules/store/csf/csf-factory-utils.ts index def96170fca1..741a1842d4a5 100644 --- a/code/core/src/preview-api/modules/store/csf/csf-factory-utils.ts +++ b/code/core/src/preview-api/modules/store/csf/csf-factory-utils.ts @@ -1,27 +1,11 @@ -/* eslint-disable @typescript-eslint/naming-convention */ - -/* eslint-disable no-underscore-dangle */ +import { isStory } from '@storybook/core/csf'; import type { Args, ComponentAnnotations, LegacyStoryAnnotationsOrFn, - ModuleExports, ProjectAnnotations, Renderer, - StoryAnnotations, -} from '@storybook/types'; - -export function getCsfFactoryPreview(preview: ModuleExports): ProjectAnnotations | null { - return Object.values(preview).find(isCsfFactory) ?? null; -} - -export function isCsfFactory(target: StoryAnnotations | ProjectAnnotations) { - return ( - target != null && - typeof target === 'object' && - ('isCSFFactory' in target || 'isCSFFactoryPreview' in target) - ); -} +} from '@storybook/core/types'; export function getCsfFactoryAnnotations< TRenderer extends Renderer = Renderer, @@ -31,12 +15,11 @@ export function getCsfFactoryAnnotations< meta?: ComponentAnnotations, projectAnnotations?: ProjectAnnotations ) { - const _isCsfFactory = isCsfFactory(story); - - return { - // TODO: @kasperpeulen will fix this once csf factory types are defined - story: _isCsfFactory ? (story as any)?.input : story, - meta: _isCsfFactory ? (story as any)?.meta?.input : meta, - preview: _isCsfFactory ? (story as any)?.config?.input : projectAnnotations, - }; + return isStory(story) + ? { + story: story.input, + meta: story.meta.input, + preview: story.meta.preview.composed, + } + : { story, meta, preview: projectAnnotations }; } From bd5f88c536dbb7b8e591a4404270c6c39ff6d178 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 30 Jan 2025 15:25:18 +0100 Subject: [PATCH 107/144] Adjust runtime --- .../builder-vite/src/codegen-modern-iframe-script.ts | 1 + code/core/src/csf/index.ts | 2 +- .../src/preview-api/modules/store/csf/processCSFFile.ts | 7 ++++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts index 31e1e93f3468..a953acdaee91 100644 --- a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts +++ b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts @@ -79,6 +79,7 @@ export async function generateModernIframeScriptCode(options: Options, projectRo setup(); import { composeConfigs, PreviewWeb, ClientApi, getCsfFactoryPreview } from 'storybook/internal/preview-api'; + import { isPreview } from 'storybook/internal/csf'; import { importFn } from '${SB_VIRTUAL_FILES.VIRTUAL_STORIES_FILE}'; diff --git a/code/core/src/csf/index.ts b/code/core/src/csf/index.ts index f913c8b205ae..91b503856c56 100644 --- a/code/core/src/csf/index.ts +++ b/code/core/src/csf/index.ts @@ -90,6 +90,6 @@ function defineStory( }; } -export function isStory(input: unknown): input is Story { +export function isStory(input: unknown): input is Story { return input != null && typeof input === 'object' && '_tag' in input && input?._tag === 'Story'; } diff --git a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts index 9242d7cf8d18..b29d7d67d9e5 100644 --- a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts +++ b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts @@ -1,3 +1,4 @@ +import { isStory } from '@storybook/core/csf'; import type { ComponentTitle, Parameters, Path, Renderer } from '@storybook/core/types'; import type { CSFFile, ModuleExports, NormalizedComponentAnnotations } from '@storybook/core/types'; import { isExportStory } from '@storybook/csf'; @@ -46,10 +47,10 @@ export function processCSFFile( // eslint-disable-next-line @typescript-eslint/naming-convention const { default: defaultExport, __namedExportsOrder, ...namedExports } = moduleExports; - const firstStory: any = Object.values(namedExports)[0]; + const firstStory = Object.values(namedExports)[0]; // CSF4 // TODO: @kasperpeulen will fix this once csf factory types are defined - if (!defaultExport && 'isCSFFactory' in firstStory) { + if (isStory(firstStory)) { const meta: NormalizedComponentAnnotations = normalizeComponentAnnotations(firstStory.meta.input, title, importPath); checkDisallowedParameters(meta.parameters); @@ -65,7 +66,7 @@ export function processCSFFile( } }); - csfFile.projectAnnotations = firstStory.config.input; + csfFile.projectAnnotations = firstStory.meta.preview.composed; return csfFile; } From c5cb4b1b4823aaa469ad0a63d6b370f8c1752193 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 30 Jan 2025 15:26:15 +0100 Subject: [PATCH 108/144] Fix TODO --- code/core/src/preview-api/modules/store/csf/processCSFFile.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts index b29d7d67d9e5..9ebb75d6e7d5 100644 --- a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts +++ b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts @@ -48,8 +48,6 @@ export function processCSFFile( const { default: defaultExport, __namedExportsOrder, ...namedExports } = moduleExports; const firstStory = Object.values(namedExports)[0]; - // CSF4 - // TODO: @kasperpeulen will fix this once csf factory types are defined if (isStory(firstStory)) { const meta: NormalizedComponentAnnotations = normalizeComponentAnnotations(firstStory.meta.input, title, importPath); From 209cdeb83c19118e4f3df0db4e50b4fcf4647730 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 30 Jan 2025 15:31:13 +0100 Subject: [PATCH 109/144] Use default export for preview --- code/.storybook/preview.tsx | 3 +-- code/.storybook/storybook.setup.ts | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index f26260d48339..739c2ea7ba43 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -130,7 +130,6 @@ const ThemedSetRoot = () => { return null; }; -// eslint-disable-next-line no-underscore-dangle const preview = (window as any).__STORYBOOK_PREVIEW__ as PreviewWeb | undefined; const channel = (window as any).__STORYBOOK_ADDONS_CHANNEL__ as Channel | undefined; const loaders = [ @@ -373,7 +372,7 @@ const parameters = { }, }; -export const config = definePreview({ +export default definePreview({ addons: [ addonThemes(), addonEssentials(), diff --git a/code/.storybook/storybook.setup.ts b/code/.storybook/storybook.setup.ts index 3521dcafb8cc..2fe58c6dbe96 100644 --- a/code/.storybook/storybook.setup.ts +++ b/code/.storybook/storybook.setup.ts @@ -3,17 +3,16 @@ import { beforeAll, vi, expect as vitestExpect } from 'vitest'; import { setProjectAnnotations } from '@storybook/react'; import { userEvent as storybookEvent, expect as storybookExpect } from '@storybook/test'; -import { config } from './preview'; +import preview from './preview'; vi.spyOn(console, 'warn').mockImplementation((...args) => console.log(...args)); const annotations = setProjectAnnotations([ - config.input, + preview.composed, { // experiment with injecting Vitest's interactivity API over our userEvent while tests run in browser mode // https://vitest.dev/guide/browser/interactivity-api.html loaders: async (context) => { - // eslint-disable-next-line no-underscore-dangle if (globalThis.__vitest_browser__) { const vitest = await import('@vitest/browser/context'); const { userEvent: browserEvent } = vitest; From d5b592a5c7d18800951cc1144259040070f53646 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 30 Jan 2025 15:59:20 +0100 Subject: [PATCH 110/144] Fix type inference issue --- .../components/Button/Button.stories.tsx | 4 ++-- .../mocks/csf4-variances.stories.tsx | 4 ++-- code/renderers/react/src/preview.tsx | 18 +++++++++++------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/code/core/src/components/components/Button/Button.stories.tsx b/code/core/src/components/components/Button/Button.stories.tsx index c2724412aa18..78e061d5d1a7 100644 --- a/code/core/src/components/components/Button/Button.stories.tsx +++ b/code/core/src/components/components/Button/Button.stories.tsx @@ -3,10 +3,10 @@ import React from 'react'; import { FaceHappyIcon } from '@storybook/icons'; -import { config } from '../../../../../.storybook/preview'; +import preview from '../../../../../.storybook/preview'; import { Button } from './Button'; -const meta = config.meta({ +const meta = preview.meta({ id: 'button-component', title: 'Button', component: Button, diff --git a/code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx b/code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx index fd9003947612..51f78d90ed92 100644 --- a/code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx +++ b/code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx @@ -1,7 +1,7 @@ // @ts-expect-error this is just a mock file -import { config } from '#.storybook/preview'; +import preview from '#.storybook/preview'; -const meta = config.meta({ +const meta = preview.meta({ title: 'MyComponent', args: { initial: 'foo', diff --git a/code/renderers/react/src/preview.tsx b/code/renderers/react/src/preview.tsx index d2c435bf121c..498521c241f0 100644 --- a/code/renderers/react/src/preview.tsx +++ b/code/renderers/react/src/preview.tsx @@ -55,13 +55,17 @@ interface ReactMeta< Context extends { args: Args }, MetaInput extends ComponentAnnotations, > extends Meta { - story( - story: StoryAnnotations< - ReactRenderer, - // TODO: infer mocks from story itself as well - AddMocks, - SetOptional - > + story< + const TInput extends Simplify< + StoryAnnotations< + ReactRenderer, + // TODO: infer mocks from story itself as well + AddMocks, + SetOptional + > + >, + >( + story: TInput ): ReactStory; } From 1f5a406ee6f9618a9fba4977fe33ded19959c8e1 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 30 Jan 2025 16:06:52 +0100 Subject: [PATCH 111/144] Fix --- .../builder-vite/src/codegen-modern-iframe-script.ts | 2 +- code/core/src/preview-api/index.ts | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts index a953acdaee91..7b8f82a3f330 100644 --- a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts +++ b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts @@ -78,7 +78,7 @@ export async function generateModernIframeScriptCode(options: Options, projectRo setup(); - import { composeConfigs, PreviewWeb, ClientApi, getCsfFactoryPreview } from 'storybook/internal/preview-api'; + import { composeConfigs, PreviewWeb, ClientApi } from 'storybook/internal/preview-api'; import { isPreview } from 'storybook/internal/csf'; import { importFn } from '${SB_VIRTUAL_FILES.VIRTUAL_STORIES_FILE}'; diff --git a/code/core/src/preview-api/index.ts b/code/core/src/preview-api/index.ts index 907897730377..12ee8d71fba3 100644 --- a/code/core/src/preview-api/index.ts +++ b/code/core/src/preview-api/index.ts @@ -60,12 +60,7 @@ export { } from './store'; /** CSF API */ -export { - createPlaywrightTest, - getCsfFactoryPreview, - getCsfFactoryAnnotations, - isCsfFactory, -} from './modules/store/csf'; +export { createPlaywrightTest, getCsfFactoryAnnotations } from './modules/store/csf'; export type { PropDescriptor } from './store'; From 1f4c92fdf457b58ae6e5ea8886e3fcc3bf3b819d Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 31 Jan 2025 09:05:44 +0100 Subject: [PATCH 112/144] Fix import --- .../preview-api/modules/preview-web/docs-context/DocsContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts b/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts index d70abc89646a..d8d83bed4deb 100644 --- a/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts +++ b/code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts @@ -1,4 +1,5 @@ import type { Channel } from '@storybook/core/channels'; +import { isStory } from '@storybook/core/csf'; import type { CSFFile, ModuleExport, @@ -11,7 +12,6 @@ import type { StoryName, } from '@storybook/core/types'; -import { isStory } from 'core/src/csf'; import { dedent } from 'ts-dedent'; import { type StoryStore } from '../../store'; From 1a356cbb820bb61120f7792daaa1c97d97556ebf Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 31 Jan 2025 09:32:06 +0100 Subject: [PATCH 113/144] Fix build --- code/addons/test/src/vitest-plugin/test-utils.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/code/addons/test/src/vitest-plugin/test-utils.ts b/code/addons/test/src/vitest-plugin/test-utils.ts index 765ab38d5a1b..92b09a49623a 100644 --- a/code/addons/test/src/vitest-plugin/test-utils.ts +++ b/code/addons/test/src/vitest-plugin/test-utils.ts @@ -1,6 +1,4 @@ /* eslint-disable @typescript-eslint/naming-convention */ - -/* eslint-disable no-underscore-dangle */ import { type RunnerTask, type TaskMeta, type TestContext } from 'vitest'; import { @@ -33,7 +31,7 @@ export const testStory = ( const annotations = getCsfFactoryAnnotations(story, meta); const composedStory = composeStory( annotations.story, - annotations.meta, + annotations.meta!, { initialGlobals: (await getInitialGlobals?.()) ?? {}, tags: await getTags?.() }, annotations.preview, exportName From 03ec5932446094b24c8dccc7c01dceb41719ad5c Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 31 Jan 2025 09:43:11 +0100 Subject: [PATCH 114/144] Fix test --- code/renderers/react/src/__test__/Button.csf4.stories.tsx | 2 +- .../__snapshots__/portable-stories-factory.test.tsx.snap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/renderers/react/src/__test__/Button.csf4.stories.tsx b/code/renderers/react/src/__test__/Button.csf4.stories.tsx index 067e481a3d6a..f84f6233a559 100644 --- a/code/renderers/react/src/__test__/Button.csf4.stories.tsx +++ b/code/renderers/react/src/__test__/Button.csf4.stories.tsx @@ -88,7 +88,7 @@ export const CSF3Button = meta.story({ }); export const CSF3ButtonWithRender = meta.story({ - ...CSF3Button, + ...CSF3Button.input, render: (args) => (

I am a custom render function

diff --git a/code/renderers/react/src/__test__/__snapshots__/portable-stories-factory.test.tsx.snap b/code/renderers/react/src/__test__/__snapshots__/portable-stories-factory.test.tsx.snap index 4e8d3a043beb..3f00ff746281 100644 --- a/code/renderers/react/src/__test__/__snapshots__/portable-stories-factory.test.tsx.snap +++ b/code/renderers/react/src/__test__/__snapshots__/portable-stories-factory.test.tsx.snap @@ -52,7 +52,7 @@ exports[`Renders CSF3ButtonWithRender story 1`] = ` class="storybook-button storybook-button--medium storybook-button--secondary" type="button" > - Children coming from meta args + foo
From cecfd850f3f073aed1d8b06b2cb1c1e240827839 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 31 Jan 2025 10:18:25 +0100 Subject: [PATCH 115/144] Fix eslint --- code/.eslintrc.js | 1 - code/core/src/csf/index.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/code/.eslintrc.js b/code/.eslintrc.js index 06c7c4fb137e..4268ec993dd6 100644 --- a/code/.eslintrc.js +++ b/code/.eslintrc.js @@ -15,7 +15,6 @@ module.exports = { 'error', { devDependencies: true, peerDependencies: true }, ], - 'no-underscore-dangle': 'off', 'import/no-unresolved': 'off', // covered by typescript 'eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }], 'eslint-comments/no-unused-disable': 'error', diff --git a/code/core/src/csf/index.ts b/code/core/src/csf/index.ts index 91b503856c56..0c0ea4cbe21a 100644 --- a/code/core/src/csf/index.ts +++ b/code/core/src/csf/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-underscore-dangle */ import type { Args, ComponentAnnotations, From 815c7784911eaa5b9ae606f50536ee83988d0a91 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 31 Jan 2025 11:59:31 +0100 Subject: [PATCH 116/144] Fix eslint --- code/.storybook/preview.tsx | 1 + code/.storybook/storybook.setup.ts | 1 + code/addons/test/src/vitest-plugin/test-utils.ts | 2 ++ 3 files changed, 4 insertions(+) diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index 739c2ea7ba43..1555e13ef113 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -130,6 +130,7 @@ const ThemedSetRoot = () => { return null; }; +// eslint-disable-next-line no-underscore-dangle const preview = (window as any).__STORYBOOK_PREVIEW__ as PreviewWeb | undefined; const channel = (window as any).__STORYBOOK_ADDONS_CHANNEL__ as Channel | undefined; const loaders = [ diff --git a/code/.storybook/storybook.setup.ts b/code/.storybook/storybook.setup.ts index 2fe58c6dbe96..80160218a314 100644 --- a/code/.storybook/storybook.setup.ts +++ b/code/.storybook/storybook.setup.ts @@ -13,6 +13,7 @@ const annotations = setProjectAnnotations([ // experiment with injecting Vitest's interactivity API over our userEvent while tests run in browser mode // https://vitest.dev/guide/browser/interactivity-api.html loaders: async (context) => { + // eslint-disable-next-line no-underscore-dangle if (globalThis.__vitest_browser__) { const vitest = await import('@vitest/browser/context'); const { userEvent: browserEvent } = vitest; diff --git a/code/addons/test/src/vitest-plugin/test-utils.ts b/code/addons/test/src/vitest-plugin/test-utils.ts index 92b09a49623a..f8259d4445fb 100644 --- a/code/addons/test/src/vitest-plugin/test-utils.ts +++ b/code/addons/test/src/vitest-plugin/test-utils.ts @@ -1,4 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ + +/* eslint-disable no-underscore-dangle */ import { type RunnerTask, type TaskMeta, type TestContext } from 'vitest'; import { From 43ac42571ad5a51612d6d00942628cd8878d6bd0 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 31 Jan 2025 12:16:50 +0100 Subject: [PATCH 117/144] Fix vitest --- scripts/tasks/sandbox-parts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 563ca02ee478..e3f66447f2e8 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -429,7 +429,7 @@ export async function setupVitest(details: TemplateDetails, options: PassedOptio import projectAnnotations from './preview' // setProjectAnnotations still kept to support non-CSF4 story tests - const annotations = setProjectAnnotations(projectAnnotations.input) + const annotations = setProjectAnnotations(projectAnnotations.composed) beforeAll(annotations.beforeAll) ` ); From 70429c2392e565a0f0dad6094bac57d7c4c73caf Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 31 Jan 2025 13:44:55 +0100 Subject: [PATCH 118/144] Re-export definePreview --- code/frameworks/react-vite/src/index.ts | 2 ++ code/frameworks/react-webpack5/src/index.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/code/frameworks/react-vite/src/index.ts b/code/frameworks/react-vite/src/index.ts index fcb073fefcd6..54688d096160 100644 --- a/code/frameworks/react-vite/src/index.ts +++ b/code/frameworks/react-vite/src/index.ts @@ -1 +1,3 @@ +export { definePreview } from '@storybook/react'; + export * from './types'; diff --git a/code/frameworks/react-webpack5/src/index.ts b/code/frameworks/react-webpack5/src/index.ts index fcb073fefcd6..84081fa8f85d 100644 --- a/code/frameworks/react-webpack5/src/index.ts +++ b/code/frameworks/react-webpack5/src/index.ts @@ -1 +1,2 @@ export * from './types'; +export { definePreview } from '@storybook/react'; From a838e73c0cc20041802e26a4fe9274a27ecde684 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 31 Jan 2025 14:18:05 +0100 Subject: [PATCH 119/144] Adjust codemod --- code/.storybook/preview.tsx | 2 +- code/core/src/csf-tools/ConfigFile.test.ts | 18 +++++++++--------- .../experimental-nextjs-vite/src/index.ts | 14 ++++++++++++++ .../codemod/helpers/config-to-csf-factory.ts | 7 ------- scripts/tasks/sandbox-parts.ts | 14 ++++++++++++-- 5 files changed, 36 insertions(+), 19 deletions(-) diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index 1555e13ef113..048343067be2 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -17,12 +17,12 @@ import { import { DocsContext } from '@storybook/blocks'; import { global } from '@storybook/global'; import type { Decorator, Loader, ReactRenderer } from '@storybook/react'; -import { definePreview } from '@storybook/react'; // TODO add empty preview // import * as storysource from '@storybook/addon-storysource'; // import * as designs from '@storybook/addon-designs/preview'; import addonTest from '@storybook/experimental-addon-test'; +import { definePreview } from '@storybook/react-vite'; import addonA11y from '@storybook/addon-a11y'; import addonEssentials from '@storybook/addon-essentials'; diff --git a/code/core/src/csf-tools/ConfigFile.test.ts b/code/core/src/csf-tools/ConfigFile.test.ts index 6425a0ced108..bd9c6b97d482 100644 --- a/code/core/src/csf-tools/ConfigFile.test.ts +++ b/code/core/src/csf-tools/ConfigFile.test.ts @@ -247,7 +247,7 @@ describe('ConfigFile', () => { describe('factory config', () => { it('parses correctly', () => { const source = dedent` - import { definePreview } from '@storybook/react-vite/preview'; + import { definePreview } from '@storybook/react-vite'; const config = definePreview({ framework: 'foo', @@ -262,7 +262,7 @@ describe('ConfigFile', () => { getField( ['core', 'builder'], dedent` - import { definePreview } from '@storybook/react-vite/preview'; + import { definePreview } from '@storybook/react-vite'; export const foo = definePreview({ core: { builder: 'webpack5' } }); ` ) @@ -273,7 +273,7 @@ describe('ConfigFile', () => { getField( ['tags'], dedent` - import { definePreview } from '@storybook/react-vite/preview'; + import { definePreview } from '@storybook/react-vite'; const parameters = {}; export const config = definePreview({ parameters, @@ -528,14 +528,14 @@ describe('ConfigFile', () => { ['core', 'builder'], 'webpack5', dedent` - import { definePreview } from '@storybook/react-vite/preview'; + import { definePreview } from '@storybook/react-vite'; export const foo = definePreview({ addons: [], }); ` ) ).toMatchInlineSnapshot(` - import { definePreview } from '@storybook/react-vite/preview'; + import { definePreview } from '@storybook/react-vite'; export const foo = definePreview({ addons: [], @@ -551,14 +551,14 @@ describe('ConfigFile', () => { ['core', 'builder'], 'webpack5', dedent` - import { definePreview } from '@storybook/react-vite/preview'; + import { definePreview } from '@storybook/react-vite'; export const foo = definePreview({ core: { foo: 'bar' }, }); ` ) ).toMatchInlineSnapshot(` - import { definePreview } from '@storybook/react-vite/preview'; + import { definePreview } from '@storybook/react-vite'; export const foo = definePreview({ core: { foo: 'bar', @@ -573,14 +573,14 @@ describe('ConfigFile', () => { ['core', 'builder'], 'webpack5', dedent` - import { definePreview } from '@storybook/react-vite/preview'; + import { definePreview } from '@storybook/react-vite'; export const foo = definePreview({ core: { builder: 'webpack4' }, }); ` ) ).toMatchInlineSnapshot(` - import { definePreview } from '@storybook/react-vite/preview'; + import { definePreview } from '@storybook/react-vite'; export const foo = definePreview({ core: { builder: 'webpack5' }, }); diff --git a/code/frameworks/experimental-nextjs-vite/src/index.ts b/code/frameworks/experimental-nextjs-vite/src/index.ts index 32476387c88c..f620bc6df0a0 100644 --- a/code/frameworks/experimental-nextjs-vite/src/index.ts +++ b/code/frameworks/experimental-nextjs-vite/src/index.ts @@ -1,5 +1,10 @@ +import type { ReactPreview } from '@storybook/react'; +import { definePreview as definePreviewBase } from '@storybook/react'; + import type vitePluginStorybookNextJs from 'vite-plugin-storybook-nextjs'; +import * as nextPreview from './preview'; + export * from './types'; export * from './portable-stories'; @@ -8,3 +13,12 @@ export * from './portable-stories'; declare module '@storybook/experimental-nextjs-vite/vite-plugin' { export const storybookNextJsPlugin: typeof vitePluginStorybookNextJs; } + +export function definePreview(preview: NextPreview['input']) { + return definePreviewBase({ + ...preview, + addons: [nextPreview, ...(preview.addons ?? [])], + }) as NextPreview; +} + +interface NextPreview extends ReactPreview {} diff --git a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts index 4231c3a6fd2a..34d62b3c7f2a 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts @@ -27,13 +27,6 @@ export async function configToCsfFactory( } const methodName = configType === 'main' ? 'defineMain' : 'definePreview'; - // TODO: remove this later, it's just a quick workaround for preview imports - // while it is part of @storybook/react and not @storybook/react-vite - frameworkPackage = - configType === 'preview' && frameworkPackage === '@storybook/react-vite' - ? '@storybook/react' - : frameworkPackage; - const programNode = config._ast.program; const hasNamedExports = Object.keys(config._exportDecls).length > 0; diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index e3f66447f2e8..74da77ec18b5 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -816,7 +816,12 @@ export const extendPreview: Task['run'] = async ({ template, sandboxDir }) => { const previewConfig = await readConfig({ cwd: sandboxDir, fileName: 'preview' }); if ( - template.expected.framework === '@storybook/react-vite' && + [ + '@storybook/react-vite', + '@storybook/react-webpack5', + '@storybook/nextjs', + '@storybook/experimental-nextjs-vite', + ].includes(template.expected.framework) && !template.skipTasks.includes('vitest-integration') ) { previewConfig.setImport(null, '../src/stories/components'); @@ -838,7 +843,12 @@ export const extendPreview: Task['run'] = async ({ template, sandboxDir }) => { export const runMigrations: Task['run'] = async ({ sandboxDir, template }, { dryRun, debug }) => { if ( - template.expected.framework === '@storybook/react-vite' && + [ + '@storybook/react-vite', + '@storybook/react-webpack5', + '@storybook/nextjs', + '@storybook/experimental-nextjs-vite', + ].includes(template.expected.framework) && !template.skipTasks.includes('vitest-integration') ) { await executeCLIStep(steps.automigrate, { From dc79392a1a6d53533a771cc35282f98c49e6ebd0 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 31 Jan 2025 14:25:39 +0100 Subject: [PATCH 120/144] Move csf4 stories --- .../react-vite => renderers/react}/template/stories/csf4.mdx | 0 .../react}/template/stories/csf4.stories.tsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename code/{frameworks/react-vite => renderers/react}/template/stories/csf4.mdx (100%) rename code/{frameworks/react-vite => renderers/react}/template/stories/csf4.stories.tsx (81%) diff --git a/code/frameworks/react-vite/template/stories/csf4.mdx b/code/renderers/react/template/stories/csf4.mdx similarity index 100% rename from code/frameworks/react-vite/template/stories/csf4.mdx rename to code/renderers/react/template/stories/csf4.mdx diff --git a/code/frameworks/react-vite/template/stories/csf4.stories.tsx b/code/renderers/react/template/stories/csf4.stories.tsx similarity index 81% rename from code/frameworks/react-vite/template/stories/csf4.stories.tsx rename to code/renderers/react/template/stories/csf4.stories.tsx index 4bb4a60cbf35..49a02100096b 100644 --- a/code/frameworks/react-vite/template/stories/csf4.stories.tsx +++ b/code/renderers/react/template/stories/csf4.stories.tsx @@ -2,7 +2,7 @@ import config from '#.storybook/preview'; const meta = config.meta({ - component: globalThis.Components.Button, + component: (globalThis as any).Components.Button, args: { label: 'Hello world!', }, From 3847a11e6b8baa8d7522267be351b5a2f00d1778 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 31 Jan 2025 14:53:21 +0100 Subject: [PATCH 121/144] add csfFactory modification option for sandboxes --- .../cli-storybook/src/sandbox-templates.ts | 12 +++++++++++ scripts/tasks/sandbox-parts.ts | 20 ++----------------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/code/lib/cli-storybook/src/sandbox-templates.ts b/code/lib/cli-storybook/src/sandbox-templates.ts index 0324635b1ffe..ae52a6e5f1bf 100644 --- a/code/lib/cli-storybook/src/sandbox-templates.ts +++ b/code/lib/cli-storybook/src/sandbox-templates.ts @@ -75,6 +75,7 @@ export type Template = { disableDocs?: boolean; extraDependencies?: string[]; editAddons?: (addons: string[]) => string[]; + useCsfFactory?: boolean; }; /** * Flag to indicate that this template is a secondary template, which is used mainly to test @@ -136,6 +137,7 @@ const baseTemplates = { builder: '@storybook/builder-webpack5', }, modifications: { + useCsfFactory: true, extraDependencies: ['prop-types'], }, }, @@ -149,6 +151,7 @@ const baseTemplates = { builder: '@storybook/builder-webpack5', }, modifications: { + useCsfFactory: true, mainConfig: { features: { experimentalRSC: true, @@ -169,6 +172,7 @@ const baseTemplates = { builder: '@storybook/builder-webpack5', }, modifications: { + useCsfFactory: true, mainConfig: { features: { experimentalRSC: true, @@ -189,6 +193,7 @@ const baseTemplates = { builder: '@storybook/builder-webpack5', }, modifications: { + useCsfFactory: true, mainConfig: { features: { experimentalRSC: true, @@ -209,6 +214,7 @@ const baseTemplates = { builder: '@storybook/builder-webpack5', }, modifications: { + useCsfFactory: true, mainConfig: { features: { experimentalRSC: true, @@ -229,6 +235,7 @@ const baseTemplates = { builder: '@storybook/builder-vite', }, modifications: { + useCsfFactory: true, mainConfig: { framework: '@storybook/experimental-nextjs-vite', features: { @@ -255,6 +262,7 @@ const baseTemplates = { builder: '@storybook/builder-vite', }, modifications: { + useCsfFactory: true, mainConfig: { framework: '@storybook/experimental-nextjs-vite', features: { @@ -298,6 +306,7 @@ const baseTemplates = { builder: '@storybook/builder-vite', }, modifications: { + useCsfFactory: true, extraDependencies: ['prop-types'], mainConfig: { features: { @@ -329,6 +338,7 @@ const baseTemplates = { builder: '@storybook/builder-vite', }, modifications: { + useCsfFactory: true, extraDependencies: ['prop-types'], mainConfig: { features: { @@ -347,6 +357,7 @@ const baseTemplates = { builder: '@storybook/builder-webpack5', }, modifications: { + useCsfFactory: true, extraDependencies: ['prop-types'], }, skipTasks: ['e2e-tests-dev', 'bench', 'vitest-integration'], @@ -385,6 +396,7 @@ const baseTemplates = { builder: '@storybook/builder-webpack5', }, modifications: { + useCsfFactory: true, extraDependencies: ['prop-types'], }, skipTasks: ['e2e-tests-dev', 'bench', 'vitest-integration'], diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 74da77ec18b5..9c8887fad2b6 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -815,15 +815,7 @@ export const extendPreview: Task['run'] = async ({ template, sandboxDir }) => { logger.log('📝 Extending preview.js'); const previewConfig = await readConfig({ cwd: sandboxDir, fileName: 'preview' }); - if ( - [ - '@storybook/react-vite', - '@storybook/react-webpack5', - '@storybook/nextjs', - '@storybook/experimental-nextjs-vite', - ].includes(template.expected.framework) && - !template.skipTasks.includes('vitest-integration') - ) { + if (template.modifications.useCsfFactory) { previewConfig.setImport(null, '../src/stories/components'); previewConfig.setImport({ namespace: 'coreAnnotations' }, '../template-stories/core/preview'); previewConfig.setImport( @@ -842,15 +834,7 @@ export const extendPreview: Task['run'] = async ({ template, sandboxDir }) => { }; export const runMigrations: Task['run'] = async ({ sandboxDir, template }, { dryRun, debug }) => { - if ( - [ - '@storybook/react-vite', - '@storybook/react-webpack5', - '@storybook/nextjs', - '@storybook/experimental-nextjs-vite', - ].includes(template.expected.framework) && - !template.skipTasks.includes('vitest-integration') - ) { + if (template.modifications.useCsfFactory) { await executeCLIStep(steps.automigrate, { cwd: sandboxDir, argument: 'csf-factories', From b5b0420014fc9d53bf45a67deef25ce4c66b730e Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 31 Jan 2025 14:59:26 +0100 Subject: [PATCH 122/144] fix tests --- .../src/codemod/helpers/config-to-csf-factory.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts index 85174836901b..6be5462c51ee 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts @@ -156,7 +156,7 @@ describe('preview specific functionality', () => { }; `) ).resolves.toMatchInlineSnapshot(` - import { definePreview } from '@storybook/react'; + import { definePreview } from '@storybook/react-vite'; export default definePreview({ tags: ['test'], @@ -167,7 +167,7 @@ describe('preview specific functionality', () => { it('should remove legacy preview type imports', async () => { await expect( transform(dedent` - import type { Preview } from '@storybook/react' + import type { Preview } from '@storybook/react-vite' const preview: Preview = { tags: [] @@ -175,7 +175,7 @@ describe('preview specific functionality', () => { export default preview; `) ).resolves.toMatchInlineSnapshot(` - import { definePreview } from '@storybook/react'; + import { definePreview } from '@storybook/react-vite'; export default definePreview({ tags: [], From aa305bb946b048b29942c50ffe5a2a4758c4d8c4 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 31 Jan 2025 16:09:57 +0100 Subject: [PATCH 123/144] fix issues --- code/frameworks/nextjs/package.json | 2 +- scripts/tasks/sandbox-parts.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index 0a8750f46c0d..1c827f099204 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -87,7 +87,7 @@ "import": "./dist/export-mocks/router/index.mjs", "require": "./dist/export-mocks/router/index.js" }, - "./main": { + "./node": { "types": "./dist/node/index.d.ts", "node": "./dist/node/index.js", "import": "./dist/node/index.mjs", diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 9c8887fad2b6..07db3deff32c 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -815,7 +815,7 @@ export const extendPreview: Task['run'] = async ({ template, sandboxDir }) => { logger.log('📝 Extending preview.js'); const previewConfig = await readConfig({ cwd: sandboxDir, fileName: 'preview' }); - if (template.modifications.useCsfFactory) { + if (template.modifications?.useCsfFactory) { previewConfig.setImport(null, '../src/stories/components'); previewConfig.setImport({ namespace: 'coreAnnotations' }, '../template-stories/core/preview'); previewConfig.setImport( @@ -834,7 +834,7 @@ export const extendPreview: Task['run'] = async ({ template, sandboxDir }) => { }; export const runMigrations: Task['run'] = async ({ sandboxDir, template }, { dryRun, debug }) => { - if (template.modifications.useCsfFactory) { + if (template.modifications?.useCsfFactory) { await executeCLIStep(steps.automigrate, { cwd: sandboxDir, argument: 'csf-factories', From ce00ea5f9b852113149b4c8e0b8f07a35a0ce783 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 31 Jan 2025 16:22:33 +0100 Subject: [PATCH 124/144] fix sandboxes --- code/lib/cli-storybook/src/sandbox-templates.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/lib/cli-storybook/src/sandbox-templates.ts b/code/lib/cli-storybook/src/sandbox-templates.ts index ae52a6e5f1bf..d066b4b34438 100644 --- a/code/lib/cli-storybook/src/sandbox-templates.ts +++ b/code/lib/cli-storybook/src/sandbox-templates.ts @@ -107,6 +107,7 @@ const baseTemplates = { skipTasks: ['e2e-tests-dev', 'bench', 'vitest-integration'], modifications: { + useCsfFactory: true, extraDependencies: ['prop-types'], mainConfig: (config) => { const stories = config.getFieldValue>(['stories']); @@ -288,6 +289,7 @@ const baseTemplates = { builder: '@storybook/builder-vite', }, modifications: { + useCsfFactory: true, extraDependencies: ['prop-types'], mainConfig: { features: { From d0c668baa5e8439c88628bcb41a76dfbc65c7b53 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 31 Jan 2025 17:01:37 +0100 Subject: [PATCH 125/144] review changes --- code/addons/a11y/src/types.ts | 10 ++++++---- code/addons/actions/src/types.ts | 12 ++++++++---- code/addons/backgrounds/src/types.ts | 2 +- code/addons/controls/src/types.ts | 22 ++++------------------ code/addons/docs/src/types.ts | 12 ++++-------- code/addons/highlight/src/types.ts | 2 +- code/addons/interactions/src/preview.ts | 2 +- code/addons/interactions/src/types.ts | 5 +---- code/addons/measure/src/types.ts | 2 +- code/addons/outline/src/types.ts | 2 +- code/addons/storysource/src/types.ts | 2 +- code/addons/test/src/types.ts | 5 +---- code/addons/themes/src/types.ts | 2 +- code/addons/viewport/src/types.ts | 4 ++-- code/renderers/react/src/types.ts | 6 +++++- 15 files changed, 38 insertions(+), 52 deletions(-) diff --git a/code/addons/a11y/src/types.ts b/code/addons/a11y/src/types.ts index f13d913c1503..b05b75ca1833 100644 --- a/code/addons/a11y/src/types.ts +++ b/code/addons/a11y/src/types.ts @@ -15,7 +15,7 @@ export interface A11yParameters { /** * Configuration for the accessibility rules * - * @see https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter + * @see https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure */ config?: Spec; @@ -26,7 +26,7 @@ export interface A11yParameters { */ options?: RunOptions; - /** Turn off this addon's behavior */ + /** Remove the addon panel and disable the addon's behavior */ disable?: boolean; }; } @@ -39,8 +39,10 @@ export interface A11yGlobals { */ a11y: { /** - * Prevent the addon to execute automatic accessibility checks upon visiting a story. You can - * still trigger the checks from the addon panel. + * Prevent the addon from executing automated accessibility checks upon visiting a story. You + * can still trigger the checks from the addon panel. + * + * @see https://storybook.js.org/docs/writing-tests/accessibility-testing#turn-off-automated-a11y-tests */ manual?: boolean; }; diff --git a/code/addons/actions/src/types.ts b/code/addons/actions/src/types.ts index 2e2f249eb407..47b3bb9ddd84 100644 --- a/code/addons/actions/src/types.ts +++ b/code/addons/actions/src/types.ts @@ -6,7 +6,7 @@ export interface ActionsParameters { */ actions: { /** - * Create actions for each arg that matches the regex. + * Create actions for each arg that matches the regex. (**NOT recommended, see below**) * * This is quite useful when your component has dozens (or hundreds) of methods and you do not * want to manually apply the fn utility for each of those methods. However, this is not the @@ -15,11 +15,11 @@ export interface ActionsParameters { * functions, you will need to also define args with the fn utility to test them in your play * function. * - * @example ArgTypesRegex: '^on.*' + * @example `argTypesRegex: '^on.*'` */ argTypesRegex?: string; - /** Turn off this addon's behavior */ + /** Remove the addon panel and disable the addon's behavior */ disable?: boolean; /** @@ -27,7 +27,11 @@ export interface ActionsParameters { * and triggers an action when the event is called for a given selector. The format is * ` `. The selector is optional; it defaults to all elements. * - * @example Handles: ['mouseover', 'click .btn'] + * **To enable this feature, you must use the `withActions` decorator.** + * + * @example `handles: ['mouseover', 'click .btn']` + * + * @see https://storybook.js.org/docs/essentials/actions#action-event-handlers */ handles?: string[]; }; diff --git a/code/addons/backgrounds/src/types.ts b/code/addons/backgrounds/src/types.ts index 067d5b83720b..e9d4fd846089 100644 --- a/code/addons/backgrounds/src/types.ts +++ b/code/addons/backgrounds/src/types.ts @@ -32,7 +32,7 @@ export interface BackgroundsParameters { /** Default background color */ default?: string; - /** Turn off this addon's behavior */ + /** Remove the addon panel and disable the addon's behavior */ disable?: boolean; /** Configuration for the background grid */ diff --git a/code/addons/controls/src/types.ts b/code/addons/controls/src/types.ts index 65743ed269b7..d12dc06ad802 100644 --- a/code/addons/controls/src/types.ts +++ b/code/addons/controls/src/types.ts @@ -5,17 +5,13 @@ export interface ControlsParameters { * @see https://storybook.js.org/docs/essentials/controls#parameters-1 */ controls: { - /** Turn off this addon's behavior */ + /** Remove the addon panel and disable the addon's behavior */ disable?: boolean; /** Disable the ability to create or edit stories from the Controls panel */ disableSaveFromUI?: boolean; - /** - * Exclude specific properties from the Controls panel - * - * @see https://storybook.js.org/docs/essentials/controls#filtering-controls - */ + /** Exclude specific properties from the Controls panel */ exclude?: string[] | RegExp; /** @@ -24,11 +20,7 @@ export interface ControlsParameters { */ expanded?: boolean; - /** - * Exclude only specific properties in the Controls panel - * - * @see https://storybook.js.org/docs/essentials/controls#filtering-controls - */ + /** Exclude only specific properties in the Controls panel */ include?: string[] | RegExp; /** @@ -36,16 +28,10 @@ export interface ControlsParameters { * * @example PresetColors: [{ color: '#ff4785', title: 'Coral' }, 'rgba(0, 159, 183, 1)', * '#fe4a49'] - * - * @see https://storybook.js.org/docs/essentials/controls#specify-initial-preset-color-swatches */ presetColors?: Array; - /** - * Controls sorting order - * - * @see https://storybook.js.org/docs/essentials/controls#sorting-controls - */ + /** Controls sorting order */ sort?: 'none' | 'alpha' | 'requiredFirst'; }; } diff --git a/code/addons/docs/src/types.ts b/code/addons/docs/src/types.ts index 563f30328fd5..880044841577 100644 --- a/code/addons/docs/src/types.ts +++ b/code/addons/docs/src/types.ts @@ -5,7 +5,7 @@ type StoryBlockParameters = { autoplay?: boolean; /** * Set a minimum height (note for an iframe this is the actual height) when rendering a story in - * an iframe or inline. This overrides parameters.docs.story.iframeHeight for iframes. + * an iframe or inline. This overrides `parameters.docs.story.iframeHeight` for iframes. */ height?: string; /** IFrame configuration */ @@ -18,7 +18,7 @@ type StoryBlockParameters = { /** Specifies the CSF file to which the story is associated */ meta: ModuleExports; /** - * Specifies which story is rendered by the Story block. If no of is defined and the MDX file is + * Specifies which story is rendered by the Story block. If no `of` is defined and the MDX file is * attached, the primary (first) story will be rendered. */ of: ModuleExport; @@ -31,11 +31,7 @@ type ControlsBlockParameters = { /** Exclude only specific properties in the Controls panel */ include?: string[] | RegExp; - /** - * Controls sorting order - * - * @see https://storybook.js.org/docs/api/doc-blocks/doc-block-controls#sort - */ + /** Controls sorting order */ sort?: 'none' | 'alpha' | 'requiredFirst'; }; @@ -182,7 +178,7 @@ export interface DocsParameters { */ description?: DescriptionBlockParameters; - /** Turn off this addon's behavior */ + /** Remove the addon panel and disable the addon's behavior */ disable?: boolean; /** diff --git a/code/addons/highlight/src/types.ts b/code/addons/highlight/src/types.ts index 98a801d923ea..3613b23fb9b5 100644 --- a/code/addons/highlight/src/types.ts +++ b/code/addons/highlight/src/types.ts @@ -5,7 +5,7 @@ export interface HighlightParameters { * @see https://storybook.js.org/docs/essentials/highlight#parameters */ highlight: { - /** Turn off this addon's behavior */ + /** Remove the addon panel and disable the addon's behavior */ disable?: boolean; }; } diff --git a/code/addons/interactions/src/preview.ts b/code/addons/interactions/src/preview.ts index 8239442bf912..42c419390239 100644 --- a/code/addons/interactions/src/preview.ts +++ b/code/addons/interactions/src/preview.ts @@ -20,6 +20,6 @@ export const runStep = instrument( // perhaps csf types need to be updated? StepRunner expects Promise and not Promise | void ).step as StepRunner; -export const parameters: InteractionsParameters['interactions'] = { +export const parameters: InteractionsParameters['test'] = { throwPlayFunctionExceptions: false, }; diff --git a/code/addons/interactions/src/types.ts b/code/addons/interactions/src/types.ts index c21a192b713b..9e21dc4e1521 100644 --- a/code/addons/interactions/src/types.ts +++ b/code/addons/interactions/src/types.ts @@ -4,10 +4,7 @@ export interface InteractionsParameters { * * @see https://storybook.js.org/docs/essentials/interactions */ - interactions: { - /** Turn off this addon's behavior */ - disable?: boolean; - + test: { /** Ignore unhandled errors during test execution */ dangerouslyIgnoreUnhandledErrors?: boolean; diff --git a/code/addons/measure/src/types.ts b/code/addons/measure/src/types.ts index 8aaf6ecf0d7d..e51cf69775b5 100644 --- a/code/addons/measure/src/types.ts +++ b/code/addons/measure/src/types.ts @@ -5,7 +5,7 @@ export interface MeasureParameters { * @see https://storybook.js.org/docs/essentials/measure-and-outline#parameters */ measure: { - /** Turn off this addon's behavior */ + /** Remove the addon panel and disable the addon's behavior */ disable?: boolean; }; } diff --git a/code/addons/outline/src/types.ts b/code/addons/outline/src/types.ts index b4eacf61a798..b5b3d4b3d663 100644 --- a/code/addons/outline/src/types.ts +++ b/code/addons/outline/src/types.ts @@ -5,7 +5,7 @@ export interface OutlineParameters { * @see https://storybook.js.org/docs/essentials/measure-and-outline#parameters */ outline: { - /** Turn off this addon's behavior */ + /** Remove the addon panel and disable the addon's behavior */ disable?: boolean; }; } diff --git a/code/addons/storysource/src/types.ts b/code/addons/storysource/src/types.ts index 83ee083c3e56..1a350264c6d1 100644 --- a/code/addons/storysource/src/types.ts +++ b/code/addons/storysource/src/types.ts @@ -8,7 +8,7 @@ export interface StorySourceParameters { /** Dark mode for source code */ dark?: boolean; - /** Turn off this addon's behavior */ + /** Remove the addon panel and disable the addon's behavior */ disable?: boolean; /** Source code formatting options */ diff --git a/code/addons/test/src/types.ts b/code/addons/test/src/types.ts index 6b9dd033a3b4..1e36b89e08ff 100644 --- a/code/addons/test/src/types.ts +++ b/code/addons/test/src/types.ts @@ -1,13 +1,10 @@ export interface TestParameters { /** - * Interactions configuration + * Test addon configuration * * @see https://storybook.js.org/docs/writing-tests/test-addon */ test: { - /** Turn off this addon's behavior */ - disable?: boolean; - /** Ignore unhandled errors during test execution */ dangerouslyIgnoreUnhandledErrors?: boolean; diff --git a/code/addons/themes/src/types.ts b/code/addons/themes/src/types.ts index 018c38e916e0..3a825e37983e 100644 --- a/code/addons/themes/src/types.ts +++ b/code/addons/themes/src/types.ts @@ -10,7 +10,7 @@ export interface ThemesParameters { * @see https://github.com/storybookjs/storybook/blob/next/code/addons/themes/README.md */ themes: { - /** Turn off this addon's behavior */ + /** Remove the addon panel and disable the addon's behavior */ disable?: boolean; /** Which theme to override for the story */ themeOverride?: string; diff --git a/code/addons/viewport/src/types.ts b/code/addons/viewport/src/types.ts index fff8216d13b1..0a99d209f642 100644 --- a/code/addons/viewport/src/types.ts +++ b/code/addons/viewport/src/types.ts @@ -59,8 +59,8 @@ export interface ViewportParameters { defaultViewport?: string; /** - * Turn off this addon's behavior. If you wish to turn off this addon for the entire Storybook, - * you should do so when registering addon-essentials + * Remove the addon panel and disable the addon's behavior . If you wish to turn off this addon + * for the entire Storybook, you should do so when registering addon-essentials * * @see https://storybook.js.org/docs/essentials/index#disabling-addons */ diff --git a/code/renderers/react/src/types.ts b/code/renderers/react/src/types.ts index ef8154b89f59..269e5897f52e 100644 --- a/code/renderers/react/src/types.ts +++ b/code/renderers/react/src/types.ts @@ -18,7 +18,11 @@ export interface ShowErrorArgs { export interface ReactParameters { /** React renderer configuration */ react?: { - /** Whether to enable React Server Components */ + /** + * Whether to enable React Server Components + * + * @see https://storybook.js.org/docs/get-started/frameworks/nextjs#react-server-components-rsc + */ rsc?: boolean; /** Options passed to React root creation */ rootOptions?: { From 580f1e2a460fed51fa9c7ce7aa89f6a3f21b1eeb Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 3 Feb 2025 10:29:20 +0100 Subject: [PATCH 126/144] support storysort in csf factories --- code/.storybook/preview.tsx | 8 +--- code/.storybook/storybook.setup.ts | 4 +- .../components/Button/Button.stories.tsx | 4 +- .../mocks/csf4-variances.stories.tsx | 4 +- .../csf-tools/getStorySortParameter.test.ts | 45 +++++++++++++++++++ .../src/csf-tools/getStorySortParameter.ts | 5 ++- .../template/stories/csf4.stories.tsx | 4 +- .../codemod/helpers/story-to-csf-factory.ts | 4 +- .../src/__test__/Button.csf4.stories.tsx | 4 +- 9 files changed, 63 insertions(+), 19 deletions(-) diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index f26260d48339..9314446e0a74 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -321,10 +321,6 @@ const decorators = [ ] satisfies Decorator[]; const parameters = { - options: { - storySort: (a, b) => - a.title === b.title ? 0 : a.id.localeCompare(b.id, undefined, { numeric: true }), - }, docs: { theme: themes.light, toc: {}, @@ -373,7 +369,7 @@ const parameters = { }, }; -export const config = definePreview({ +export default definePreview({ addons: [ addonThemes(), addonEssentials(), @@ -382,8 +378,8 @@ export const config = definePreview({ addonsPreview, templatePreview, ], - parameters, decorators, loaders, tags: ['test', 'vitest'], + parameters, }); diff --git a/code/.storybook/storybook.setup.ts b/code/.storybook/storybook.setup.ts index 3521dcafb8cc..81f5202d719c 100644 --- a/code/.storybook/storybook.setup.ts +++ b/code/.storybook/storybook.setup.ts @@ -3,12 +3,12 @@ import { beforeAll, vi, expect as vitestExpect } from 'vitest'; import { setProjectAnnotations } from '@storybook/react'; import { userEvent as storybookEvent, expect as storybookExpect } from '@storybook/test'; -import { config } from './preview'; +import previw from './preview'; vi.spyOn(console, 'warn').mockImplementation((...args) => console.log(...args)); const annotations = setProjectAnnotations([ - config.input, + previw.input, { // experiment with injecting Vitest's interactivity API over our userEvent while tests run in browser mode // https://vitest.dev/guide/browser/interactivity-api.html diff --git a/code/core/src/components/components/Button/Button.stories.tsx b/code/core/src/components/components/Button/Button.stories.tsx index c2724412aa18..78e061d5d1a7 100644 --- a/code/core/src/components/components/Button/Button.stories.tsx +++ b/code/core/src/components/components/Button/Button.stories.tsx @@ -3,10 +3,10 @@ import React from 'react'; import { FaceHappyIcon } from '@storybook/icons'; -import { config } from '../../../../../.storybook/preview'; +import preview from '../../../../../.storybook/preview'; import { Button } from './Button'; -const meta = config.meta({ +const meta = preview.meta({ id: 'button-component', title: 'Button', component: Button, diff --git a/code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx b/code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx index fd9003947612..51f78d90ed92 100644 --- a/code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx +++ b/code/core/src/core-server/utils/save-story/mocks/csf4-variances.stories.tsx @@ -1,7 +1,7 @@ // @ts-expect-error this is just a mock file -import { config } from '#.storybook/preview'; +import preview from '#.storybook/preview'; -const meta = config.meta({ +const meta = preview.meta({ title: 'MyComponent', args: { initial: 'foo', diff --git a/code/core/src/csf-tools/getStorySortParameter.test.ts b/code/core/src/csf-tools/getStorySortParameter.test.ts index 565e0e10fdf2..ea468abd19ba 100644 --- a/code/core/src/csf-tools/getStorySortParameter.test.ts +++ b/code/core/src/csf-tools/getStorySortParameter.test.ts @@ -477,6 +477,51 @@ export default { } `); }); + describe('csf factories', () => { + it('inline storysort in default export', () => { + expect( + getStorySortParameter(dedent` + export default definePreview({ + parameters: { + options: { + storySort: { + order: ['General'] + } + }, + }, + }); + `) + ).toMatchInlineSnapshot(` + { + "order": [ + "General", + ], + } + `); + }); + it('variable reference in default export', () => { + expect( + getStorySortParameter(dedent` + const parameters = { + options: { + storySort: { + order: ['General'] + } + }, + }; + export default definePreview({ + parameters, + }); + `) + ).toMatchInlineSnapshot(` + { + "order": [ + "General", + ], + } + `); + }); + }); }); describe('unsupported', () => { it('bad default export', () => { diff --git a/code/core/src/csf-tools/getStorySortParameter.ts b/code/core/src/csf-tools/getStorySortParameter.ts index 4615ca64ce0e..4659859e1c77 100644 --- a/code/core/src/csf-tools/getStorySortParameter.ts +++ b/code/core/src/csf-tools/getStorySortParameter.ts @@ -131,7 +131,10 @@ export const getStorySortParameter = (previewCode: string) => { defaultObj = findVarInitialization(defaultObj.name, ast.program); } defaultObj = stripTSModifiers(defaultObj); - if (t.isObjectExpression(defaultObj)) { + // parse the call arg when using definePreview({ ... }) + if (t.isCallExpression(defaultObj) && t.isObjectExpression(defaultObj.arguments?.[0])) { + storySort = parseDefault(defaultObj.arguments[0], ast.program); + } else if (t.isObjectExpression(defaultObj)) { storySort = parseDefault(defaultObj, ast.program); } else { unsupported('default', false); diff --git a/code/frameworks/react-vite/template/stories/csf4.stories.tsx b/code/frameworks/react-vite/template/stories/csf4.stories.tsx index 4bb4a60cbf35..05c06367c297 100644 --- a/code/frameworks/react-vite/template/stories/csf4.stories.tsx +++ b/code/frameworks/react-vite/template/stories/csf4.stories.tsx @@ -1,7 +1,7 @@ // @ts-expect-error this will be part of the package.json of the sandbox -import config from '#.storybook/preview'; +import preview from '#.storybook/preview'; -const meta = config.meta({ +const meta = preview.meta({ component: globalThis.Components.Button, args: { label: 'Hello world!', diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts index 61ba048e8970..f18a37791786 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts @@ -34,7 +34,7 @@ export async function storyToCsfFactory(info: FileInfo) { /** * Add the preview import if it doesn't exist yet: * - * `import { config } from '#.storybook/preview'`; + * `import preview from '#.storybook/preview'`; */ const programNode = csf._ast.program; let foundConfigImport = false; @@ -196,7 +196,7 @@ export async function storyToCsfFactory(info: FileInfo) { * * Into a meta call: * - * `const meta = config.meta({ title: 'A' });` + * `const meta = preview.meta({ title: 'A' });` */ const binding = csf._metaPath.scope.getBinding(declaration.name); if (binding && binding.path.isVariableDeclarator()) { diff --git a/code/renderers/react/src/__test__/Button.csf4.stories.tsx b/code/renderers/react/src/__test__/Button.csf4.stories.tsx index 067e481a3d6a..3ea470023cfe 100644 --- a/code/renderers/react/src/__test__/Button.csf4.stories.tsx +++ b/code/renderers/react/src/__test__/Button.csf4.stories.tsx @@ -8,9 +8,9 @@ import { action } from '@storybook/addon-actions'; import { definePreview } from '../preview'; import { Button } from './Button'; -const config = definePreview({}); +const preview = definePreview({}); -const meta = config.meta({ +const meta = preview.meta({ id: 'button-component', title: 'Example/CSF4/Button', component: Button, From c8621582abd690ed6d8026a0fbb0da8ca393a9ca Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 3 Feb 2025 12:50:19 +0100 Subject: [PATCH 127/144] UI: Support CSF factories when creating story files from UI --- .../common/utils/sync-main-preview-addons.ts | 24 +++----- .../core-server/utils/get-new-story-file.ts | 56 ++++++++++++++----- .../csf-factory-template.test.ts | 26 +++++++++ .../csf-factory-template.ts | 33 +++++++++++ code/core/src/csf-tools/ConfigFile.ts | 17 ++++++ 5 files changed, 125 insertions(+), 31 deletions(-) create mode 100644 code/core/src/core-server/utils/new-story-templates/csf-factory-template.test.ts create mode 100644 code/core/src/core-server/utils/new-story-templates/csf-factory-template.ts diff --git a/code/core/src/common/utils/sync-main-preview-addons.ts b/code/core/src/common/utils/sync-main-preview-addons.ts index 340956da19b9..462b9554adc7 100644 --- a/code/core/src/common/utils/sync-main-preview-addons.ts +++ b/code/core/src/common/utils/sync-main-preview-addons.ts @@ -2,7 +2,12 @@ import { types as t } from '@storybook/core/babel'; import type { StorybookConfig } from '@storybook/types'; -import { type ConfigFile, readConfig, writeConfig } from '@storybook/core/csf-tools'; +import { + type ConfigFile, + isCsfFactoryPreview, + readConfig, + writeConfig, +} from '@storybook/core/csf-tools'; import picocolors from 'picocolors'; @@ -22,22 +27,9 @@ export async function getSyncedStorybookAddons( mainConfig: StorybookConfig, previewConfig: ConfigFile ): Promise { - const program = previewConfig._ast.program; - const isCsfFactoryPreview = !!program.body.find((node) => { - return ( - t.isImportDeclaration(node) && - node.source.value.includes('@storybook') && - node.specifiers.some((specifier) => { - return ( - t.isImportSpecifier(specifier) && - t.isIdentifier(specifier.imported) && - specifier.imported.name === 'definePreview' - ); - }) - ); - }); + const isCsfFactory = isCsfFactoryPreview(previewConfig); - if (!isCsfFactoryPreview) { + if (!isCsfFactory) { return previewConfig; } diff --git a/code/core/src/core-server/utils/get-new-story-file.ts b/code/core/src/core-server/utils/get-new-story-file.ts index 570ab8aecf7e..df8276484693 100644 --- a/code/core/src/core-server/utils/get-new-story-file.ts +++ b/code/core/src/core-server/utils/get-new-story-file.ts @@ -1,8 +1,10 @@ import { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; import { basename, dirname, extname, join } from 'node:path'; import { extractProperRendererNameFromFramework, + findConfigFile, getFrameworkName, getProjectRoot, rendererPackages, @@ -10,7 +12,10 @@ import { import type { Options } from '@storybook/core/types'; import type { CreateNewStoryRequestPayload } from '@storybook/core/core-events'; +import { isCsfFactoryPreview } from '@storybook/core/csf-tools'; +import { loadConfig } from '../../csf-tools'; +import { getCsfFactoryTemplateForNewStoryFile } from './new-story-templates/csf-factory-template'; import { getJavaScriptTemplateForNewStoryFile } from './new-story-templates/javascript'; import { getTypeScriptTemplateForNewStoryFile } from './new-story-templates/typescript'; @@ -42,21 +47,42 @@ export async function getNewStoryFile( const exportedStoryName = 'Default'; - const storyFileContent = - isTypescript && rendererPackage - ? await getTypeScriptTemplateForNewStoryFile({ - basenameWithoutExtension, - componentExportName, - componentIsDefaultExport, - rendererPackage, - exportedStoryName, - }) - : await getJavaScriptTemplateForNewStoryFile({ - basenameWithoutExtension, - componentExportName, - componentIsDefaultExport, - exportedStoryName, - }); + let useCsfFactory = false; + try { + const previewConfig = findConfigFile('preview', options.configDir); + if (previewConfig) { + const previewContent = await readFile(previewConfig, 'utf-8'); + useCsfFactory = isCsfFactoryPreview(loadConfig(previewContent)); + } + } catch (err) { + // TODO: improve this later on, for now while CSF factories are experimental, just fallback to CSF3 + } + + let storyFileContent = ''; + if (useCsfFactory) { + storyFileContent = await getCsfFactoryTemplateForNewStoryFile({ + basenameWithoutExtension, + componentExportName, + componentIsDefaultExport, + exportedStoryName, + }); + } else { + storyFileContent = + isTypescript && rendererPackage + ? await getTypeScriptTemplateForNewStoryFile({ + basenameWithoutExtension, + componentExportName, + componentIsDefaultExport, + rendererPackage, + exportedStoryName, + }) + : await getJavaScriptTemplateForNewStoryFile({ + basenameWithoutExtension, + componentExportName, + componentIsDefaultExport, + exportedStoryName, + }); + } const storyFilePath = doesStoryFileExist(join(cwd, dir), storyFileName) && componentExportCount > 1 diff --git a/code/core/src/core-server/utils/new-story-templates/csf-factory-template.test.ts b/code/core/src/core-server/utils/new-story-templates/csf-factory-template.test.ts new file mode 100644 index 000000000000..2c41ce689062 --- /dev/null +++ b/code/core/src/core-server/utils/new-story-templates/csf-factory-template.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from 'vitest'; + +import { getCsfFactoryTemplateForNewStoryFile } from './csf-factory-template'; + +describe('csf-factories', () => { + it('should return a CSF factories template with a default import', async () => { + const result = await getCsfFactoryTemplateForNewStoryFile({ + basenameWithoutExtension: 'foo', + componentExportName: 'default', + componentIsDefaultExport: true, + exportedStoryName: 'Default', + }); + + expect(result).toMatchInlineSnapshot(` + "import preview from '#.storybook/preview'; + + import Foo from './foo'; + + const meta = preview.meta({ + component: Foo, + }); + + export const Default = meta.story({});" + `); + }); +}); diff --git a/code/core/src/core-server/utils/new-story-templates/csf-factory-template.ts b/code/core/src/core-server/utils/new-story-templates/csf-factory-template.ts new file mode 100644 index 000000000000..33af96bb6e66 --- /dev/null +++ b/code/core/src/core-server/utils/new-story-templates/csf-factory-template.ts @@ -0,0 +1,33 @@ +import { dedent } from 'ts-dedent'; + +import { getComponentVariableName } from '../get-component-variable-name'; + +interface CsfFactoryTemplateData { + /** The components file name without the extension */ + basenameWithoutExtension: string; + componentExportName: string; + componentIsDefaultExport: boolean; + /** The exported name of the default story */ + exportedStoryName: string; +} + +export async function getCsfFactoryTemplateForNewStoryFile(data: CsfFactoryTemplateData) { + const importName = data.componentIsDefaultExport + ? await getComponentVariableName(data.basenameWithoutExtension) + : data.componentExportName; + const importStatement = data.componentIsDefaultExport + ? `import ${importName} from './${data.basenameWithoutExtension}';` + : `import { ${importName} } from './${data.basenameWithoutExtension}';`; + const previewImport = `import preview from '#.storybook/preview';`; + return dedent` + ${previewImport} + + ${importStatement} + + const meta = preview.meta({ + component: ${importName}, + }); + + export const ${data.exportedStoryName} = meta.story({}); + `; +} diff --git a/code/core/src/csf-tools/ConfigFile.ts b/code/core/src/csf-tools/ConfigFile.ts index 2b76055fa0f8..605e37675866 100644 --- a/code/core/src/csf-tools/ConfigFile.ts +++ b/code/core/src/csf-tools/ConfigFile.ts @@ -970,3 +970,20 @@ export const writeConfig = async (config: ConfigFile, fileName?: string) => { } await writeFile(fname, formatConfig(config)); }; + +export const isCsfFactoryPreview = (previewConfig: ConfigFile) => { + const program = previewConfig._ast.program; + return !!program.body.find((node) => { + return ( + t.isImportDeclaration(node) && + node.source.value.includes('@storybook') && + node.specifiers.some((specifier) => { + return ( + t.isImportSpecifier(specifier) && + t.isIdentifier(specifier.imported) && + specifier.imported.name === 'definePreview' + ); + }) + ); + }); +}; From de6ae64ec548cb0d534ad7609c7563f9d3170b1d Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 3 Feb 2025 14:01:54 +0100 Subject: [PATCH 128/144] Addon Docs: Add missing type entrypoints --- code/addons/docs/ember/index.d.ts | 1 + code/addons/docs/package.json | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 code/addons/docs/ember/index.d.ts diff --git a/code/addons/docs/ember/index.d.ts b/code/addons/docs/ember/index.d.ts new file mode 100644 index 000000000000..18986c0c909f --- /dev/null +++ b/code/addons/docs/ember/index.d.ts @@ -0,0 +1 @@ +export declare const setJSONDoc: (jsonDoc: any) => void; diff --git a/code/addons/docs/package.json b/code/addons/docs/package.json index f10fd2bc2ce4..d0a8a8f8b90f 100644 --- a/code/addons/docs/package.json +++ b/code/addons/docs/package.json @@ -86,6 +86,15 @@ "*": [ "dist/index.d.ts" ], + "angular": [ + "angular/index.d.ts" + ], + "blocks": [ + "dist/blocks.d.ts" + ], + "ember": [ + "ember/index.d.ts" + ], "preview": [ "dist/preview.d.ts" ] From 636346d6080f06d10b7a5bab3b4d1ee4112aa014 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 4 Feb 2025 11:59:21 +0100 Subject: [PATCH 129/144] fix types --- code/frameworks/experimental-nextjs-vite/package.json | 3 +++ code/frameworks/nextjs/package.json | 3 +++ code/frameworks/react-vite/package.json | 3 +++ 3 files changed, 9 insertions(+) diff --git a/code/frameworks/experimental-nextjs-vite/package.json b/code/frameworks/experimental-nextjs-vite/package.json index fd5db4497c9b..67149fecdc65 100644 --- a/code/frameworks/experimental-nextjs-vite/package.json +++ b/code/frameworks/experimental-nextjs-vite/package.json @@ -85,6 +85,9 @@ ], "navigation.mock": [ "dist/export-mocks/navigation/index.d.ts" + ], + "node": [ + "dist/node/index.d.ts" ] } }, diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index 168f9b217a9d..ae268eb69bdc 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -120,6 +120,9 @@ ], "navigation.mock": [ "dist/export-mocks/navigation/index.d.ts" + ], + "node": [ + "dist/node/index.d.ts" ] } }, diff --git a/code/frameworks/react-vite/package.json b/code/frameworks/react-vite/package.json index 309f5705b4ae..5910b01e81de 100644 --- a/code/frameworks/react-vite/package.json +++ b/code/frameworks/react-vite/package.json @@ -48,6 +48,9 @@ ], "preset": [ "dist/preset.d.ts" + ], + "node": [ + "dist/node/index.d.ts" ] } }, From 0f00a1861181cee62e7764dff5197d3a3bacb66e Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 4 Feb 2025 12:57:19 +0100 Subject: [PATCH 130/144] Fix react-webpack/17-ts --- code/lib/cli-storybook/src/sandbox-templates.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/code/lib/cli-storybook/src/sandbox-templates.ts b/code/lib/cli-storybook/src/sandbox-templates.ts index d066b4b34438..1b5ff4b4279e 100644 --- a/code/lib/cli-storybook/src/sandbox-templates.ts +++ b/code/lib/cli-storybook/src/sandbox-templates.ts @@ -374,6 +374,7 @@ const baseTemplates = { builder: '@storybook/builder-webpack5', }, modifications: { + useCsfFactory: true, extraDependencies: ['prop-types'], }, skipTasks: ['e2e-tests-dev', 'bench', 'vitest-integration'], From 9349e5b0d6e7dff08452e1e188512ffc7967851a Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 4 Feb 2025 14:54:05 +0100 Subject: [PATCH 131/144] add option to have relative path in codemod --- .../src/codemod/csf-factories.ts | 76 ++++++++++++++----- .../codemod/helpers/story-to-csf-factory.ts | 25 +++++- 2 files changed, 82 insertions(+), 19 deletions(-) diff --git a/code/lib/cli-storybook/src/codemod/csf-factories.ts b/code/lib/cli-storybook/src/codemod/csf-factories.ts index da2ca82aa81f..a8c167d744ea 100644 --- a/code/lib/cli-storybook/src/codemod/csf-factories.ts +++ b/code/lib/cli-storybook/src/codemod/csf-factories.ts @@ -1,6 +1,7 @@ import { type JsPackageManager, syncStorybookAddons } from 'storybook/internal/common'; import prompts from 'prompts'; +import { dedent } from 'ts-dedent'; import { runCodemod } from '../automigrate/codemod'; import { getFrameworkPackageName } from '../automigrate/helpers/mainConfigFile'; @@ -13,19 +14,26 @@ export const logger = console; async function runStoriesCodemod(options: { dryRun: boolean | undefined; packageManager: JsPackageManager; + useImportsMap: boolean; + previewConfigPath: string; }) { - const { dryRun, packageManager } = options; + const { dryRun, packageManager, ...codemodOptions } = options; try { - let globString = 'src/stories/*.stories.*'; + let globString = 'src/**/*.stories.*'; if (!process.env.IN_STORYBOOK_SANDBOX) { logger.log('Please enter the glob for your stories to migrate'); globString = ( - await prompts({ - type: 'text', - name: 'glob', - message: 'glob', - initial: globString, - }) + await prompts( + { + type: 'text', + name: 'glob', + message: 'glob', + initial: globString, + }, + { + onCancel: () => process.exit(0), + } + ) ).glob; } @@ -39,7 +47,9 @@ async function runStoriesCodemod(options: { ignoreError: true, }); - await runCodemod(globString, storyToCsfFactory, { dryRun }); + await runCodemod(globString, (info) => storyToCsfFactory(info, codemodOptions), { + dryRun, + }); } catch (err: any) { console.log('err message', err.message); if (err.message === 'No files matched') { @@ -62,15 +72,47 @@ export const csfFactories: CommandFix = { packageJson, packageManager, }) { - logger.log(`Adding imports map in ${packageManager.packageJsonPath()}`); - packageJson.imports = { - ...packageJson.imports, - // @ts-expect-error we need to upgrade type-fest - '#*': ['./*', './*.ts', './*.tsx', './*.js', './*.jsx'], - }; - await packageManager.writePackageJson(packageJson); + // prompt whether the user wants to use imports map + logger.log( + dedent`The CSF factories format relies on having an import map in your package.json so that it's more convenient to import the preview config in your stories. + + Here's how it looks like: + - imports map: \`import preview from '#.storybook/preview'\` + - relative map: \`import preview from '../../.storybook/preview'\` + ` + ); + const { useImportsMap } = await prompts( + { + type: 'select', + name: 'useImportsMap', + message: 'Which would you like to use?', + choices: [ + { title: 'Imports map', value: true }, + { title: 'Relative imports', value: false }, + ], + initial: 0, + }, + { + onCancel: () => process.exit(0), + } + ); - await runStoriesCodemod({ dryRun, packageManager }); + if (useImportsMap) { + logger.log(`Adding imports map in ${packageManager.packageJsonPath()}`); + packageJson.imports = { + ...packageJson.imports, + // @ts-expect-error we need to upgrade type-fest + '#*': ['./*', './*.ts', './*.tsx', './*.js', './*.jsx'], + }; + await packageManager.writePackageJson(packageJson); + } + + await runStoriesCodemod({ + dryRun, + packageManager, + useImportsMap, + previewConfigPath: previewConfigPath!, + }); logger.log('Applying codemod on your main config...'); const frameworkPackage = diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts index f18a37791786..4ddc8e41fc11 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts @@ -2,6 +2,8 @@ import { types as t, traverse } from 'storybook/internal/babel'; import { isValidPreviewPath, loadCsf, printCsf } from 'storybook/internal/csf-tools'; +import { dirname, relative } from 'path'; + import type { FileInfo } from '../../automigrate/codemod'; import { logger } from '../csf-factories'; import { cleanupTypeImports } from './csf-factories-utils'; @@ -20,7 +22,12 @@ const typesDisallowList = [ 'ComponentMeta', ]; -export async function storyToCsfFactory(info: FileInfo) { +type Options = { previewConfigPath: string; useImportsMap: boolean }; + +export async function storyToCsfFactory( + info: FileInfo, + { previewConfigPath, useImportsMap }: Options +) { const csf = loadCsf(info.source, { makeTitle: () => 'FIXME' }); try { csf.parse(); @@ -46,6 +53,20 @@ export async function storyToCsfFactory(info: FileInfo) { n.declarations.some((declaration) => t.isIdentifier(declaration.id, { name: 'preview' })) ); + let previewPath = '#.storybook/preview'; + if (!useImportsMap) { + // calculate relative path from info.path to previewConfigPath + const relativePath = relative(dirname(info.path), previewConfigPath) + // Convert Windows backslashes to forward slashes if present + .replace(/\\/g, '/') + // Remove .ts or .js extension + .replace(/\.(ts|js)x?$/, ''); + + // If the path doesn't start with . or .., add ./ + const normalizedPath = relativePath.startsWith('.') ? relativePath : `./${relativePath}`; + previewPath = normalizedPath; + } + let sbConfigImportName = hasRootLevelConfig ? 'storybookPreview' : 'preview'; const sbConfigImportSpecifier = t.importDefaultSpecifier(t.identifier(sbConfigImportName)); @@ -228,7 +249,7 @@ export async function storyToCsfFactory(info: FileInfo) { if (hasMeta && !foundConfigImport) { const configImport = t.importDeclaration( [t.importDefaultSpecifier(t.identifier(sbConfigImportName))], - t.stringLiteral('#.storybook/preview') + t.stringLiteral(previewPath) ); programNode.body.unshift(configImport); } From 1b9a3ea10d50daf07886f43e6bc6b1cb2fae1278 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 4 Feb 2025 15:25:19 +0100 Subject: [PATCH 132/144] tweaks --- .../src/codemod/csf-factories.ts | 63 +++++++++++-------- .../codemod/helpers/story-to-csf-factory.ts | 6 +- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/code/lib/cli-storybook/src/codemod/csf-factories.ts b/code/lib/cli-storybook/src/codemod/csf-factories.ts index a8c167d744ea..f987a6619ba4 100644 --- a/code/lib/cli-storybook/src/codemod/csf-factories.ts +++ b/code/lib/cli-storybook/src/codemod/csf-factories.ts @@ -14,7 +14,7 @@ export const logger = console; async function runStoriesCodemod(options: { dryRun: boolean | undefined; packageManager: JsPackageManager; - useImportsMap: boolean; + useSubPathImports: boolean; previewConfigPath: string; }) { const { dryRun, packageManager, ...codemodOptions } = options; @@ -72,32 +72,45 @@ export const csfFactories: CommandFix = { packageJson, packageManager, }) { - // prompt whether the user wants to use imports map - logger.log( - dedent`The CSF factories format relies on having an import map in your package.json so that it's more convenient to import the preview config in your stories. + let useSubPathImports = true; + if (!process.env.IN_STORYBOOK_SANDBOX) { + // prompt whether the user wants to use imports map + logger.log( + dedent` + The CSF factories format relies on subpath imports (the imports map in your \`package.json\`), which makes it more convenient to import the preview config in your stories. + + We recommend using the **imports map** option, as it's the TypeScript standard for module resolution. + However, please note that this might not work if you have an outdated tsconfig, use custom paths or type alias plugins configured in your project. + + More info: https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules#subpath-imports + + As we modify your story files, we can provide two options of imports: - Here's how it looks like: - - imports map: \`import preview from '#.storybook/preview'\` - - relative map: \`import preview from '../../.storybook/preview'\` + - **Subpath imports (recommended):** \`import preview from '#.storybook/preview'\` + - **Relative imports (fallback):** \`import preview from '../../.storybook/preview'\` ` - ); - const { useImportsMap } = await prompts( - { - type: 'select', - name: 'useImportsMap', - message: 'Which would you like to use?', - choices: [ - { title: 'Imports map', value: true }, - { title: 'Relative imports', value: false }, - ], - initial: 0, - }, - { - onCancel: () => process.exit(0), - } - ); + ); + useSubPathImports = ( + await prompts( + { + type: 'select', + name: 'useSubPathImports', + message: 'Which would you like to use?', + choices: [ + { title: 'Subpath imports', value: true }, + { title: 'Relative imports', value: false }, + ], + initial: 0, + }, + { + onCancel: () => process.exit(0), + } + ) + ).useSubPathImports; + logger.log(); + } - if (useImportsMap) { + if (useSubPathImports) { logger.log(`Adding imports map in ${packageManager.packageJsonPath()}`); packageJson.imports = { ...packageJson.imports, @@ -110,7 +123,7 @@ export const csfFactories: CommandFix = { await runStoriesCodemod({ dryRun, packageManager, - useImportsMap, + useSubPathImports, previewConfigPath: previewConfigPath!, }); diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts index 4ddc8e41fc11..bde2092e5981 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts @@ -22,11 +22,11 @@ const typesDisallowList = [ 'ComponentMeta', ]; -type Options = { previewConfigPath: string; useImportsMap: boolean }; +type Options = { previewConfigPath: string; useSubPathImports: boolean }; export async function storyToCsfFactory( info: FileInfo, - { previewConfigPath, useImportsMap }: Options + { previewConfigPath, useSubPathImports }: Options ) { const csf = loadCsf(info.source, { makeTitle: () => 'FIXME' }); try { @@ -54,7 +54,7 @@ export async function storyToCsfFactory( ); let previewPath = '#.storybook/preview'; - if (!useImportsMap) { + if (!useSubPathImports) { // calculate relative path from info.path to previewConfigPath const relativePath = relative(dirname(info.path), previewConfigPath) // Convert Windows backslashes to forward slashes if present From 359ec9c409821b3c384d456329e2da17edb337a4 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 4 Feb 2025 16:30:46 +0100 Subject: [PATCH 133/144] fix codemod --- .../src/codemod/helpers/config-to-csf-factory.test.ts | 10 ++++++++++ .../src/codemod/helpers/config-to-csf-factory.ts | 5 +++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts index 6be5462c51ee..0da113d41a4d 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts @@ -119,6 +119,16 @@ describe('main/preview codemod: general parsing functionality', () => { ).toHaveLength(1); }); + it('should leave already transformed code as is', async () => { + const original = dedent` + import { defineMain } from '@storybook/react-vite/node'; + + export default defineMain({}); + `; + const transformed = await transform(original); + expect(transformed).toEqual(original); + }); + it('should remove legacy main config type imports', async () => { await expect( transform(dedent` diff --git a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts index 34d62b3c7f2a..734435af3377 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts @@ -131,8 +131,9 @@ export async function configToCsfFactory( const existingImport = programNode.body.find( (node) => t.isImportDeclaration(node) && - node.source.value === configImport.source.value && - !node.importKind + !t.isImportSpecifier(node) && + node.importKind !== 'type' && + node.source.value === configImport.source.value ); if (existingImport && t.isImportDeclaration(existingImport)) { From 9760a3ec51baff30e5cba16d102d6fe4cd8835f0 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 5 Feb 2025 10:26:12 +0100 Subject: [PATCH 134/144] Update code/lib/cli-storybook/src/codemod/csf-factories.ts Co-authored-by: Kyle Gach --- code/lib/cli-storybook/src/codemod/csf-factories.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/code/lib/cli-storybook/src/codemod/csf-factories.ts b/code/lib/cli-storybook/src/codemod/csf-factories.ts index f987a6619ba4..3f40623a6165 100644 --- a/code/lib/cli-storybook/src/codemod/csf-factories.ts +++ b/code/lib/cli-storybook/src/codemod/csf-factories.ts @@ -77,14 +77,13 @@ export const csfFactories: CommandFix = { // prompt whether the user wants to use imports map logger.log( dedent` - The CSF factories format relies on subpath imports (the imports map in your \`package.json\`), which makes it more convenient to import the preview config in your stories. + The CSF factories format benefits from subpath imports (the imports property in your \`package.json\`), which is a node standard for module resolution. This makes it more convenient to import the preview config in your story files. - We recommend using the **imports map** option, as it's the TypeScript standard for module resolution. - However, please note that this might not work if you have an outdated tsconfig, use custom paths or type alias plugins configured in your project. + However, please note that this might not work if you have an outdated tsconfig, use custom paths, or have type alias plugins configured in your project. More info: https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules#subpath-imports - As we modify your story files, we can provide two options of imports: + As we modify your story files, we can create two types of imports: - **Subpath imports (recommended):** \`import preview from '#.storybook/preview'\` - **Relative imports (fallback):** \`import preview from '../../.storybook/preview'\` From 04a004bfe401d4e61d798505332eeafe37e452d2 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 5 Feb 2025 13:10:04 +0100 Subject: [PATCH 135/144] improve codemod to be able to run it multiple times --- .../src/codemod/csf-factories.ts | 34 +++++++---- .../codemod/helpers/config-to-csf-factory.ts | 1 - .../helpers/story-to-csf-factory.test.ts | 56 ++++++++++++++++++- .../codemod/helpers/story-to-csf-factory.ts | 53 +++++++++++------- code/lib/cli-storybook/src/util.ts | 4 ++ 5 files changed, 114 insertions(+), 34 deletions(-) create mode 100644 code/lib/cli-storybook/src/util.ts diff --git a/code/lib/cli-storybook/src/codemod/csf-factories.ts b/code/lib/cli-storybook/src/codemod/csf-factories.ts index 3f40623a6165..8f7761e94a5d 100644 --- a/code/lib/cli-storybook/src/codemod/csf-factories.ts +++ b/code/lib/cli-storybook/src/codemod/csf-factories.ts @@ -1,11 +1,13 @@ import { type JsPackageManager, syncStorybookAddons } from 'storybook/internal/common'; +import picocolors from 'picocolors'; import prompts from 'prompts'; import { dedent } from 'ts-dedent'; import { runCodemod } from '../automigrate/codemod'; import { getFrameworkPackageName } from '../automigrate/helpers/mainConfigFile'; import type { CommandFix } from '../automigrate/types'; +import { printBoxedMessage } from '../util'; import { configToCsfFactory } from './helpers/config-to-csf-factory'; import { storyToCsfFactory } from './helpers/story-to-csf-factory'; @@ -37,7 +39,7 @@ async function runStoriesCodemod(options: { ).glob; } - logger.log('Applying codemod on your stories, this might take some time...'); + logger.log('\n🛠️ Applying codemod on your stories, this might take some time...'); // TODO: Move the csf-2-to-3 codemod into automigrations await packageManager.executeCommand({ @@ -76,18 +78,18 @@ export const csfFactories: CommandFix = { if (!process.env.IN_STORYBOOK_SANDBOX) { // prompt whether the user wants to use imports map logger.log( - dedent` + printBoxedMessage(dedent` The CSF factories format benefits from subpath imports (the imports property in your \`package.json\`), which is a node standard for module resolution. This makes it more convenient to import the preview config in your story files. However, please note that this might not work if you have an outdated tsconfig, use custom paths, or have type alias plugins configured in your project. - More info: https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules#subpath-imports + More info: ${picocolors.yellow('https://storybook.js.org/docs/api/csf/csf-factories#subpath-imports')} As we modify your story files, we can create two types of imports: - - **Subpath imports (recommended):** \`import preview from '#.storybook/preview'\` - - **Relative imports (fallback):** \`import preview from '../../.storybook/preview'\` - ` + - ${picocolors.bold('Subpath imports (recommended):')} ${picocolors.cyan("`import preview from '#.storybook/preview'`")} + - ${picocolors.bold('Relative imports:')} ${picocolors.cyan("`import preview from '../../.storybook/preview'`")} + `) ); useSubPathImports = ( await prompts( @@ -106,11 +108,10 @@ export const csfFactories: CommandFix = { } ) ).useSubPathImports; - logger.log(); } - if (useSubPathImports) { - logger.log(`Adding imports map in ${packageManager.packageJsonPath()}`); + if (useSubPathImports && !packageJson.imports?.['#*']) { + logger.log(`🗺️ Adding imports map in ${picocolors.cyan(packageManager.packageJsonPath())}`); packageJson.imports = { ...packageJson.imports, // @ts-expect-error we need to upgrade type-fest @@ -126,18 +127,29 @@ export const csfFactories: CommandFix = { previewConfigPath: previewConfigPath!, }); - logger.log('Applying codemod on your main config...'); + logger.log('\n🛠️ Applying codemod on your main config...'); const frameworkPackage = getFrameworkPackageName(mainConfig) || '@storybook/your-framework-here'; await runCodemod(mainConfigPath, (fileInfo) => configToCsfFactory(fileInfo, { configType: 'main', frameworkPackage }, { dryRun }) ); - logger.log('Applying codemod on your preview config...'); + logger.log('\n🛠️ Applying codemod on your preview config...'); await runCodemod(previewConfigPath, (fileInfo) => configToCsfFactory(fileInfo, { configType: 'preview', frameworkPackage }, { dryRun }) ); await syncStorybookAddons(mainConfig, previewConfigPath!); + + logger.log( + printBoxedMessage( + dedent` + You can now run Storybook with the new CSF factories format. + + For more info, check out the docs: + ${picocolors.yellow('https://storybook.js.org/docs/api/csf/csf-factories')} + ` + ) + ); }, }; diff --git a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts index 734435af3377..9cf2bef83443 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts @@ -131,7 +131,6 @@ export async function configToCsfFactory( const existingImport = programNode.body.find( (node) => t.isImportDeclaration(node) && - !t.isImportSpecifier(node) && node.importKind !== 'type' && node.source.value === configImport.source.value ); diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts index cd526f3c17fd..12e920a772fd 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts @@ -1,7 +1,8 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { formatFileContent } from '@storybook/core/common'; +import path from 'path'; import { dedent } from 'ts-dedent'; import { storyToCsfFactory } from './story-to-csf-factory'; @@ -15,7 +16,10 @@ describe('stories codemod', () => { const transform = async (source: string) => formatFileContent( 'Component.stories.tsx', - await storyToCsfFactory({ source, path: 'Component.stories.tsx' }) + await storyToCsfFactory( + { source, path: 'Component.stories.tsx' }, + { previewConfigPath: '#.storybook/preview', useSubPathImports: true } + ) ); describe('javascript', () => { it('should wrap const declared meta', async () => { @@ -255,6 +259,54 @@ describe('stories codemod', () => { // expect(transformed).toContain('C = meta.story'); }); + it('converts the preview import path based on useSubPathImports flag', async () => { + const relativeMock = vi.spyOn(path, 'relative').mockReturnValue('../../preview.ts'); + + try { + await expect( + formatFileContent( + 'Component.stories.tsx', + await storyToCsfFactory( + { + source: dedent` + import preview, { extra } from '../../../.storybook/preview'; + export default {}; + `, + path: 'Component.stories.tsx', + }, + { previewConfigPath: '#.storybook/preview', useSubPathImports: true } + ) + ) + ).resolves.toMatchInlineSnapshot(` + import preview, { extra } from '#.storybook/preview'; + + const meta = preview.meta({}); + `); + + await expect( + formatFileContent( + 'Component.stories.tsx', + await storyToCsfFactory( + { + source: dedent` + import preview, { extra } from '#.storybook/preview'; + export default {}; + `, + path: 'Component.stories.tsx', + }, + { previewConfigPath: '#.storybook/preview', useSubPathImports: false } + ) + ) + ).resolves.toMatchInlineSnapshot(` + import preview, { extra } from '../../preview'; + + const meta = preview.meta({}); + `); + } finally { + relativeMock.mockRestore(); + } + }); + it('converts CSF1 into CSF4 with render', async () => { await expect( transform(dedent` diff --git a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts index bde2092e5981..a419dfb53fce 100644 --- a/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts +++ b/code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts @@ -2,7 +2,7 @@ import { types as t, traverse } from 'storybook/internal/babel'; import { isValidPreviewPath, loadCsf, printCsf } from 'storybook/internal/csf-tools'; -import { dirname, relative } from 'path'; +import path from 'path'; import type { FileInfo } from '../../automigrate/codemod'; import { logger } from '../csf-factories'; @@ -44,7 +44,7 @@ export async function storyToCsfFactory( * `import preview from '#.storybook/preview'`; */ const programNode = csf._ast.program; - let foundConfigImport = false; + let previewImport: t.ImportDeclaration | undefined; // Check if a root-level constant named 'preview' exists const hasRootLevelConfig = programNode.body.some( @@ -55,16 +55,20 @@ export async function storyToCsfFactory( let previewPath = '#.storybook/preview'; if (!useSubPathImports) { - // calculate relative path from info.path to previewConfigPath - const relativePath = relative(dirname(info.path), previewConfigPath) - // Convert Windows backslashes to forward slashes if present - .replace(/\\/g, '/') - // Remove .ts or .js extension - .replace(/\.(ts|js)x?$/, ''); - - // If the path doesn't start with . or .., add ./ - const normalizedPath = relativePath.startsWith('.') ? relativePath : `./${relativePath}`; - previewPath = normalizedPath; + // calculate relative path from story file to preview file + const relativePath = path.relative(path.dirname(info.path), previewConfigPath); + const { dir, name } = path.parse(relativePath); + + // Construct the path manually and replace Windows backslashes + previewPath = `${dir ? `${dir}/` : ''}${name}`; + + // account for stories in the same path as preview file + if (!previewPath.startsWith('.')) { + previewPath = `./${previewPath}`; + } + + // Convert Windows backslashes to forward slashes + previewPath = previewPath.replace(/\\/g, '/'); } let sbConfigImportName = hasRootLevelConfig ? 'storybookPreview' : 'preview'; @@ -83,7 +87,7 @@ export async function storyToCsfFactory( sbConfigImportName = defaultImportSpecifier.local.name; } - foundConfigImport = true; + previewImport = node; } }); @@ -92,7 +96,7 @@ export async function storyToCsfFactory( // @TODO: Support unconventional formats: // `export function Story() { };` and `export { Story }; // These are not part of csf._storyExports but rather csf._storyStatements and are tricky to support. - Object.entries(csf._storyExports).forEach(([key, decl]) => { + Object.entries(csf._storyExports).forEach(([_key, decl]) => { const id = decl.id; const declarator = decl as t.VariableDeclarator; let init = t.isVariableDeclarator(declarator) ? declarator.init : undefined; @@ -140,15 +144,15 @@ export async function storyToCsfFactory( // For each story, replace any reference of story reuse e.g. // Story.args -> Story.input.args traverse(csf._ast, { - Identifier(path) { - const binding = path.scope.getBinding(path.node.name); + Identifier(nodePath) { + const binding = nodePath.scope.getBinding(nodePath.node.name); // Check if the identifier corresponds to a story export if (binding && storyExportDecls.has(binding.identifier.name)) { - const parent = path.parent; + const parent = nodePath.parent; // Skip declarations (e.g., `const Story = {};`) - if (t.isVariableDeclarator(parent) && parent.id === path.node) { + if (t.isVariableDeclarator(parent) && parent.id === nodePath.node) { return; } @@ -177,7 +181,9 @@ export async function storyToCsfFactory( try { // Replace the identifier with `Story.input` - path.replaceWith(t.memberExpression(t.identifier(path.node.name), t.identifier('input'))); + nodePath.replaceWith( + t.memberExpression(t.identifier(nodePath.node.name), t.identifier('input')) + ); } catch (err: any) { // This is a tough one to support, we just skip for now. // Relates to `Stories.Story.args` where Stories is coming from another file. We can't know whether it should be transformed or not. @@ -246,7 +252,14 @@ export async function storyToCsfFactory( } } - if (hasMeta && !foundConfigImport) { + if (previewImport) { + // If there is alerady an import, just update the path. This is useful for users + // who rerun the codemod to change the preview import to use (or not) subpaths + if (previewImport.source.value !== previewPath) { + previewImport.source = t.stringLiteral(previewPath); + } + } else if (hasMeta) { + // If the import doesn't exist, create a new one const configImport = t.importDeclaration( [t.importDefaultSpecifier(t.identifier(sbConfigImportName))], t.stringLiteral(previewPath) diff --git a/code/lib/cli-storybook/src/util.ts b/code/lib/cli-storybook/src/util.ts new file mode 100644 index 000000000000..f8fa3f3d6f3a --- /dev/null +++ b/code/lib/cli-storybook/src/util.ts @@ -0,0 +1,4 @@ +import boxen, { type Options } from 'boxen'; + +export const printBoxedMessage = (message: string, style?: Options) => + boxen(message, { borderStyle: 'round', padding: 1, borderColor: '#F1618C', ...style }); From eab492651cc48750a035b7f16b04aa1dfdfd6e6d Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 5 Feb 2025 14:05:40 +0100 Subject: [PATCH 136/144] fix type issues --- code/addons/essentials/src/backgrounds/manager.ts | 1 - code/addons/essentials/src/docs/manager.ts | 1 - code/addons/essentials/src/outline/manager.ts | 1 - code/addons/essentials/src/viewport/manager.ts | 1 - code/addons/jest/src/types.ts | 2 +- .../react/src/__test__/portable-stories-factory.test.tsx | 2 +- code/renderers/react/template/stories/csf4.stories.tsx | 1 + 7 files changed, 3 insertions(+), 6 deletions(-) diff --git a/code/addons/essentials/src/backgrounds/manager.ts b/code/addons/essentials/src/backgrounds/manager.ts index 9da6a432be39..930d5ee38181 100644 --- a/code/addons/essentials/src/backgrounds/manager.ts +++ b/code/addons/essentials/src/backgrounds/manager.ts @@ -1,2 +1 @@ -// @ts-expect-error (no types needed for this) export * from '@storybook/addon-backgrounds/manager'; diff --git a/code/addons/essentials/src/docs/manager.ts b/code/addons/essentials/src/docs/manager.ts index 6101f7d79261..9f14a38904c4 100644 --- a/code/addons/essentials/src/docs/manager.ts +++ b/code/addons/essentials/src/docs/manager.ts @@ -1,2 +1 @@ -// @ts-expect-error (no types needed for this) export * from '@storybook/addon-docs/manager'; diff --git a/code/addons/essentials/src/outline/manager.ts b/code/addons/essentials/src/outline/manager.ts index d3a29db6d98b..9f46ef8cbae4 100644 --- a/code/addons/essentials/src/outline/manager.ts +++ b/code/addons/essentials/src/outline/manager.ts @@ -1,2 +1 @@ -// @ts-expect-error (no types needed for this) export * from '@storybook/addon-outline/manager'; diff --git a/code/addons/essentials/src/viewport/manager.ts b/code/addons/essentials/src/viewport/manager.ts index 48bc7a850de6..ccbe283d4101 100644 --- a/code/addons/essentials/src/viewport/manager.ts +++ b/code/addons/essentials/src/viewport/manager.ts @@ -1,2 +1 @@ -// @ts-expect-error (no types needed for this) export * from '@storybook/addon-viewport/manager'; diff --git a/code/addons/jest/src/types.ts b/code/addons/jest/src/types.ts index f88cfdc861f6..998c0254d83f 100644 --- a/code/addons/jest/src/types.ts +++ b/code/addons/jest/src/types.ts @@ -4,5 +4,5 @@ export interface JestParameters { * * @see https://github.com/storybookjs/storybook/blob/next/code/addons/jest/README.md#usage */ - jest?: string | string[] | { jest: { disabled: true } }; + jest?: string | string[] | { disabled: true }; } diff --git a/code/renderers/react/src/__test__/portable-stories-factory.test.tsx b/code/renderers/react/src/__test__/portable-stories-factory.test.tsx index 6accc1bf88bc..f8614b4344d3 100644 --- a/code/renderers/react/src/__test__/portable-stories-factory.test.tsx +++ b/code/renderers/react/src/__test__/portable-stories-factory.test.tsx @@ -138,7 +138,7 @@ describe('projectAnnotations', () => { addonActionsPreview as ProjectAnnotations ); - // @ts-expect-error TODO: add a way to provide custom args/argTypes + // TODO: add a way to provide custom args/argTypes, right now it's type any expect(Story.args.someActionArg).toHaveProperty('isAction', true); }); }); diff --git a/code/renderers/react/template/stories/csf4.stories.tsx b/code/renderers/react/template/stories/csf4.stories.tsx index 05c06367c297..40b9e7898d4d 100644 --- a/code/renderers/react/template/stories/csf4.stories.tsx +++ b/code/renderers/react/template/stories/csf4.stories.tsx @@ -2,6 +2,7 @@ import preview from '#.storybook/preview'; const meta = preview.meta({ + // @ts-expect-error fix globalThis.Components type not existing later component: globalThis.Components.Button, args: { label: 'Hello world!', From 4820815f76e4a3b8b971374290959e44d5bd97b8 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 5 Feb 2025 14:11:35 +0100 Subject: [PATCH 137/144] clarify message --- code/lib/cli-storybook/src/codemod/csf-factories.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/cli-storybook/src/codemod/csf-factories.ts b/code/lib/cli-storybook/src/codemod/csf-factories.ts index 8f7761e94a5d..d59407bbf9f4 100644 --- a/code/lib/cli-storybook/src/codemod/csf-factories.ts +++ b/code/lib/cli-storybook/src/codemod/csf-factories.ts @@ -81,7 +81,7 @@ export const csfFactories: CommandFix = { printBoxedMessage(dedent` The CSF factories format benefits from subpath imports (the imports property in your \`package.json\`), which is a node standard for module resolution. This makes it more convenient to import the preview config in your story files. - However, please note that this might not work if you have an outdated tsconfig, use custom paths, or have type alias plugins configured in your project. + However, please note that this might not work if you have an outdated tsconfig, use custom paths, or have type alias plugins configured in your project. You can always rerun this codemod and select another option to update your code later. More info: ${picocolors.yellow('https://storybook.js.org/docs/api/csf/csf-factories#subpath-imports')} From 2e92d97548c47fb8cfe0847067534a8b84509a91 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 5 Feb 2025 14:50:10 +0100 Subject: [PATCH 138/144] fix lint --- scripts/tasks/sandbox.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/tasks/sandbox.ts b/scripts/tasks/sandbox.ts index 45c82f80fe83..69c6cf29c658 100644 --- a/scripts/tasks/sandbox.ts +++ b/scripts/tasks/sandbox.ts @@ -149,7 +149,7 @@ export const sandbox: Task = { await runMigrations(details, options); await extendPreview(details, options); - + const { JsPackageManagerFactory } = await import('../../code/core/src/common'); const packageManager = JsPackageManagerFactory.getPackageManager({}, details.sandboxDir); From 8babc1dd7414ecb32a1abc295f1dca8f588b4a72 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 5 Feb 2025 14:51:42 +0100 Subject: [PATCH 139/144] increase resource of knip step --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ac31411a5af7..77dd22f22bb9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -195,7 +195,7 @@ jobs: - cancel-workflow-on-failure knip: executor: - class: medium + class: medium+ name: sb_node_22_classic steps: - git-shallow-clone/checkout_advanced: From 08bb05365c92bfb0dcaa724cf99af011c1367743 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 5 Feb 2025 16:03:17 +0100 Subject: [PATCH 140/144] remove logs --- code/lib/cli-storybook/src/codemod/csf-factories.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/lib/cli-storybook/src/codemod/csf-factories.ts b/code/lib/cli-storybook/src/codemod/csf-factories.ts index d59407bbf9f4..eeae5bdab993 100644 --- a/code/lib/cli-storybook/src/codemod/csf-factories.ts +++ b/code/lib/cli-storybook/src/codemod/csf-factories.ts @@ -53,9 +53,7 @@ async function runStoriesCodemod(options: { dryRun, }); } catch (err: any) { - console.log('err message', err.message); if (err.message === 'No files matched') { - console.log('going to run again'); await runStoriesCodemod(options); } else { throw err; From e21a07bc433dd9202fed654cdb3aaf520eba3d73 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 5 Feb 2025 16:40:53 +0100 Subject: [PATCH 141/144] fix sandbox command --- scripts/tasks/sandbox.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/tasks/sandbox.ts b/scripts/tasks/sandbox.ts index 69c6cf29c658..f914bbe2f481 100644 --- a/scripts/tasks/sandbox.ts +++ b/scripts/tasks/sandbox.ts @@ -146,16 +146,16 @@ export const sandbox: Task = { await setImportMap(details.sandboxDir); - await runMigrations(details, options); - - await extendPreview(details, options); - const { JsPackageManagerFactory } = await import('../../code/core/src/common'); const packageManager = JsPackageManagerFactory.getPackageManager({}, details.sandboxDir); await packageManager.installDependencies(); + await runMigrations(details, options); + + await extendPreview(details, options); + logger.info(`✅ Storybook sandbox created at ${details.sandboxDir}`); }, }; From de13df08c3d9e5abe1ad9cf78c5d20d23d25c623 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 5 Feb 2025 17:34:17 +0100 Subject: [PATCH 142/144] fix vite types issue --- code/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/yarn.lock b/code/yarn.lock index 583ce7e5bdb3..f01845e69a31 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -31483,8 +31483,8 @@ __metadata: linkType: hard "vite@npm:^4.0.0, vite@npm:^4.0.4": - version: 4.5.1 - resolution: "vite@npm:4.5.1" + version: 4.5.9 + resolution: "vite@npm:4.5.9" dependencies: esbuild: "npm:^0.18.10" fsevents: "npm:~2.3.2" @@ -31518,7 +31518,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/352a94b13f793e4bcbc424d680a32507343223eeda8917fde0f23c1fa1ba3db7c806dade8461ca5cfb270154ddb8895a219fdd4384519fe9b8e46d1cf491a890 + checksum: 10c0/d51b9da32fddc6079333a16306c4c70d6ea6b253267931b5cd5d1c521bcfbee926297dc6878da79b0f1e058b7eef72555226be701fae376c2dfae9f83bc5699a languageName: node linkType: hard From c7beffeee85a8740d9b630d5ce98de20f9edfe85 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Mon, 10 Feb 2025 14:42:09 +0100 Subject: [PATCH 143/144] Merge conflicts --- code/core/src/csf/csf-factories.ts | 96 +++++++++++++++++++ code/core/src/csf/index.ts | 1 + .../modules/preview-web/render/mount-utils.ts | 3 +- code/lib/cli/package.json | 5 - .../portable-stories-factory.test.tsx | 2 +- .../react/src/csf-factories.test.tsx | 2 +- 6 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 code/core/src/csf/csf-factories.ts diff --git a/code/core/src/csf/csf-factories.ts b/code/core/src/csf/csf-factories.ts new file mode 100644 index 000000000000..0c0ea4cbe21a --- /dev/null +++ b/code/core/src/csf/csf-factories.ts @@ -0,0 +1,96 @@ +/* eslint-disable no-underscore-dangle */ +import type { + Args, + ComponentAnnotations, + NormalizedComponentAnnotations, + NormalizedProjectAnnotations, + NormalizedStoryAnnotations, + ProjectAnnotations, + Renderer, + StoryAnnotations, +} from '@storybook/core/types'; + +import { composeConfigs, normalizeProjectAnnotations } from '@storybook/core/preview-api'; + +export interface Preview { + readonly _tag: 'Preview'; + input: ProjectAnnotations; + composed: NormalizedProjectAnnotations; + + meta(input: ComponentAnnotations): Meta; +} + +export function definePreview( + preview: Preview['input'] +): Preview { + return { + _tag: 'Preview', + input: preview, + get composed() { + const { addons, ...rest } = preview; + return normalizeProjectAnnotations(composeConfigs([...(addons ?? []), rest])); + }, + meta(meta: ComponentAnnotations) { + return defineMeta(meta, this); + }, + }; +} + +export function isPreview(input: unknown): input is Preview { + return input != null && typeof input === 'object' && '_tag' in input && input?._tag === 'Preview'; +} + +export interface Meta { + readonly _tag: 'Meta'; + input: ComponentAnnotations; + composed: NormalizedComponentAnnotations; + preview: Preview; + + story(input: ComponentAnnotations): Story; +} + +export function isMeta(input: unknown): input is Meta { + return input != null && typeof input === 'object' && '_tag' in input && input?._tag === 'Meta'; +} + +function defineMeta( + input: ComponentAnnotations, + preview: Preview +): Meta { + return { + _tag: 'Meta', + input, + preview, + get composed(): never { + throw new Error('Not implemented'); + }, + story(story: StoryAnnotations) { + return defineStory(story, this); + }, + }; +} + +export interface Story { + readonly _tag: 'Story'; + input: StoryAnnotations; + composed: NormalizedStoryAnnotations; + meta: Meta; +} + +function defineStory( + input: ComponentAnnotations, + meta: Meta +): Story { + return { + _tag: 'Story', + input, + meta, + get composed(): never { + throw new Error('Not implemented'); + }, + }; +} + +export function isStory(input: unknown): input is Story { + return input != null && typeof input === 'object' && '_tag' in input && input?._tag === 'Story'; +} diff --git a/code/core/src/csf/index.ts b/code/core/src/csf/index.ts index 4c8bc2b44a01..4997de1b3705 100644 --- a/code/core/src/csf/index.ts +++ b/code/core/src/csf/index.ts @@ -88,3 +88,4 @@ export const combineTags = (...tags: string[]): string[] => { export { includeConditionalArg } from './includeConditionalArg'; export * from './story'; +export * from './csf-factories'; diff --git a/code/core/src/preview-api/modules/preview-web/render/mount-utils.ts b/code/core/src/preview-api/modules/preview-web/render/mount-utils.ts index 00510d0f5edb..23fee127a9df 100644 --- a/code/core/src/preview-api/modules/preview-web/render/mount-utils.ts +++ b/code/core/src/preview-api/modules/preview-web/render/mount-utils.ts @@ -1,9 +1,10 @@ // Inspired by Vitest fixture implementation: // https://github.com/vitest-dev/vitest/blob/200a4349a2f85686bc7005dce686d9d1b48b84d2/packages/runner/src/fixture.ts +import type { PlayFunction } from '@storybook/core/csf'; import { type PreparedStory, type Renderer } from '@storybook/core/types'; export function mountDestructured( - playFunction: PreparedStory['playFunction'] + playFunction?: PlayFunction ): boolean { return playFunction != null && getUsedProps(playFunction).includes('mount'); } diff --git a/code/lib/cli/package.json b/code/lib/cli/package.json index 854ef75660cb..c1d976277461 100644 --- a/code/lib/cli/package.json +++ b/code/lib/cli/package.json @@ -198,11 +198,6 @@ }, "./internal/preview/runtime": { "import": "./core/preview/runtime.js" - }, - "./internal/csf": { - "types": "./core/csf/index.d.ts", - "import": "./core/csf/index.js", - "require": "./core/csf/index.cjs" } }, "main": "dist/index.cjs", diff --git a/code/renderers/react/src/__test__/portable-stories-factory.test.tsx b/code/renderers/react/src/__test__/portable-stories-factory.test.tsx index f8614b4344d3..ce1ba2f74c9a 100644 --- a/code/renderers/react/src/__test__/portable-stories-factory.test.tsx +++ b/code/renderers/react/src/__test__/portable-stories-factory.test.tsx @@ -6,9 +6,9 @@ import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; import React from 'react'; +import type { ProjectAnnotations } from 'storybook/internal/csf'; import { addons } from 'storybook/internal/preview-api'; -import type { ProjectAnnotations } from '@storybook/csf'; import type { Meta, ReactRenderer } from '@storybook/react'; import * as addonActionsPreview from '@storybook/addon-actions/preview'; diff --git a/code/renderers/react/src/csf-factories.test.tsx b/code/renderers/react/src/csf-factories.test.tsx index ccaf6d405421..27fe0515298c 100644 --- a/code/renderers/react/src/csf-factories.test.tsx +++ b/code/renderers/react/src/csf-factories.test.tsx @@ -6,9 +6,9 @@ import { expect, test } from 'vitest'; import type { KeyboardEventHandler, ReactElement, ReactNode } from 'react'; import React from 'react'; +import type { Canvas } from 'storybook/internal/csf'; import type { Args, StrictArgs } from 'storybook/internal/types'; -import type { Canvas } from '@storybook/csf'; import type { Mock } from '@storybook/test'; import { fn } from '@storybook/test'; From a73f65688a31b8df23ff3aaaabef8ac86f559bb9 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 12 Feb 2025 15:02:42 +0100 Subject: [PATCH 144/144] Fix type checks --- code/builders/builder-vite/src/vite-server.ts | 2 -- code/core/template/stories/preview.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/code/builders/builder-vite/src/vite-server.ts b/code/builders/builder-vite/src/vite-server.ts index e58f6c98028d..e50935d77758 100644 --- a/code/builders/builder-vite/src/vite-server.ts +++ b/code/builders/builder-vite/src/vite-server.ts @@ -31,9 +31,7 @@ export async function createViteServer(options: Options, devServer: Server) { const ipRegex = /^(?:\d{1,3}\.){3}\d{1,3}$|^(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}$/; - // @ts-expect-error (does not exist) config.server.allowedHosts = - // @ts-expect-error (does not exist) commonCfg.server?.allowedHosts ?? (options.host && !ipRegex.test(options.host) ? [options.host.toLowerCase()] : true); diff --git a/code/core/template/stories/preview.ts b/code/core/template/stories/preview.ts index 23eb1b8ec26e..483bb2edd360 100644 --- a/code/core/template/stories/preview.ts +++ b/code/core/template/stories/preview.ts @@ -1,6 +1,6 @@ /* eslint-disable no-underscore-dangle */ import type { PartialStoryFn, StoryContext } from '@storybook/core/types'; -import type { ReactRenderer } from '@storybook/react/src'; +import type { ReactRenderer } from '@storybook/react'; declare global { interface Window {