diff --git a/.circleci/config.yml b/.circleci/config.yml index b212c825ddc2..0d3340694846 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -706,22 +706,22 @@ workflows: requires: - build - create-sandboxes: - parallelism: 34 + parallelism: 35 requires: - build # - smoke-test-sandboxes: # disabled for now # requires: # - create-sandboxes - build-sandboxes: - parallelism: 34 + parallelism: 35 requires: - create-sandboxes - chromatic-sandboxes: - parallelism: 31 + parallelism: 32 requires: - build-sandboxes - e2e-production: - parallelism: 29 + parallelism: 30 requires: - build-sandboxes - e2e-dev: @@ -729,7 +729,7 @@ workflows: requires: - create-sandboxes - test-runner-production: - parallelism: 29 + parallelism: 30 requires: - build-sandboxes diff --git a/.github/workflows/generate-sandboxes-main.yml b/.github/workflows/generate-sandboxes-main.yml deleted file mode 100644 index cc17376c57de..000000000000 --- a/.github/workflows/generate-sandboxes-main.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Generate and push sandboxes (main) - -on: - schedule: - - cron: '2 2 */1 * *' - workflow_dispatch: - # To test fixes on push rather than wait for the scheduling, do the following: - # 1. Uncomment the lines below and add your branch. - # push: - # branches: - # - - # 2. change the "ref" value to in the actions/checkout step below. - # 3. πŸ‘‰ DON'T FORGET TO UNDO THE VALUES BACK TO `main` BEFORE YOU MERGE YOUR CHANGES! - -jobs: - generate: - runs-on: ubuntu-latest - env: - YARN_ENABLE_IMMUTABLE_INSTALLS: false - CLEANUP_SANDBOX_NODE_MODULES: true - steps: - - uses: actions/checkout@v3 - with: - ref: main - - uses: actions/setup-node@v3 - with: - node-version-file: '.nvmrc' - - name: Setup git user - run: | - git config --global user.name "Storybook Bot" - git config --global user.email "bot@storybook.js.org" - - name: Install dependencies - run: | - cd ./scripts - node --experimental-modules ./check-dependencies.js - cd .. - - name: Compile Storybook libraries - run: yarn task --task compile --start-from=auto --no-link - - name: Publishing to local registry - run: yarn local-registry --publish - working-directory: ./code - - name: Running local registry - run: yarn local-registry --open & - working-directory: ./code - - name: Wait for registry - run: yarn wait-on tcp:127.0.0.1:6001 - working-directory: ./code - - name: Generate - run: yarn generate-sandboxes --local-registry - working-directory: ./code - - name: Publish - run: yarn publish-sandboxes --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT}}@github.com/storybookjs/sandboxes.git --push --branch=main - working-directory: ./code - - name: The job has failed - if: ${{ failure() || cancelled() }} - env: - DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }} - uses: Ilshidur/action-discord@master - with: - args: 'The generation of sandboxes in the **main** branch has failed. [View Run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})' diff --git a/.github/workflows/generate-sandboxes-next.yml b/.github/workflows/generate-sandboxes-next.yml deleted file mode 100644 index 2c0b592d024f..000000000000 --- a/.github/workflows/generate-sandboxes-next.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Generate and push sandboxes (next) - -on: - schedule: - - cron: '2 2 */1 * *' - workflow_dispatch: - # To test fixes on push rather than wait for the scheduling, do the following: - # 1. Uncomment the lines below and add your branch. - # push: - # branches: - # - - # 2. change the "ref" value to in the actions/checkout step below. - # 3. πŸ‘‰ DON'T FORGET TO UNDO THE VALUES BACK TO `next` BEFORE YOU MERGE YOUR CHANGES! - -jobs: - generate: - runs-on: ubuntu-latest - env: - YARN_ENABLE_IMMUTABLE_INSTALLS: false - CLEANUP_SANDBOX_NODE_MODULES: true - steps: - - uses: actions/checkout@v3 - with: - ref: next - - uses: actions/setup-node@v3 - with: - node-version-file: '.nvmrc' - - name: Setup git user - run: | - git config --global user.name "Storybook Bot" - git config --global user.email "bot@storybook.js.org" - - name: Install dependencies - run: | - cd ./scripts - node --experimental-modules ./check-dependencies.js - cd .. - - name: Compile Storybook libraries - run: yarn task --task compile --start-from=auto --no-link - - name: Publishing to local registry - run: yarn local-registry --publish - working-directory: ./code - - name: Running local registry - run: yarn local-registry --open & - working-directory: ./code - - name: Wait for registry - run: yarn wait-on tcp:127.0.0.1:6001 - working-directory: ./code - - name: Generate - run: yarn generate-sandboxes --local-registry --debug - working-directory: ./code - - name: Publish - run: yarn publish-sandboxes --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT}}@github.com/storybookjs/sandboxes.git --push --branch=next - working-directory: ./code - - name: The job has failed - if: ${{ failure() || cancelled() }} - env: - DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }} - uses: Ilshidur/action-discord@master - with: - args: 'The generation of sandboxes in the **next** branch has failed. [View Run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})' diff --git a/.github/workflows/generate-sandboxes.yml b/.github/workflows/generate-sandboxes.yml new file mode 100644 index 000000000000..88dc9e21bb41 --- /dev/null +++ b/.github/workflows/generate-sandboxes.yml @@ -0,0 +1,127 @@ +name: Generate and publish sandboxes + +on: + schedule: + - cron: "2 2 */1 * *" + workflow_dispatch: + # To test fixes on push rather than wait for the scheduling, do the following: + # 1. Uncomment the lines below and add your branch. + # push: + # branches: + # - + # 2. Change the "ref" value to in the actions/checkout step below. + # 3. Comment out the whole "generate-main" job starting at line 77 + # 4. πŸ‘‰ DON'T FORGET TO UNDO THE STEPS BEFORE YOU MERGE YOUR CHANGES! + +env: + YARN_ENABLE_IMMUTABLE_INSTALLS: "false" + CLEANUP_SANDBOX_NODE_MODULES: "true" + +defaults: + run: + working-directory: ./code + +jobs: + generate-next: + name: Generate to next + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: next + + - uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + + - name: Setup git user + run: | + git config --global user.name "storybook-bot" + git config --global user.email "32066757+storybook-bot@users.noreply.github.com" + + - name: Install dependencies + working-directory: ./scripts + run: node --experimental-modules ./check-dependencies.js + + - name: Compile Storybook libraries + run: yarn task --task compile --start-from=auto --no-link + + - name: Publish to local registry + run: yarn local-registry --publish + + - name: Run local registry + run: yarn local-registry --open & + + - name: Wait for registry + run: yarn wait-on tcp:127.0.0.1:6001 + + - name: Generate + id: generate + run: yarn generate-sandboxes --local-registry + + - name: Publish + # publish sandboxes even if the generation fails, as some sandboxes might have been generated successfully + if: ${{ !cancelled() }} + run: yarn publish-sandboxes --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT }}@github.com/storybookjs/sandboxes.git --push --branch=next + + - name: Report failure to Discord + if: failure() + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }} + uses: Ilshidur/action-discord@master + with: + args: | + The generation of some or all sandboxes on the **next** branch has failed. + [See the job summary for details](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + + generate-main: + name: Generate to main + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: main + + - uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + + - name: Setup git user + run: | + git config --global user.name "storybook-bot" + git config --global user.email "32066757+storybook-bot@users.noreply.github.com" + + - name: Install dependencies + working-directory: ./scripts + run: node --experimental-modules ./check-dependencies.js + + - name: Compile Storybook libraries + run: yarn task --task compile --start-from=auto --no-link + + - name: Publish to local registry + run: yarn local-registry --publish + + - name: Run local registry + run: yarn local-registry --open & + + - name: Wait for registry + run: yarn wait-on tcp:127.0.0.1:6001 + + - name: Generate + id: generate + run: yarn generate-sandboxes --local-registry + + - name: Publish + # publish sandboxes even if the generation fails, as some sandboxes might have been generated successfully + if: ${{ !cancelled() }} + run: yarn publish-sandboxes --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT }}@github.com/storybookjs/sandboxes.git --push --branch=main + + - name: Report failure to Discord + if: failure() + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }} + uses: Ilshidur/action-discord@master + with: + args: | + The generation of some or all sandboxes on the **main** branch has failed. + [See the job summary for details](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index 6e9e1eb4ba44..dce7c33f53c7 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,19 @@ +## 8.0.0-rc.2 + +- CLI: Add @storybook/addons automigration - [#26295](https://github.com/storybookjs/storybook/pull/26295), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- CLI: Fix vite config automigration to resolve from project root - [#26262](https://github.com/storybookjs/storybook/pull/26262), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- CLI: Improve `add` command & add tests - [#26298](https://github.com/storybookjs/storybook/pull/26298), thanks [@ndelangen](https://github.com/ndelangen)! +- CLI: Update minimum Node.js version requirement - [#26312](https://github.com/storybookjs/storybook/pull/26312), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- CSF-tools/Codemods: Upgrade recast - [#26286](https://github.com/storybookjs/storybook/pull/26286), thanks [@43081j](https://github.com/43081j)! +- Controls: Fix type summary when table.type unset - [#26283](https://github.com/storybookjs/storybook/pull/26283), thanks [@shilman](https://github.com/shilman)! +- Core: Add event when serverChannel disconnects - [#26322](https://github.com/storybookjs/storybook/pull/26322), thanks [@ndelangen](https://github.com/ndelangen)! +- Core: Fix composition of storybooks on same origin - [#26304](https://github.com/storybookjs/storybook/pull/26304), thanks [@ndelangen](https://github.com/ndelangen)! +- Portable stories: Improve existing APIs, add loaders support - [#26267](https://github.com/storybookjs/storybook/pull/26267), thanks [@yannbf](https://github.com/yannbf)! +- React: Handle TypeScript path aliases in react-docgen loader - [#26273](https://github.com/storybookjs/storybook/pull/26273), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Svelte: Support `5.0.0-next.65` prerelease - [#26188](https://github.com/storybookjs/storybook/pull/26188), thanks [@JReinhold](https://github.com/JReinhold)! +- Upgrade: Add missing isUpgrade parameter to automigrate function - [#26293](https://github.com/storybookjs/storybook/pull/26293), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Vue: Return component from `composeStory` - [#26317](https://github.com/storybookjs/storybook/pull/26317), thanks [@JReinhold](https://github.com/JReinhold)! + ## 8.0.0-rc.1 - CLI: Fix addon compatibility check error reporting in storybook dev - [#26258](https://github.com/storybookjs/storybook/pull/26258), thanks [@yannbf](https://github.com/yannbf)! diff --git a/MIGRATION.md b/MIGRATION.md index 202962c2cf7f..6488919f513b 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,7 +1,11 @@

Migration

- [From version 7.x to 8.0.0](#from-version-7x-to-800) - - [Type change in `composeStories` API](#type-change-in-composestories-api) + - [Portable stories](#portable-stories) + - [Project annotations are now merged instead of overwritten in composeStory](#project-annotations-are-now-merged-instead-of-overwritten-in-composestory) + - [Type change in `composeStories` API](#type-change-in-composestories-api) + - [DOM structure changed in portable stories](#dom-structure-changed-in-portable-stories) + - [Composed Vue stories are now components instead of functions](#composed-vue-stories-are-now-components-instead-of-functions) - [Tab addons are now routed to a query parameter](#tab-addons-are-now-routed-to-a-query-parameter) - [Default keyboard shortcuts changed](#default-keyboard-shortcuts-changed) - [Manager addons are now rendered with React 18](#manager-addons-are-now-rendered-with-react-18) @@ -86,17 +90,17 @@ - [Tab addons cannot manually route, Tool addons can filter their visibility via tabId](#tab-addons-cannot-manually-route-tool-addons-can-filter-their-visibility-via-tabid) - [Removed `config` preset](#removed-config-preset-1) - [From version 7.5.0 to 7.6.0](#from-version-750-to-760) - - [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated) - - [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated) - - [typescript.skipBabel deprecated](#typescriptskipbabel-deprecated) - - [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop) - - [Addons no longer need a peer dependency on React](#addons-no-longer-need-a-peer-dependency-on-react) + - [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated) + - [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated) + - [typescript.skipBabel deprecated](#typescriptskipbabel-deprecated) + - [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop) + - [Addons no longer need a peer dependency on React](#addons-no-longer-need-a-peer-dependency-on-react) - [From version 7.4.0 to 7.5.0](#from-version-740-to-750) - - [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated) - - [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers) + - [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated) + - [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers) - [From version 7.0.0 to 7.2.0](#from-version-700-to-720) - - [Addon API is more type-strict](#addon-api-is-more-type-strict) - - [Addon-controls hideNoControlsWarning parameter is deprecated](#addon-controls-hidenocontrolswarning-parameter-is-deprecated) + - [Addon API is more type-strict](#addon-api-is-more-type-strict) + - [Addon-controls hideNoControlsWarning parameter is deprecated](#addon-controls-hidenocontrolswarning-parameter-is-deprecated) - [From version 6.5.x to 7.0.0](#from-version-65x-to-700) - [7.0 breaking changes](#70-breaking-changes) - [Dropped support for Node 15 and below](#dropped-support-for-node-15-and-below) @@ -122,7 +126,7 @@ - [Deploying build artifacts](#deploying-build-artifacts) - [Dropped support for file URLs](#dropped-support-for-file-urls) - [Serving with nginx](#serving-with-nginx) - - [Ignore story files from node\_modules](#ignore-story-files-from-node_modules) + - [Ignore story files from node_modules](#ignore-story-files-from-node_modules) - [7.0 Core changes](#70-core-changes) - [7.0 feature flags removed](#70-feature-flags-removed) - [Story context is prepared before for supporting fine grained updates](#story-context-is-prepared-before-for-supporting-fine-grained-updates) @@ -136,7 +140,7 @@ - [Addon-interactions: Interactions debugger is now default](#addon-interactions-interactions-debugger-is-now-default) - [7.0 Vite changes](#70-vite-changes) - [Vite builder uses Vite config automatically](#vite-builder-uses-vite-config-automatically) - - [Vite cache moved to node\_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook) + - [Vite cache moved to node_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook) - [7.0 Webpack changes](#70-webpack-changes) - [Webpack4 support discontinued](#webpack4-support-discontinued) - [Babel mode v7 exclusively](#babel-mode-v7-exclusively) @@ -186,7 +190,7 @@ - [Dropped addon-docs manual babel configuration](#dropped-addon-docs-manual-babel-configuration) - [Dropped addon-docs manual configuration](#dropped-addon-docs-manual-configuration) - [Autoplay in docs](#autoplay-in-docs) - - [Removed STORYBOOK\_REACT\_CLASSES global](#removed-storybook_react_classes-global) + - [Removed STORYBOOK_REACT_CLASSES global](#removed-storybook_react_classes-global) - [7.0 Deprecations and default changes](#70-deprecations-and-default-changes) - [storyStoreV7 enabled by default](#storystorev7-enabled-by-default) - [`Story` type deprecated](#story-type-deprecated) @@ -401,7 +405,24 @@ ## From version 7.x to 8.0.0 -### Type change in `composeStories` API +### Portable stories + +#### Project annotations are now merged instead of overwritten in composeStory + +When passing project annotations overrides via `composeStory` such as: + +```tsx +const projectAnnotationOverrides = { parameters: { foo: "bar" } }; +const Primary = composeStory( + stories.Primary, + stories, + projectAnnotationOverrides +); +``` + +they are now merged with the annotations passed via `setProjectAnnotations` rather than completely overwriting them. This was seen as a bug and it's now fixed. If you have a use case where you really need this, please open an issue to elaborate. + +#### Type change in `composeStories` API There is a TypeScript type change in the `play` function returned from `composeStories` or `composeStory` in `@storybook/react` or `@storybook/vue3`, where before it was always defined, now it is potentially undefined. This means that you might have to make a small change in your code, such as: @@ -418,6 +439,62 @@ await Primary.play!(...) // if you want a runtime error when the play function d There are plans to make the type of the play function be inferred based on your imported story's play function in a near future, so the types will be 100% accurate. +#### DOM structure changed in portable stories + +The portable stories API now adds a wrapper to your stories with a unique id based on your story id, such as: + +```html +
+ +
+``` + +This means that if you take DOM snapshots of your stories, they will be affected and you will have to update them. + +The id calculation is based on different heuristics based on your Meta title and Story name. When using `composeStories`, the id can be inferred automatically. However, when using `composeStory` and your story does not explicitly have a `storyName` property, the story name can't be inferred automatically. As a result, its name will be "Unnamed Story", resulting in a wrapper id like `"#storybook-story-button--unnamed-story"`. If the id matters to you and you want to fix it, you have to specify the `exportsName` property like so: + +```ts +test("snapshots the story with custom id", () => { + const Primary = composeStory( + stories.Primary, + stories.default, + undefined, + // If you do not want the `unnamed-story` id, you have to pass the name of the story as a parameter + "Primary" + ); + + const { baseElement } = render(); + expect(baseElement).toMatchSnapshot(); +}); +``` + +#### Composed Vue stories are now components instead of functions + +`composeStory` (and `composeStories`) from `@storybook/vue3` now return Vue components rather than story functions that return components. This means that when rendering these composed stories you just pass the composed story _without_ first calling it. + +Previously when using `composeStory` from `@storybook/testing-vue3`, you would render composed stories with e.g. `render(MyStoryComposedStory({ someProp: true}))`. That is now changed to more [closely match how you would render regular Vue components](https://testing-library.com/docs/vue-testing-library/examples). + +When migrating from `@storybook/testing-vue3`, you will likely hit the following error: + +```ts +TypeError: Cannot read properties of undefined (reading 'devtoolsRawSetupState') +``` + +To fix it, you should change the usage of the composed story to reference it instead of calling it as a function. Here's an example using `@testing-library/vue` and Vitest: + +```diff +import { it } from 'vitest'; +import { render } from '@testing-library/vue'; +import * as stories from './Button.stories'; +import { composeStory } from '@storybook/vue3'; + +it('renders primary button', () => { + const Primary = composeStory(stories.Primary, stories.default); +- render(Primary({ label: 'Hello world' })); ++ render(Primary, { props: { label: 'Hello world' } }); +}); +``` + ### Tab addons are now routed to a query parameter The URL of a tab used to be: `http://localhost:6006/?path=/my-addon-tab/my-story`. @@ -556,7 +633,6 @@ This means https://github.com/IanVS/vite-plugin-turbosnap is no longer necessary Now that both Vite and Webpack support the `preview-stats.json` file, the flag has been renamed. The old flag will continue to work. - ### Implicit actions can not be used during rendering (for example in the play function) In Storybook 7, we inferred if the component accepts any action props, @@ -1013,7 +1089,7 @@ The `hideNoControlsWarning` parameter is now removed. [More info here](#addon-co The `setGlobalConfig` (used for reusing stories in your tests) is now removed in favor of `setProjectAnnotations`. ```ts -import { setProjectAnnotations } from `@storybook/testing-react`. +import { setProjectAnnotations } from `@storybook/react`. ``` #### StorybookViteConfig type from @storybook/builder-vite diff --git a/code/addons/a11y/package.json b/code/addons/a11y/package.json index ebae68256c87..abe43c249f6b 100644 --- a/code/addons/a11y/package.json +++ b/code/addons/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-a11y", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Test component compliance with web accessibility standards", "keywords": [ "a11y", diff --git a/code/addons/actions/package.json b/code/addons/actions/package.json index bc89e2f5e5c7..5dcdd61e3c52 100644 --- a/code/addons/actions/package.json +++ b/code/addons/actions/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-actions", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Get UI feedback when an action is performed on an interactive element", "keywords": [ "storybook", diff --git a/code/addons/backgrounds/package.json b/code/addons/backgrounds/package.json index 4fbdfc5c027c..17a842b06f13 100644 --- a/code/addons/backgrounds/package.json +++ b/code/addons/backgrounds/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-backgrounds", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Switch backgrounds to view components in different settings", "keywords": [ "addon", diff --git a/code/addons/controls/package.json b/code/addons/controls/package.json index 588947d02c70..3df650748609 100644 --- a/code/addons/controls/package.json +++ b/code/addons/controls/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-controls", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Interact with component inputs dynamically in the Storybook UI", "keywords": [ "addon", diff --git a/code/addons/docs/package.json b/code/addons/docs/package.json index a159c03063b6..a74ff2e67653 100644 --- a/code/addons/docs/package.json +++ b/code/addons/docs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-docs", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Document component usage and properties in Markdown", "keywords": [ "addon", diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json index 61dbf507bbb1..5b01bbd974b1 100644 --- a/code/addons/essentials/package.json +++ b/code/addons/essentials/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-essentials", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Curated addons to bring out the best of Storybook", "keywords": [ "addon", diff --git a/code/addons/gfm/package.json b/code/addons/gfm/package.json index 5491ba3a58e4..031416826745 100644 --- a/code/addons/gfm/package.json +++ b/code/addons/gfm/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-mdx-gfm", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "GitHub Flavored Markdown in Storybook", "keywords": [ "addon", diff --git a/code/addons/highlight/package.json b/code/addons/highlight/package.json index 28f331bf4dc8..e4802d08da43 100644 --- a/code/addons/highlight/package.json +++ b/code/addons/highlight/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-highlight", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Highlight DOM nodes within your stories", "keywords": [ "storybook-addons", diff --git a/code/addons/interactions/package.json b/code/addons/interactions/package.json index daece4716036..29d753da6fbf 100644 --- a/code/addons/interactions/package.json +++ b/code/addons/interactions/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-interactions", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Automate, test and debug user interactions", "keywords": [ "storybook-addons", diff --git a/code/addons/jest/package.json b/code/addons/jest/package.json index 3b194968932c..7284de78eec3 100644 --- a/code/addons/jest/package.json +++ b/code/addons/jest/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-jest", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "React storybook addon that show component jest report", "keywords": [ "addon", diff --git a/code/addons/links/package.json b/code/addons/links/package.json index d1777a65aa4d..a23690c6e9b2 100644 --- a/code/addons/links/package.json +++ b/code/addons/links/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-links", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Link stories together to build demos and prototypes with your UI components", "keywords": [ "addon", diff --git a/code/addons/measure/package.json b/code/addons/measure/package.json index 2d23660bdac2..843dd37f5197 100644 --- a/code/addons/measure/package.json +++ b/code/addons/measure/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-measure", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Inspect layouts by visualizing the box model", "keywords": [ "storybook-addons", diff --git a/code/addons/onboarding/package.json b/code/addons/onboarding/package.json index 057221c7df4e..5064aa48addb 100644 --- a/code/addons/onboarding/package.json +++ b/code/addons/onboarding/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-onboarding", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook Addon Onboarding - Introduces a new onboarding experience", "keywords": [ "storybook-addons", diff --git a/code/addons/outline/package.json b/code/addons/outline/package.json index a0a1ff61ab03..b5b5771d6049 100644 --- a/code/addons/outline/package.json +++ b/code/addons/outline/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-outline", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Outline all elements with CSS to help with layout placement and alignment", "keywords": [ "storybook-addons", diff --git a/code/addons/storysource/package.json b/code/addons/storysource/package.json index c9fc3680601d..e060751d1a6f 100644 --- a/code/addons/storysource/package.json +++ b/code/addons/storysource/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-storysource", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "View a story’s source code to see how it works and paste into your app", "keywords": [ "addon", diff --git a/code/addons/themes/package.json b/code/addons/themes/package.json index c28b3426d2e1..a13dcf595c17 100644 --- a/code/addons/themes/package.json +++ b/code/addons/themes/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-themes", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Switch between multiple themes for you components in Storybook", "keywords": [ "css", diff --git a/code/addons/toolbars/package.json b/code/addons/toolbars/package.json index f9aa8edb217e..b1bdae2a70cd 100644 --- a/code/addons/toolbars/package.json +++ b/code/addons/toolbars/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-toolbars", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Create your own toolbar items that control story rendering", "keywords": [ "addon", diff --git a/code/addons/viewport/package.json b/code/addons/viewport/package.json index 55459bde0a95..ae6af334349d 100644 --- a/code/addons/viewport/package.json +++ b/code/addons/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-viewport", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Build responsive components by adjusting Storybook’s viewport size and orientation", "keywords": [ "addon", diff --git a/code/builders/builder-manager/package.json b/code/builders/builder-manager/package.json index e892d2dbf0e9..c53e7272fb60 100644 --- a/code/builders/builder-manager/package.json +++ b/code/builders/builder-manager/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/builder-manager", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook manager builder", "keywords": [ "storybook" @@ -52,7 +52,7 @@ "@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10", "browser-assert": "^1.2.1", "ejs": "^3.1.8", - "esbuild": "^0.18.0", + "esbuild": "^18.0.0 || ^19.0.0 || ^0.20.0", "esbuild-plugin-alias": "^0.2.1", "express": "^4.17.3", "fs-extra": "^11.1.0", diff --git a/code/builders/builder-vite/package.json b/code/builders/builder-vite/package.json index b1dd337500cb..affa9c2fa59b 100644 --- a/code/builders/builder-vite/package.json +++ b/code/builders/builder-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/builder-vite", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "A plugin to run and build Storybooks with Vite", "homepage": "https://github.com/storybookjs/storybook/tree/next/code/builders/builder-vite/#readme", "bugs": { diff --git a/code/builders/builder-webpack5/package.json b/code/builders/builder-webpack5/package.json index 09e2717e316a..9565198092f8 100644 --- a/code/builders/builder-webpack5/package.json +++ b/code/builders/builder-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/builder-webpack5", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" diff --git a/code/frameworks/angular/package.json b/code/frameworks/angular/package.json index b58cb37738cf..8639c28d08c2 100644 --- a/code/frameworks/angular/package.json +++ b/code/frameworks/angular/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/angular", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for Angular: Develop Angular components in isolation with hot reloading.", "keywords": [ "storybook", diff --git a/code/frameworks/ember/package.json b/code/frameworks/ember/package.json index f279db33b3ed..db1b2cfe14aa 100644 --- a/code/frameworks/ember/package.json +++ b/code/frameworks/ember/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/ember", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for Ember: Develop Ember Component in isolation with Hot Reloading.", "homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/ember", "bugs": { diff --git a/code/frameworks/html-vite/package.json b/code/frameworks/html-vite/package.json index c4004391a219..5fb64a4a8c48 100644 --- a/code/frameworks/html-vite/package.json +++ b/code/frameworks/html-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html-vite", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for HTML and Vite: Develop HTML in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/html-webpack5/package.json b/code/frameworks/html-webpack5/package.json index 81cc4e41b756..55e6496baa3c 100644 --- a/code/frameworks/html-webpack5/package.json +++ b/code/frameworks/html-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html-webpack5", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index 73b56bd55c4b..f622f9eeaabe 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/nextjs", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for Next.js", "keywords": [ "storybook", diff --git a/code/frameworks/preact-vite/package.json b/code/frameworks/preact-vite/package.json index e059321937b6..19e035c0740d 100644 --- a/code/frameworks/preact-vite/package.json +++ b/code/frameworks/preact-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact-vite", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for Preact and Vite: Develop Preact components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/preact-webpack5/package.json b/code/frameworks/preact-webpack5/package.json index 1bfffc99ab65..8423ab98fade 100644 --- a/code/frameworks/preact-webpack5/package.json +++ b/code/frameworks/preact-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact-webpack5", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for Preact: Develop Preact Component in isolation.", "keywords": [ "storybook" diff --git a/code/frameworks/react-vite/package.json b/code/frameworks/react-vite/package.json index 1d3cfc67a144..a76d1c4cbddc 100644 --- a/code/frameworks/react-vite/package.json +++ b/code/frameworks/react-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-vite", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for React and Vite: Develop React components in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -50,10 +50,13 @@ "@joshwooding/vite-plugin-react-docgen-typescript": "0.3.0", "@rollup/pluginutils": "^5.0.2", "@storybook/builder-vite": "workspace:*", + "@storybook/node-logger": "workspace:*", "@storybook/react": "workspace:*", + "find-up": "^5.0.0", "magic-string": "^0.30.0", "react-docgen": "^7.0.0", - "resolve": "^1.22.8" + "resolve": "^1.22.8", + "tsconfig-paths": "^4.2.0" }, "devDependencies": { "@types/node": "^18.0.0", diff --git a/code/frameworks/react-vite/src/plugins/react-docgen.test.ts b/code/frameworks/react-vite/src/plugins/react-docgen.test.ts new file mode 100644 index 000000000000..1fed686198b9 --- /dev/null +++ b/code/frameworks/react-vite/src/plugins/react-docgen.test.ts @@ -0,0 +1,52 @@ +import { getReactDocgenImporter } from './react-docgen'; +import { describe, it, expect, vi } from 'vitest'; + +const reactDocgenMock = vi.hoisted(() => { + return { + makeFsImporter: vi.fn().mockImplementation((fn) => fn), + }; +}); + +const reactDocgenResolverMock = vi.hoisted(() => { + return { + defaultLookupModule: vi.fn(), + }; +}); + +vi.mock('./docgen-resolver', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + defaultLookupModule: reactDocgenResolverMock.defaultLookupModule, + }; +}); + +vi.mock('react-docgen', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + makeFsImporter: reactDocgenMock.makeFsImporter, + }; +}); + +describe('getReactDocgenImporter function', () => { + it('should not map the request if a tsconfig path mapping is not available', () => { + const filename = './src/components/Button.tsx'; + const basedir = '/src'; + const imported = getReactDocgenImporter(undefined); + reactDocgenResolverMock.defaultLookupModule.mockImplementation((filen: string) => filen); + const result = (imported as any)(filename, basedir); + expect(result).toBe(filename); + }); + + it('should map the request', () => { + const mappedFile = './mapped-file.tsx'; + const matchPath = vi.fn().mockReturnValue(mappedFile); + const filename = './src/components/Button.tsx'; + const basedir = '/src'; + const imported = getReactDocgenImporter(matchPath); + reactDocgenResolverMock.defaultLookupModule.mockImplementation((filen: string) => filen); + const result = (imported as any)(filename, basedir); + expect(result).toBe(mappedFile); + }); +}); diff --git a/code/frameworks/react-vite/src/plugins/react-docgen.ts b/code/frameworks/react-vite/src/plugins/react-docgen.ts index 9d2242ce2654..c59861e4ff43 100644 --- a/code/frameworks/react-vite/src/plugins/react-docgen.ts +++ b/code/frameworks/react-vite/src/plugins/react-docgen.ts @@ -10,12 +10,15 @@ import { } from 'react-docgen'; import MagicString from 'magic-string'; import type { PluginOption } from 'vite'; +import * as TsconfigPaths from 'tsconfig-paths'; +import findUp from 'find-up'; import actualNameHandler from './docgen-handlers/actualNameHandler'; import { RESOLVE_EXTENSIONS, ReactDocgenResolveError, defaultLookupModule, } from './docgen-resolver'; +import { logger } from '@storybook/node-logger'; type DocObj = Documentation & { actualName: string }; @@ -29,13 +32,27 @@ type Options = { exclude?: string | RegExp | (string | RegExp)[]; }; -export function reactDocgen({ +export async function reactDocgen({ include = /\.(mjs|tsx?|jsx?)$/, exclude = [/node_modules\/.*/], -}: Options = {}): PluginOption { +}: Options = {}): Promise { const cwd = process.cwd(); const filter = createFilter(include, exclude); + const tsconfigPath = await findUp('tsconfig.json', { cwd }); + const tsconfig = TsconfigPaths.loadConfig(tsconfigPath); + + let matchPath: TsconfigPaths.MatchPath | undefined; + + if (tsconfig.resultType === 'success') { + logger.info('Using tsconfig paths for react-docgen'); + matchPath = TsconfigPaths.createMatchPath(tsconfig.absoluteBaseUrl, tsconfig.paths, [ + 'browser', + 'module', + 'main', + ]); + } + return { name: 'storybook:react-docgen-plugin', enforce: 'pre', @@ -48,15 +65,7 @@ export function reactDocgen({ const docgenResults = parse(src, { resolver: defaultResolver, handlers, - importer: makeFsImporter((filename, basedir) => { - const result = defaultLookupModule(filename, basedir); - - if (RESOLVE_EXTENSIONS.find((ext) => result.endsWith(ext))) { - return result; - } - - throw new ReactDocgenResolveError(filename); - }), + importer: getReactDocgenImporter(matchPath), filename: id, }) as DocObj[]; const s = new MagicString(src); @@ -83,3 +92,24 @@ export function reactDocgen({ }, }; } + +export function getReactDocgenImporter(matchPath: TsconfigPaths.MatchPath | undefined) { + return makeFsImporter((filename, basedir) => { + const mappedFilenameByPaths = (() => { + if (matchPath) { + const match = matchPath(filename); + return match || filename; + } else { + return filename; + } + })(); + + const result = defaultLookupModule(mappedFilenameByPaths, basedir); + + if (RESOLVE_EXTENSIONS.find((ext) => result.endsWith(ext))) { + return result; + } + + throw new ReactDocgenResolveError(filename); + }); +} diff --git a/code/frameworks/react-vite/src/preset.ts b/code/frameworks/react-vite/src/preset.ts index 35a83a306ce0..cbdde0f61a07 100644 --- a/code/frameworks/react-vite/src/preset.ts +++ b/code/frameworks/react-vite/src/preset.ts @@ -43,7 +43,7 @@ export const viteFinal: StorybookConfig['viteFinal'] = async (config, { presets // Needs to run before the react plugin, so add to the front plugins.unshift( // If react-docgen is specified, use it for everything, otherwise only use it for non-typescript files - reactDocgen({ + await reactDocgen({ include: reactDocgenOption === 'react-docgen' ? /\.(mjs|tsx?|jsx?)$/ : /\.(mjs|jsx?)$/, }) ); diff --git a/code/frameworks/react-webpack5/package.json b/code/frameworks/react-webpack5/package.json index 448905c2a899..a2096ef8e059 100644 --- a/code/frameworks/react-webpack5/package.json +++ b/code/frameworks/react-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-webpack5", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for React: Develop React Component in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/server-webpack5/package.json b/code/frameworks/server-webpack5/package.json index 8b00bc35433d..7c3771652cfd 100644 --- a/code/frameworks/server-webpack5/package.json +++ b/code/frameworks/server-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/server-webpack5", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/svelte-vite/package.json b/code/frameworks/svelte-vite/package.json index 6af6bc742f3a..edac75e5589d 100644 --- a/code/frameworks/svelte-vite/package.json +++ b/code/frameworks/svelte-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte-vite", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for Svelte and Vite: Develop Svelte components in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -58,13 +58,13 @@ "devDependencies": { "@sveltejs/vite-plugin-svelte": "^3.0.1", "@types/node": "^18.0.0", - "svelte": "^5.0.0-next.28", + "svelte": "^5.0.0-next.65", "typescript": "^5.3.2", "vite": "^4.0.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0", - "svelte": "^4.0.0 || ^5.0.0-next.16", + "svelte": "^4.0.0 || ^5.0.0-next.65", "vite": "^4.0.0 || ^5.0.0" }, "engines": { diff --git a/code/frameworks/svelte-webpack5/package.json b/code/frameworks/svelte-webpack5/package.json index 7dccdc501e4a..85a17dfc6a76 100644 --- a/code/frameworks/svelte-webpack5/package.json +++ b/code/frameworks/svelte-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte-webpack5", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -58,7 +58,7 @@ "typescript": "^5.3.2" }, "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.16", + "svelte": "^4.0.0 || ^5.0.0-next.65", "svelte-loader": "*" }, "engines": { diff --git a/code/frameworks/svelte-webpack5/src/typings.d.ts b/code/frameworks/svelte-webpack5/src/typings.d.ts index 48e7ba6228fd..153f6ec1f9ce 100644 --- a/code/frameworks/svelte-webpack5/src/typings.d.ts +++ b/code/frameworks/svelte-webpack5/src/typings.d.ts @@ -1,3 +1,2 @@ -declare module '@storybook/svelte/templates/SlotDecorator.svelte'; -declare module '@storybook/svelte/templates/PreviewRender.svelte'; -declare module '@storybook/svelte/templates/HOC.svelte'; +declare module '@storybook/svelte/internal/SlotDecorator.svelte'; +declare module '@storybook/svelte/internal/PreviewRender.svelte'; diff --git a/code/frameworks/sveltekit/package.json b/code/frameworks/sveltekit/package.json index cc3d8ca57b87..b6450c030f5f 100644 --- a/code/frameworks/sveltekit/package.json +++ b/code/frameworks/sveltekit/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/sveltekit", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for SvelteKit", "keywords": [ "storybook", @@ -64,7 +64,7 @@ "vite": "^4.0.0" }, "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.16", + "svelte": "^4.0.0 || ^5.0.0-next.65", "vite": "^4.0.0 || ^5.0.0" }, "engines": { diff --git a/code/frameworks/vue3-vite/package.json b/code/frameworks/vue3-vite/package.json index ad452ade8d46..58e9602305b1 100644 --- a/code/frameworks/vue3-vite/package.json +++ b/code/frameworks/vue3-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue3-vite", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for Vue3 and Vite: Develop Vue3 components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts b/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts index bd6a22fe338a..7ec671bddf62 100644 --- a/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts +++ b/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts @@ -94,7 +94,13 @@ export async function vueComponentMeta(): Promise { // we can only add the "__docgenInfo" to variables that are actually defined in the current file // so e.g. re-exports like "export { default as MyComponent } from './MyComponent.vue'" must be ignored // to prevent runtime errors - if (new RegExp(`export {.*${name}.*}`).test(src)) { + if ( + new RegExp(`export {.*${name}.*}`).test(src) || + new RegExp(`export \\* from ['"]\\S*${name}['"]`).test(src) || + // when using re-exports, some exports might be resolved via checker.getExportNames + // but are not directly exported inside the current file so we need to ignore them too + !src.includes(name) + ) { return; } diff --git a/code/frameworks/vue3-webpack5/package.json b/code/frameworks/vue3-webpack5/package.json index 6b6504531dbf..a23d5980b17f 100644 --- a/code/frameworks/vue3-webpack5/package.json +++ b/code/frameworks/vue3-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue3-webpack5", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for Vue 3: Develop Vue 3 Components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/web-components-vite/package.json b/code/frameworks/web-components-vite/package.json index c4151a92ca89..6c559ff1a849 100644 --- a/code/frameworks/web-components-vite/package.json +++ b/code/frameworks/web-components-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components-vite", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for web-components and Vite: Develop Web Components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/web-components-webpack5/package.json b/code/frameworks/web-components-webpack5/package.json index 7e0ab8ec6130..04f0b31cbf1f 100644 --- a/code/frameworks/web-components-webpack5/package.json +++ b/code/frameworks/web-components-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components-webpack5", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for web-components: View web components snippets in isolation with Hot Reloading.", "keywords": [ "lit", diff --git a/code/lib/channels/package.json b/code/lib/channels/package.json index 9a4bb3c30fc1..2c32a9940490 100644 --- a/code/lib/channels/package.json +++ b/code/lib/channels/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/channels", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/channels/src/index.test.ts b/code/lib/channels/src/index.test.ts index 07f6d605af10..f99e04f6099c 100644 --- a/code/lib/channels/src/index.test.ts +++ b/code/lib/channels/src/index.test.ts @@ -1,9 +1,43 @@ import { describe, beforeEach, it, expect, vi } from 'vitest'; import type { ChannelTransport, Listener } from '.'; -import { Channel } from '.'; +import { Channel, WebsocketTransport } from '.'; vi.useFakeTimers(); +const MockedWebsocket = vi.hoisted(() => { + const ref = { current: undefined as unknown as InstanceType }; + class MyMockedWebsocket { + onopen: () => void; + + onmessage: (event: { data: string }) => void; + + onerror: (e: any) => void; + + onclose: () => void; + + constructor(url: string) { + this.onopen = vi.fn(); + this.onmessage = vi.fn(); + this.onerror = vi.fn(); + this.onclose = vi.fn(); + + ref.current = this; + } + + send(data: string) { + this.onmessage({ data }); + } + } + return { MyMockedWebsocket, ref }; +}); + +vi.mock('@storybook/global', () => ({ + global: { + ...global, + WebSocket: MockedWebsocket.MyMockedWebsocket, + }, +})); + describe('Channel', () => { let transport: ChannelTransport; let channel: Channel; @@ -232,3 +266,78 @@ describe('Channel', () => { }); }); }); + +describe('WebsocketTransport', () => { + it('should connect', async () => { + const onError = vi.fn(); + const handler = vi.fn(); + + const transport = new WebsocketTransport({ + url: 'ws://localhost:6006', + page: 'preview', + onError, + }); + + transport.setHandler(handler); + MockedWebsocket.ref.current.onopen(); + + expect(handler).toHaveBeenCalledTimes(0); + }); + it('should send message upon disconnect', async () => { + const onError = vi.fn(); + const handler = vi.fn(); + + const transport = new WebsocketTransport({ + url: 'ws://localhost:6006', + page: 'preview', + onError, + }); + + transport.setHandler(handler); + MockedWebsocket.ref.current.onclose(); + + expect(handler.mock.calls[0][0]).toMatchInlineSnapshot(` + { + "args": [], + "from": "preview", + "type": "channelWSDisconnect", + } + `); + }); + it('should send message when send', async () => { + const onError = vi.fn(); + const handler = vi.fn(); + + const transport = new WebsocketTransport({ + url: 'ws://localhost:6006', + page: 'preview', + onError, + }); + + transport.setHandler(handler); + MockedWebsocket.ref.current.send('{ "type": "test", "args": [], "from": "preview" }'); + + expect(handler.mock.calls[0][0]).toMatchInlineSnapshot(` + { + "args": [], + "from": "preview", + "type": "test", + } + `); + }); + it('should call onError handler', async () => { + const onError = vi.fn(); + const handler = vi.fn(); + + const transport = new WebsocketTransport({ + url: 'ws://localhost:6006', + page: 'preview', + onError, + }); + + transport.setHandler(handler); + MockedWebsocket.ref.current.onerror(new Error('testError')); + + expect(onError.mock.calls[0][0]).toMatchInlineSnapshot(`[Error: testError]`); + }); +}); diff --git a/code/lib/channels/src/index.ts b/code/lib/channels/src/index.ts index 7942d57fa4f3..80a865f31904 100644 --- a/code/lib/channels/src/index.ts +++ b/code/lib/channels/src/index.ts @@ -35,7 +35,7 @@ export function createBrowserChannel({ page, extraTransports = [] }: Options): C const { hostname, port } = window.location; const channelUrl = `${protocol}://${hostname}:${port}/storybook-server-channel`; - transports.push(new WebsocketTransport({ url: channelUrl, onError: () => {} })); + transports.push(new WebsocketTransport({ url: channelUrl, onError: () => {}, page })); } return new Channel({ transports }); diff --git a/code/lib/channels/src/postmessage/getEventSourceUrl.ts b/code/lib/channels/src/postmessage/getEventSourceUrl.ts index da3f4ec4d2f6..33c5729a7a08 100644 --- a/code/lib/channels/src/postmessage/getEventSourceUrl.ts +++ b/code/lib/channels/src/postmessage/getEventSourceUrl.ts @@ -7,6 +7,14 @@ export const getEventSourceUrl = (event: MessageEvent) => { // try to find the originating iframe by matching it's contentWindow // This might not be cross-origin safe const [frame, ...remainder] = frames.filter((element) => { + try { + return ( + element.contentWindow?.location.origin === (event.source as Window).location.origin && + element.contentWindow?.location.pathname === (event.source as Window).location.pathname + ); + } catch (err) { + // continue + } try { return element.contentWindow === event.source; } catch (err) { diff --git a/code/lib/channels/src/websocket/index.ts b/code/lib/channels/src/websocket/index.ts index a46df1c28610..0cc73345507d 100644 --- a/code/lib/channels/src/websocket/index.ts +++ b/code/lib/channels/src/websocket/index.ts @@ -5,13 +5,14 @@ import { global } from '@storybook/global'; import { isJSON, parse, stringify } from 'telejson'; import invariant from 'tiny-invariant'; -import type { ChannelTransport, ChannelHandler } from '../types'; +import * as EVENTS from '@storybook/core-events'; +import type { ChannelTransport, ChannelHandler, Config } from '../types'; const { WebSocket } = global; type OnError = (message: Event) => void; -interface WebsocketTransportArgs { +interface WebsocketTransportArgs extends Partial { url: string; onError: OnError; } @@ -25,7 +26,7 @@ export class WebsocketTransport implements ChannelTransport { private isReady = false; - constructor({ url, onError }: WebsocketTransportArgs) { + constructor({ url, onError, page }: WebsocketTransportArgs) { this.socket = new WebSocket(url); this.socket.onopen = () => { this.isReady = true; @@ -41,6 +42,10 @@ export class WebsocketTransport implements ChannelTransport { onError(e); } }; + this.socket.onclose = () => { + invariant(this.handler, 'WebsocketTransport handler should be set'); + this.handler({ type: EVENTS.CHANNEL_WS_DISCONNECT, args: [], from: page || 'preview' }); + }; } setHandler(handler: ChannelHandler) { diff --git a/code/lib/cli-sb/package.json b/code/lib/cli-sb/package.json index fb2741dc33a2..821450e6252c 100644 --- a/code/lib/cli-sb/package.json +++ b/code/lib/cli-sb/package.json @@ -1,6 +1,6 @@ { "name": "sb", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook CLI", "keywords": [ "storybook" diff --git a/code/lib/cli-storybook/package.json b/code/lib/cli-storybook/package.json index f4e2319a57cb..17585083a303 100644 --- a/code/lib/cli-storybook/package.json +++ b/code/lib/cli-storybook/package.json @@ -1,6 +1,6 @@ { "name": "storybook", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook CLI", "keywords": [ "storybook" diff --git a/code/lib/cli/bin/index.js b/code/lib/cli/bin/index.js index 7131e95a311d..af7869a6e043 100755 --- a/code/lib/cli/bin/index.js +++ b/code/lib/cli/bin/index.js @@ -1,8 +1,8 @@ #!/usr/bin/env node -const majorNodeVersion = parseInt(process.version.toString().replace('v', '').split('.')[0], 10); -if (majorNodeVersion < 16) { - console.error('To run storybook you need to have node 16 or higher'); +const majorNodeVersion = parseInt(process.versions.node, 10); +if (majorNodeVersion < 18) { + console.error('To run Storybook you need to have Node.js 18 or higher'); process.exit(1); } diff --git a/code/lib/cli/package.json b/code/lib/cli/package.json index ed8b9f90bcf6..0f8d72361143 100644 --- a/code/lib/cli/package.json +++ b/code/lib/cli/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/cli", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook's CLI - install, dev, build, upgrade, and more", "keywords": [ "cli", diff --git a/code/lib/cli/src/add.test.ts b/code/lib/cli/src/add.test.ts new file mode 100644 index 000000000000..3025da275e49 --- /dev/null +++ b/code/lib/cli/src/add.test.ts @@ -0,0 +1,148 @@ +import { describe, expect, test, vi } from 'vitest'; +import { add, getVersionSpecifier } from './add'; + +const MockedConfig = vi.hoisted(() => { + return { + appendValueToArray: vi.fn(), + }; +}); +const MockedPackageManager = vi.hoisted(() => { + return { + retrievePackageJson: vi.fn(() => ({})), + latestVersion: vi.fn(() => '1.0.0'), + addDependencies: vi.fn(() => {}), + type: 'npm', + }; +}); +const MockedPostInstall = vi.hoisted(() => { + return { + postinstallAddon: vi.fn(), + }; +}); +const MockedConsole = { + log: vi.fn(), + warn: vi.fn(), + error: vi.fn(), +} as any as Console; + +vi.mock('@storybook/csf-tools', () => { + return { + readConfig: vi.fn(() => MockedConfig), + writeConfig: vi.fn(), + }; +}); +vi.mock('./postinstallAddon', () => { + return MockedPostInstall; +}); +vi.mock('@storybook/core-common', () => { + return { + getStorybookInfo: vi.fn(() => ({ mainConfig: {}, configDir: '' })), + serverRequire: vi.fn(() => ({})), + JsPackageManagerFactory: { + getPackageManager: vi.fn(() => MockedPackageManager), + }, + getCoercedStorybookVersion: vi.fn(() => '8.0.0'), + versions: { + '@storybook/addon-docs': '^8.0.0', + }, + }; +}); + +describe('getVersionSpecifier', (it) => { + test.each([ + ['@storybook/addon-docs', ['@storybook/addon-docs', undefined]], + ['@storybook/addon-docs@7.0.1', ['@storybook/addon-docs', '7.0.1']], + ['@storybook/addon-docs@7.0.1-beta.1', ['@storybook/addon-docs', '7.0.1-beta.1']], + ['@storybook/addon-docs@~7.0.1-beta.1', ['@storybook/addon-docs', '~7.0.1-beta.1']], + ['@storybook/addon-docs@^7.0.1-beta.1', ['@storybook/addon-docs', '^7.0.1-beta.1']], + ['@storybook/addon-docs@next', ['@storybook/addon-docs', 'next']], + ])('%s => %s', (input, expected) => { + const result = getVersionSpecifier(input); + expect(result[0]).toEqual(expected[0]); + expect(result[1]).toEqual(expected[1]); + }); +}); + +describe('add', () => { + const testData = [ + { input: 'aa', expected: 'aa@^1.0.0' }, // resolves to the latest version + { input: 'aa@4', expected: 'aa@^4' }, + { input: 'aa@4.1.0', expected: 'aa@^4.1.0' }, + { input: 'aa@^4', expected: 'aa@^4' }, + { input: 'aa@~4', expected: 'aa@~4' }, + { input: 'aa@4.1.0-alpha.1', expected: 'aa@^4.1.0-alpha.1' }, + { input: 'aa@next', expected: 'aa@next' }, + + { input: '@org/aa', expected: '@org/aa@^1.0.0' }, + { input: '@org/aa@4', expected: '@org/aa@^4' }, + { input: '@org/aa@4.1.0', expected: '@org/aa@^4.1.0' }, + { input: '@org/aa@4.1.0-alpha.1', expected: '@org/aa@^4.1.0-alpha.1' }, + { input: '@org/aa@next', expected: '@org/aa@next' }, + + { input: '@storybook/addon-docs@~4', expected: '@storybook/addon-docs@~4' }, + { input: '@storybook/addon-docs@next', expected: '@storybook/addon-docs@next' }, + { input: '@storybook/addon-docs', expected: '@storybook/addon-docs@^8.0.0' }, // takes it from the versions file + ]; + + test.each(testData)('$input', async ({ input, expected }) => { + const [packageName] = getVersionSpecifier(input); + + await add(input, { packageManager: 'npm', skipPostinstall: true }, MockedConsole); + + expect(MockedConfig.appendValueToArray).toHaveBeenCalledWith( + expect.arrayContaining(['addons']), + packageName + ); + + expect(MockedPackageManager.addDependencies).toHaveBeenCalledWith( + { installAsDevDependencies: true }, + [expected] + ); + }); +}); + +describe('add (extra)', () => { + test('not warning when installing the correct version of storybook', async () => { + await add( + '@storybook/addon-docs', + { packageManager: 'npm', skipPostinstall: true }, + MockedConsole + ); + + expect(MockedConsole.warn).not.toHaveBeenCalledWith( + expect.stringContaining(`is not the same as the version of Storybook you are using.`) + ); + }); + test('not warning when installing unrelated package', async () => { + await add('aa', { packageManager: 'npm', skipPostinstall: true }, MockedConsole); + + expect(MockedConsole.warn).not.toHaveBeenCalledWith( + expect.stringContaining(`is not the same as the version of Storybook you are using.`) + ); + }); + test('warning when installing a core addon mismatching version of storybook', async () => { + await add( + '@storybook/addon-docs@2.0.0', + { packageManager: 'npm', skipPostinstall: true }, + MockedConsole + ); + + expect(MockedConsole.warn).toHaveBeenCalledWith( + expect.stringContaining( + `The version of @storybook/addon-docs you are installing is not the same as the version of Storybook you are using. This may lead to unexpected behavior.` + ) + ); + }); + + test('postInstall', async () => { + await add( + '@storybook/addon-docs', + { packageManager: 'npm', skipPostinstall: false }, + MockedConsole + ); + + expect(MockedPostInstall.postinstallAddon).toHaveBeenCalledWith('@storybook/addon-docs', { + packageManager: 'npm', + }); + }); +}); diff --git a/code/lib/cli/src/add.ts b/code/lib/cli/src/add.ts index 0321ec966fd7..19a4b552fcc0 100644 --- a/code/lib/cli/src/add.ts +++ b/code/lib/cli/src/add.ts @@ -1,44 +1,32 @@ import { getStorybookInfo, serverRequire, - getCoercedStorybookVersion, - isCorePackage, JsPackageManagerFactory, + getCoercedStorybookVersion, type PackageManagerName, + versions, } from '@storybook/core-common'; import { readConfig, writeConfig } from '@storybook/csf-tools'; import { isAbsolute, join } from 'path'; import SemVer from 'semver'; import dedent from 'ts-dedent'; +import { postinstallAddon } from './postinstallAddon'; -const logger = console; - -interface PostinstallOptions { +export interface PostinstallOptions { packageManager: PackageManagerName; } -const postinstallAddon = async (addonName: string, options: PostinstallOptions) => { - try { - const modulePath = require.resolve(`${addonName}/postinstall`, { paths: [process.cwd()] }); - - const postinstall = require(modulePath); - - try { - logger.log(`Running postinstall script for ${addonName}`); - await postinstall(options); - } catch (e) { - logger.error(`Error running postinstall script for ${addonName}`); - logger.error(e); - } - } catch (e) { - // no postinstall script - } -}; - -const getVersionSpecifier = (addon: string) => { - const groups = /^(...*)@(.*)$/.exec(addon); +/** + * Extract the addon name and version specifier from the input string + * @param addon - the input string + * @returns [addonName, versionSpecifier] + * @example + * getVersionSpecifier('@storybook/addon-docs@7.0.1') => ['@storybook/addon-docs', '7.0.1'] + */ +export const getVersionSpecifier = (addon: string) => { + const groups = /^(@{0,1}[^@]+)(?:@(.+))?$/.exec(addon); if (groups) { - return [groups[0], groups[2]] as const; + return [groups[1], groups[2]] as const; } return [addon, undefined] as const; }; @@ -58,6 +46,8 @@ const checkInstalled = (addonName: string, main: any) => { return !!existingAddon; }; +const isCoreAddon = (addonName: string) => Object.hasOwn(versions, addonName); + /** * Install the given addon package and add it to main.js * @@ -71,9 +61,11 @@ const checkInstalled = (addonName: string, main: any) => { */ export async function add( addon: string, - options: { packageManager: PackageManagerName; skipPostinstall: boolean } + options: { packageManager: PackageManagerName; skipPostinstall: boolean }, + logger = console ) { const { packageManager: pkgMgr } = options; + const [addonName, inputVersion] = getVersionSpecifier(addon); const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr }); const packageJson = await packageManager.retrievePackageJson(); @@ -85,43 +77,52 @@ export async function add( `); } - if (checkInstalled(addon, requireMain(configDir))) { - throw new Error(dedent` - Addon ${addon} is already installed; we skipped adding it to your ${mainConfig}. - `); - } - - const [addonName, versionSpecifier] = getVersionSpecifier(addon); - if (!mainConfig) { logger.error('Unable to find storybook main.js config'); return; } + + if (checkInstalled(addonName, requireMain(configDir))) { + throw new Error(dedent` + Addon ${addonName} is already installed; we skipped adding it to your ${mainConfig}. + `); + } + const main = await readConfig(mainConfig); logger.log(`Verifying ${addonName}`); - const latestVersion = await packageManager.latestVersion(addonName); - if (!latestVersion) { - logger.error(`Unknown addon ${addonName}`); - } - // add to package.json - const isStorybookAddon = addonName.startsWith('@storybook/'); - const isAddonFromCore = isCorePackage(addonName); const storybookVersion = await getCoercedStorybookVersion(packageManager); - const version = versionSpecifier || (isAddonFromCore ? storybookVersion : latestVersion); - const addonWithVersion = SemVer.valid(version) + let version = inputVersion; + + if (!version && isCoreAddon(addonName) && storybookVersion) { + version = storybookVersion; + } + if (!version) { + version = await packageManager.latestVersion(addonName); + } + + if (isCoreAddon(addonName) && version !== storybookVersion) { + logger.warn( + `The version of ${addonName} you are installing is not the same as the version of Storybook you are using. This may lead to unexpected behavior.` + ); + } + + const addonWithVersion = isValidVersion(version) ? `${addonName}@^${version}` : `${addonName}@${version}`; + logger.log(`Installing ${addonWithVersion}`); await packageManager.addDependencies({ installAsDevDependencies: true }, [addonWithVersion]); - // add to main.js logger.log(`Adding '${addon}' to main.js addons field.`); main.appendValueToArray(['addons'], addonName); await writeConfig(main); - if (!options.skipPostinstall && isStorybookAddon) { + if (!options.skipPostinstall && isCoreAddon(addonName)) { await postinstallAddon(addonName, { packageManager: packageManager.type }); } } +function isValidVersion(version: string) { + return SemVer.valid(version) || version.match(/^\d+$/); +} diff --git a/code/lib/cli/src/automigrate/fixes/addon-postcss.ts b/code/lib/cli/src/automigrate/fixes/addon-postcss.ts index 7978e545a1c5..f8d134183c0b 100644 --- a/code/lib/cli/src/automigrate/fixes/addon-postcss.ts +++ b/code/lib/cli/src/automigrate/fixes/addon-postcss.ts @@ -29,7 +29,7 @@ export const addonPostCSS: Fix = { return dedent` ${chalk.bold( 'Attention' - )}: We've detected that you're using the following package which are incompatible with Storybook 8 and beyond: + )}: We've detected that you're using the following package which is incompatible with Storybook 8 and beyond: - ${chalk.cyan(`@storybook/addon-postcss`)} diff --git a/code/lib/cli/src/automigrate/fixes/addons-api.test.ts b/code/lib/cli/src/automigrate/fixes/addons-api.test.ts new file mode 100644 index 000000000000..2bae14386d3b --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/addons-api.test.ts @@ -0,0 +1,44 @@ +import { addonsAPI } from './addons-api'; +import type { StorybookConfig } from '@storybook/types'; +import type { JsPackageManager } from '@storybook/core-common'; +import { expect, describe, it } from 'vitest'; + +const checkAddonsAPI = async ({ + packageManager, + mainConfig = {}, + storybookVersion = '7.0.0', +}: { + packageManager?: Partial; + mainConfig?: Partial; + storybookVersion?: string; +}) => { + return addonsAPI.check({ + packageManager: packageManager as any, + storybookVersion, + mainConfig: mainConfig as any, + }); +}; + +describe('check function', () => { + it('should return { usesAddonsAPI: true } if @storybook/addons is installed', async () => { + await expect( + checkAddonsAPI({ + packageManager: { + getAllDependencies: async () => ({ + '@storybook/addons': '6.0.0', + }), + }, + }) + ).resolves.toEqual({ usesAddonsAPI: true }); + }); + + it('should return null if @storybook/addons is not installed', async () => { + await expect( + checkAddonsAPI({ + packageManager: { + getAllDependencies: async () => ({}), + }, + }) + ).resolves.toBeNull(); + }); +}); diff --git a/code/lib/cli/src/automigrate/fixes/addons-api.ts b/code/lib/cli/src/automigrate/fixes/addons-api.ts new file mode 100644 index 000000000000..f193898aa82f --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/addons-api.ts @@ -0,0 +1,45 @@ +import chalk from 'chalk'; +import { dedent } from 'ts-dedent'; +import type { Fix } from '../types'; + +interface AddonsAPIRunOptions { + usesAddonsAPI: boolean; +} + +export const addonsAPI: Fix = { + id: 'addons-api', + + versionRange: ['<8', '>=8'], + + promptType: 'notification', + + async check({ packageManager }) { + const allDependencies = await packageManager.getAllDependencies(); + const usesAddonsAPI = !!allDependencies['@storybook/addons']; + + if (!usesAddonsAPI) { + return null; + } + + return { usesAddonsAPI: true }; + }, + + prompt() { + return dedent` + ${chalk.bold( + 'Attention' + )}: We've detected that you're using the following package which is removed in Storybook 8 and beyond: + + - ${chalk.cyan(`@storybook/addons`)} + + This package has been deprecated and replaced with ${chalk.cyan( + `@storybook/preview-api` + )} and ${chalk.cyan(`@storybook/manager-api`)}. + + You can find more information about the new addons API in the migration guide: + ${chalk.yellow( + 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#new-addons-api' + )} + `; + }, +}; diff --git a/code/lib/cli/src/automigrate/fixes/index.ts b/code/lib/cli/src/automigrate/fixes/index.ts index 91ba9a27927f..17b3d4942be1 100644 --- a/code/lib/cli/src/automigrate/fixes/index.ts +++ b/code/lib/cli/src/automigrate/fixes/index.ts @@ -23,12 +23,14 @@ import { storyshotsMigration } from './storyshots-migration'; import { removeArgtypesRegex } from './remove-argtypes-regex'; import { webpack5CompilerSetup } from './webpack5-compiler-setup'; import { removeJestTestingLibrary } from './remove-jest-testing-library'; +import { addonsAPI } from './addons-api'; import { mdx1to3 } from './mdx-1-to-3'; import { addonPostCSS } from './addon-postcss'; export * from '../types'; export const allFixes: Fix[] = [ + addonsAPI, newFrameworks, cra5, webpack5, diff --git a/code/lib/cli/src/automigrate/fixes/vite-config-file.ts b/code/lib/cli/src/automigrate/fixes/vite-config-file.ts index 6325b71ad062..6a8dd9ec0e02 100644 --- a/code/lib/cli/src/automigrate/fixes/vite-config-file.ts +++ b/code/lib/cli/src/automigrate/fixes/vite-config-file.ts @@ -4,6 +4,7 @@ import findUp from 'find-up'; import { getFrameworkPackageName } from '../helpers/mainConfigFile'; import { frameworkToRenderer } from '../../helpers'; import { frameworkPackages } from '@storybook/core-common'; +import path from 'path'; interface ViteConfigFileRunOptions { plugins: string[]; @@ -15,13 +16,13 @@ export const viteConfigFile = { versionRange: ['<8.0.0-beta.3', '>=8.0.0-beta.3'], - async check({ mainConfig, packageManager }) { - let isViteConfigFileFound = !!(await findUp([ - 'vite.config.js', - 'vite.config.mjs', - 'vite.config.cjs', - 'vite.config.ts', - ])); + promptType: 'notification', + + async check({ mainConfig, packageManager, mainConfigPath }) { + let isViteConfigFileFound = !!(await findUp( + ['vite.config.js', 'vite.config.mjs', 'vite.config.cjs', 'vite.config.ts'], + { cwd: mainConfigPath ? path.join(mainConfigPath, '..') : process.cwd() } + )); const rendererToVitePluginMap: Record = { preact: '@preact/preset-vite', diff --git a/code/lib/cli/src/automigrate/index.ts b/code/lib/cli/src/automigrate/index.ts index b0864a7071c6..8a84476b5e93 100644 --- a/code/lib/cli/src/automigrate/index.ts +++ b/code/lib/cli/src/automigrate/index.ts @@ -111,6 +111,7 @@ export const automigrate = async ({ renderer: rendererPackage, skipInstall, hideMigrationSummary = false, + isUpgrade, }: AutofixOptions): Promise<{ fixResults: Record; preCheckFailure?: PreCheckFailure; @@ -142,6 +143,7 @@ export const automigrate = async ({ mainConfigPath, storybookVersion, beforeVersion, + isUpgrade, dryRun, yes, }); diff --git a/code/lib/cli/src/automigrate/types.ts b/code/lib/cli/src/automigrate/types.ts index 36b4bac18c47..d8cc9f06af3e 100644 --- a/code/lib/cli/src/automigrate/types.ts +++ b/code/lib/cli/src/automigrate/types.ts @@ -27,9 +27,8 @@ export interface RunOptions { */ export type Prompt = 'auto' | 'manual' | 'notification'; -export interface Fix { +type BaseFix = { id: string; - promptType?: Prompt | ((result: ResultType) => Promise | Prompt); /** * The from/to version range of Storybook that this fix applies to. The strings are semver ranges. * The versionRange will only be checked if the automigration is part of an upgrade. @@ -38,8 +37,23 @@ export interface Fix { versionRange: [from: string, to: string]; check: (options: CheckOptions) => Promise; prompt: (result: ResultType) => string; - run?: (options: RunOptions) => Promise; -} +}; + +type PromptType = + | T + | ((result: ResultType) => Promise | Prompt); + +export type Fix = ( + | { + promptType?: PromptType; + run: (options: RunOptions) => Promise; + } + | { + promptType: PromptType; + run?: never; + } +) & + BaseFix; export type FixId = string; diff --git a/code/lib/cli/src/postinstallAddon.ts b/code/lib/cli/src/postinstallAddon.ts new file mode 100644 index 000000000000..50719c29e29c --- /dev/null +++ b/code/lib/cli/src/postinstallAddon.ts @@ -0,0 +1,19 @@ +import type { PostinstallOptions } from './add'; + +export const postinstallAddon = async (addonName: string, options: PostinstallOptions) => { + try { + const modulePath = require.resolve(`${addonName}/postinstall`, { paths: [process.cwd()] }); + + const postinstall = require(modulePath); + + try { + console.log(`Running postinstall script for ${addonName}`); + await postinstall(options); + } catch (e) { + console.error(`Error running postinstall script for ${addonName}`); + console.error(e); + } + } catch (e) { + // no postinstall script + } +}; diff --git a/code/lib/cli/src/sandbox-templates.ts b/code/lib/cli/src/sandbox-templates.ts index e1395b81156f..182e49798148 100644 --- a/code/lib/cli/src/sandbox-templates.ts +++ b/code/lib/cli/src/sandbox-templates.ts @@ -626,6 +626,7 @@ export const daily: TemplateKey[] = [ 'vue-cli/default-js', 'lit-vite/default-js', 'svelte-kit/skeleton-js', + 'svelte-kit/prerelease-ts', 'svelte-vite/default-js', 'nextjs/13-ts', 'nextjs/default-js', diff --git a/code/lib/client-logger/package.json b/code/lib/client-logger/package.json index f5f6f32ed843..60716ac1c3cb 100644 --- a/code/lib/client-logger/package.json +++ b/code/lib/client-logger/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/client-logger", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/codemod/package.json b/code/lib/codemod/package.json index 0e4154e0db74..dbcac376c369 100644 --- a/code/lib/codemod/package.json +++ b/code/lib/codemod/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/codemod", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "A collection of codemod scripts written with JSCodeshift", "keywords": [ "storybook" @@ -67,7 +67,7 @@ "jscodeshift": "^0.15.1", "lodash": "^4.17.21", "prettier": "^3.1.1", - "recast": "^0.23.1", + "recast": "^0.23.5", "tiny-invariant": "^1.3.1" }, "devDependencies": { diff --git a/code/lib/core-common/package.json b/code/lib/core-common/package.json index 169977d00655..303b4cd42734 100644 --- a/code/lib/core-common/package.json +++ b/code/lib/core-common/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/core-common", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" @@ -52,7 +52,7 @@ "@yarnpkg/libzip": "2.3.0", "chalk": "^4.1.0", "cross-spawn": "^7.0.3", - "esbuild": "^0.18.0", + "esbuild": "^18.0.0 || ^19.0.0 || ^0.20.0", "esbuild-register": "^3.5.0", "execa": "^5.0.0", "file-system-cache": "2.3.0", diff --git a/code/lib/core-common/src/versions.ts b/code/lib/core-common/src/versions.ts index 346f3d272c65..1de3269c4e5b 100644 --- a/code/lib/core-common/src/versions.ts +++ b/code/lib/core-common/src/versions.ts @@ -1,83 +1,83 @@ // auto generated file, do not edit export default { - '@storybook/addon-a11y': '8.0.0-rc.1', - '@storybook/addon-actions': '8.0.0-rc.1', - '@storybook/addon-backgrounds': '8.0.0-rc.1', - '@storybook/addon-controls': '8.0.0-rc.1', - '@storybook/addon-docs': '8.0.0-rc.1', - '@storybook/addon-essentials': '8.0.0-rc.1', - '@storybook/addon-highlight': '8.0.0-rc.1', - '@storybook/addon-interactions': '8.0.0-rc.1', - '@storybook/addon-jest': '8.0.0-rc.1', - '@storybook/addon-links': '8.0.0-rc.1', - '@storybook/addon-mdx-gfm': '8.0.0-rc.1', - '@storybook/addon-measure': '8.0.0-rc.1', - '@storybook/addon-onboarding': '8.0.0-rc.1', - '@storybook/addon-outline': '8.0.0-rc.1', - '@storybook/addon-storysource': '8.0.0-rc.1', - '@storybook/addon-themes': '8.0.0-rc.1', - '@storybook/addon-toolbars': '8.0.0-rc.1', - '@storybook/addon-viewport': '8.0.0-rc.1', - '@storybook/angular': '8.0.0-rc.1', - '@storybook/blocks': '8.0.0-rc.1', - '@storybook/builder-manager': '8.0.0-rc.1', - '@storybook/builder-vite': '8.0.0-rc.1', - '@storybook/builder-webpack5': '8.0.0-rc.1', - '@storybook/channels': '8.0.0-rc.1', - '@storybook/cli': '8.0.0-rc.1', - '@storybook/client-logger': '8.0.0-rc.1', - '@storybook/codemod': '8.0.0-rc.1', - '@storybook/components': '8.0.0-rc.1', - '@storybook/core-common': '8.0.0-rc.1', - '@storybook/core-events': '8.0.0-rc.1', - '@storybook/core-server': '8.0.0-rc.1', - '@storybook/core-webpack': '8.0.0-rc.1', - '@storybook/csf-plugin': '8.0.0-rc.1', - '@storybook/csf-tools': '8.0.0-rc.1', - '@storybook/docs-tools': '8.0.0-rc.1', - '@storybook/ember': '8.0.0-rc.1', - '@storybook/html': '8.0.0-rc.1', - '@storybook/html-vite': '8.0.0-rc.1', - '@storybook/html-webpack5': '8.0.0-rc.1', - '@storybook/instrumenter': '8.0.0-rc.1', - '@storybook/manager': '8.0.0-rc.1', - '@storybook/manager-api': '8.0.0-rc.1', - '@storybook/nextjs': '8.0.0-rc.1', - '@storybook/node-logger': '8.0.0-rc.1', - '@storybook/preact': '8.0.0-rc.1', - '@storybook/preact-vite': '8.0.0-rc.1', - '@storybook/preact-webpack5': '8.0.0-rc.1', - '@storybook/preset-create-react-app': '8.0.0-rc.1', - '@storybook/preset-html-webpack': '8.0.0-rc.1', - '@storybook/preset-preact-webpack': '8.0.0-rc.1', - '@storybook/preset-react-webpack': '8.0.0-rc.1', - '@storybook/preset-server-webpack': '8.0.0-rc.1', - '@storybook/preset-svelte-webpack': '8.0.0-rc.1', - '@storybook/preset-vue3-webpack': '8.0.0-rc.1', - '@storybook/preview': '8.0.0-rc.1', - '@storybook/preview-api': '8.0.0-rc.1', - '@storybook/react': '8.0.0-rc.1', - '@storybook/react-dom-shim': '8.0.0-rc.1', - '@storybook/react-vite': '8.0.0-rc.1', - '@storybook/react-webpack5': '8.0.0-rc.1', - '@storybook/router': '8.0.0-rc.1', - '@storybook/server': '8.0.0-rc.1', - '@storybook/server-webpack5': '8.0.0-rc.1', - '@storybook/source-loader': '8.0.0-rc.1', - '@storybook/svelte': '8.0.0-rc.1', - '@storybook/svelte-vite': '8.0.0-rc.1', - '@storybook/svelte-webpack5': '8.0.0-rc.1', - '@storybook/sveltekit': '8.0.0-rc.1', - '@storybook/telemetry': '8.0.0-rc.1', - '@storybook/test': '8.0.0-rc.1', - '@storybook/theming': '8.0.0-rc.1', - '@storybook/types': '8.0.0-rc.1', - '@storybook/vue3': '8.0.0-rc.1', - '@storybook/vue3-vite': '8.0.0-rc.1', - '@storybook/vue3-webpack5': '8.0.0-rc.1', - '@storybook/web-components': '8.0.0-rc.1', - '@storybook/web-components-vite': '8.0.0-rc.1', - '@storybook/web-components-webpack5': '8.0.0-rc.1', - sb: '8.0.0-rc.1', - storybook: '8.0.0-rc.1', + '@storybook/addon-a11y': '8.0.0-rc.2', + '@storybook/addon-actions': '8.0.0-rc.2', + '@storybook/addon-backgrounds': '8.0.0-rc.2', + '@storybook/addon-controls': '8.0.0-rc.2', + '@storybook/addon-docs': '8.0.0-rc.2', + '@storybook/addon-essentials': '8.0.0-rc.2', + '@storybook/addon-highlight': '8.0.0-rc.2', + '@storybook/addon-interactions': '8.0.0-rc.2', + '@storybook/addon-jest': '8.0.0-rc.2', + '@storybook/addon-links': '8.0.0-rc.2', + '@storybook/addon-mdx-gfm': '8.0.0-rc.2', + '@storybook/addon-measure': '8.0.0-rc.2', + '@storybook/addon-onboarding': '8.0.0-rc.2', + '@storybook/addon-outline': '8.0.0-rc.2', + '@storybook/addon-storysource': '8.0.0-rc.2', + '@storybook/addon-themes': '8.0.0-rc.2', + '@storybook/addon-toolbars': '8.0.0-rc.2', + '@storybook/addon-viewport': '8.0.0-rc.2', + '@storybook/angular': '8.0.0-rc.2', + '@storybook/blocks': '8.0.0-rc.2', + '@storybook/builder-manager': '8.0.0-rc.2', + '@storybook/builder-vite': '8.0.0-rc.2', + '@storybook/builder-webpack5': '8.0.0-rc.2', + '@storybook/channels': '8.0.0-rc.2', + '@storybook/cli': '8.0.0-rc.2', + '@storybook/client-logger': '8.0.0-rc.2', + '@storybook/codemod': '8.0.0-rc.2', + '@storybook/components': '8.0.0-rc.2', + '@storybook/core-common': '8.0.0-rc.2', + '@storybook/core-events': '8.0.0-rc.2', + '@storybook/core-server': '8.0.0-rc.2', + '@storybook/core-webpack': '8.0.0-rc.2', + '@storybook/csf-plugin': '8.0.0-rc.2', + '@storybook/csf-tools': '8.0.0-rc.2', + '@storybook/docs-tools': '8.0.0-rc.2', + '@storybook/ember': '8.0.0-rc.2', + '@storybook/html': '8.0.0-rc.2', + '@storybook/html-vite': '8.0.0-rc.2', + '@storybook/html-webpack5': '8.0.0-rc.2', + '@storybook/instrumenter': '8.0.0-rc.2', + '@storybook/manager': '8.0.0-rc.2', + '@storybook/manager-api': '8.0.0-rc.2', + '@storybook/nextjs': '8.0.0-rc.2', + '@storybook/node-logger': '8.0.0-rc.2', + '@storybook/preact': '8.0.0-rc.2', + '@storybook/preact-vite': '8.0.0-rc.2', + '@storybook/preact-webpack5': '8.0.0-rc.2', + '@storybook/preset-create-react-app': '8.0.0-rc.2', + '@storybook/preset-html-webpack': '8.0.0-rc.2', + '@storybook/preset-preact-webpack': '8.0.0-rc.2', + '@storybook/preset-react-webpack': '8.0.0-rc.2', + '@storybook/preset-server-webpack': '8.0.0-rc.2', + '@storybook/preset-svelte-webpack': '8.0.0-rc.2', + '@storybook/preset-vue3-webpack': '8.0.0-rc.2', + '@storybook/preview': '8.0.0-rc.2', + '@storybook/preview-api': '8.0.0-rc.2', + '@storybook/react': '8.0.0-rc.2', + '@storybook/react-dom-shim': '8.0.0-rc.2', + '@storybook/react-vite': '8.0.0-rc.2', + '@storybook/react-webpack5': '8.0.0-rc.2', + '@storybook/router': '8.0.0-rc.2', + '@storybook/server': '8.0.0-rc.2', + '@storybook/server-webpack5': '8.0.0-rc.2', + '@storybook/source-loader': '8.0.0-rc.2', + '@storybook/svelte': '8.0.0-rc.2', + '@storybook/svelte-vite': '8.0.0-rc.2', + '@storybook/svelte-webpack5': '8.0.0-rc.2', + '@storybook/sveltekit': '8.0.0-rc.2', + '@storybook/telemetry': '8.0.0-rc.2', + '@storybook/test': '8.0.0-rc.2', + '@storybook/theming': '8.0.0-rc.2', + '@storybook/types': '8.0.0-rc.2', + '@storybook/vue3': '8.0.0-rc.2', + '@storybook/vue3-vite': '8.0.0-rc.2', + '@storybook/vue3-webpack5': '8.0.0-rc.2', + '@storybook/web-components': '8.0.0-rc.2', + '@storybook/web-components-vite': '8.0.0-rc.2', + '@storybook/web-components-webpack5': '8.0.0-rc.2', + sb: '8.0.0-rc.2', + storybook: '8.0.0-rc.2', }; diff --git a/code/lib/core-events/package.json b/code/lib/core-events/package.json index 03a63f346e27..8ada1c3ee9a4 100644 --- a/code/lib/core-events/package.json +++ b/code/lib/core-events/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/core-events", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Event names used in storybook core", "keywords": [ "storybook" diff --git a/code/lib/core-events/src/index.ts b/code/lib/core-events/src/index.ts index 6d98e6291200..7542d0aa4e57 100644 --- a/code/lib/core-events/src/index.ts +++ b/code/lib/core-events/src/index.ts @@ -1,5 +1,6 @@ // eslint-disable-next-line @typescript-eslint/naming-convention enum events { + CHANNEL_WS_DISCONNECT = 'channelWSDisconnect', CHANNEL_CREATED = 'channelCreated', // There was an error executing the config, likely an bug in the user's preview.js CONFIG_ERROR = 'configError', @@ -80,6 +81,7 @@ export default events; // Enables: `import * as Events from ...` or `import { CHANNEL_CREATED } as Events from ...` // This is the preferred method export const { + CHANNEL_WS_DISCONNECT, CHANNEL_CREATED, CONFIG_ERROR, CURRENT_STORY_WAS_SET, diff --git a/code/lib/core-server/package.json b/code/lib/core-server/package.json index 663facee0635..1d4f1d43c88b 100644 --- a/code/lib/core-server/package.json +++ b/code/lib/core-server/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/core-server", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" diff --git a/code/lib/core-webpack/package.json b/code/lib/core-webpack/package.json index 46f38e226ea1..84199b59fc20 100644 --- a/code/lib/core-webpack/package.json +++ b/code/lib/core-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/core-webpack", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" diff --git a/code/lib/csf-plugin/package.json b/code/lib/csf-plugin/package.json index c447831f6657..7c69964a2bf5 100644 --- a/code/lib/csf-plugin/package.json +++ b/code/lib/csf-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/csf-plugin", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Enrich CSF files via static analysis", "keywords": [ "storybook" diff --git a/code/lib/csf-tools/package.json b/code/lib/csf-tools/package.json index 5a32a79f9453..3aecb2b4c989 100644 --- a/code/lib/csf-tools/package.json +++ b/code/lib/csf-tools/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/csf-tools", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Parse and manipulate CSF and Storybook config files", "keywords": [ "storybook" @@ -49,7 +49,7 @@ "@storybook/csf": "^0.1.2", "@storybook/types": "workspace:*", "fs-extra": "^11.1.0", - "recast": "^0.23.1", + "recast": "^0.23.5", "ts-dedent": "^2.0.0" }, "devDependencies": { diff --git a/code/lib/docs-tools/package.json b/code/lib/docs-tools/package.json index 06e103124ee1..faead07c757d 100644 --- a/code/lib/docs-tools/package.json +++ b/code/lib/docs-tools/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/docs-tools", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Shared utility functions for frameworks to implement docs", "keywords": [ "storybook" diff --git a/code/lib/instrumenter/package.json b/code/lib/instrumenter/package.json index 32c7c06686a8..29aafd117e78 100644 --- a/code/lib/instrumenter/package.json +++ b/code/lib/instrumenter/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/instrumenter", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/manager-api/package.json b/code/lib/manager-api/package.json index a790395da7a3..6ce5d7c94478 100644 --- a/code/lib/manager-api/package.json +++ b/code/lib/manager-api/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/manager-api", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Core Storybook Manager API & Context", "keywords": [ "storybook" diff --git a/code/lib/manager-api/src/lib/events.ts b/code/lib/manager-api/src/lib/events.ts index c66c5a9e1403..1892d71ed57a 100644 --- a/code/lib/manager-api/src/lib/events.ts +++ b/code/lib/manager-api/src/lib/events.ts @@ -19,9 +19,13 @@ export const getEventMetadata = (context: Meta, fullAPI: API) => { const { source, refId, type } = context; const [sourceType, sourceLocation] = getSourceType(source!, refId); - const ref = - refId && fullAPI.getRefs()[refId] ? fullAPI.getRefs()[refId] : fullAPI.findRef(sourceLocation!); - + let ref: API_ComposedRef | undefined; + if (refId || sourceType === 'external') { + ref = + refId && fullAPI.getRefs()[refId] + ? fullAPI.getRefs()[refId] + : fullAPI.findRef(sourceLocation!); + } const meta = { source, sourceType, diff --git a/code/lib/manager-api/src/modules/refs.ts b/code/lib/manager-api/src/modules/refs.ts index 906798848e16..bc4b94755a53 100644 --- a/code/lib/manager-api/src/modules/refs.ts +++ b/code/lib/manager-api/src/modules/refs.ts @@ -20,8 +20,6 @@ import type { ModuleFn } from '../lib/types'; const { location, fetch } = global; -const findFilename = /(\/((?:[^\/]+?)\.[^\/]+?)|\/)$/; - export interface SubState { refs: API_Refs; } @@ -75,8 +73,10 @@ export const getSourceType = (source: string, refId?: string) => { const { origin: localOrigin, pathname: localPathname } = location; const { origin: sourceOrigin, pathname: sourcePathname } = new URL(source); - const localFull = `${localOrigin + localPathname}`.replace(findFilename, ''); - const sourceFull = `${sourceOrigin + sourcePathname}`.replace(findFilename, ''); + const localFull = `${localOrigin + localPathname}`.replace('/iframe.html', '').replace(/\/$/, ''); + const sourceFull = `${sourceOrigin + sourcePathname}` + .replace('/iframe.html', '') + .replace(/\/$/, ''); if (localFull === sourceFull) { return ['local', sourceFull]; diff --git a/code/lib/manager-api/src/tests/refs.test.ts b/code/lib/manager-api/src/tests/refs.test.ts index afdb0747e39e..791325c27337 100644 --- a/code/lib/manager-api/src/tests/refs.test.ts +++ b/code/lib/manager-api/src/tests/refs.test.ts @@ -25,7 +25,7 @@ vi.mock('@storybook/global', () => { // Add additional variations of global.location mock return values in this array. // NOTE: The order must match the order that global.location is called in the unit tests. const edgecaseLocations = [ - { origin: 'https://storybook.js.org', pathname: '/storybook/index.html' }, + { origin: 'https://storybook.js.org', pathname: '/storybook/iframe.html' }, ]; // global.location value after all edgecaseLocations are returned const lastLocation = { origin: 'https://storybook.js.org', pathname: '/storybook/' }; diff --git a/code/lib/manager-api/src/version.ts b/code/lib/manager-api/src/version.ts index 156fd9c6a94a..7900754c895d 100644 --- a/code/lib/manager-api/src/version.ts +++ b/code/lib/manager-api/src/version.ts @@ -1 +1 @@ -export const version = '8.0.0-rc.1'; +export const version = '8.0.0-rc.2'; diff --git a/code/lib/node-logger/package.json b/code/lib/node-logger/package.json index 12672e9c0278..eab0c64bfa00 100644 --- a/code/lib/node-logger/package.json +++ b/code/lib/node-logger/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/node-logger", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/preview-api/package.json b/code/lib/preview-api/package.json index 75d64c9a87b8..8706fb1e6f0e 100644 --- a/code/lib/preview-api/package.json +++ b/code/lib/preview-api/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preview-api", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/preview-api/src/index.ts b/code/lib/preview-api/src/index.ts index e47cdaa0a0dd..63d45114dc23 100644 --- a/code/lib/preview-api/src/index.ts +++ b/code/lib/preview-api/src/index.ts @@ -56,6 +56,7 @@ export { filterArgTypes, sanitizeStoryContextUpdate, setProjectAnnotations, + getPortableStoryWrapperId, inferControls, userOrAutoTitleFromSpecifier, userOrAutoTitle, diff --git a/code/lib/preview-api/src/modules/store/csf/portable-stories.test.ts b/code/lib/preview-api/src/modules/store/csf/portable-stories.test.ts index af775f27360b..dbca4640b05b 100644 --- a/code/lib/preview-api/src/modules/store/csf/portable-stories.test.ts +++ b/code/lib/preview-api/src/modules/store/csf/portable-stories.test.ts @@ -1,10 +1,18 @@ // @vitest-environment node import { describe, expect, vi, it } from 'vitest'; -import { composeStory, composeStories } from './portable-stories'; +import type { + ComponentAnnotations as Meta, + StoryAnnotationsOrFn as Story, + Store_CSFExports, +} from '@storybook/types'; + +import { composeStory, composeStories, setProjectAnnotations } from './portable-stories'; + +type StoriesModule = Store_CSFExports & Record; // Most integration tests for this functionality are located under renderers/react describe('composeStory', () => { - const meta = { + const meta: Meta = { title: 'Button', parameters: { firstAddon: true, @@ -15,13 +23,26 @@ describe('composeStory', () => { }, }; - it('should return story with composed args and parameters', () => { - const Story = () => {}; - Story.args = { primary: true }; - Story.parameters = { + it('should return story with composed annotations from story, meta and project', () => { + const decoratorFromProjectAnnotations = vi.fn((StoryFn) => StoryFn()); + const decoratorFromStoryAnnotations = vi.fn((StoryFn) => StoryFn()); + setProjectAnnotations([ + { + parameters: { injected: true }, + globalTypes: { + locale: { defaultValue: 'en' }, + }, + decorators: [decoratorFromProjectAnnotations], + }, + ]); + + const Story: Story = { + render: () => {}, + args: { primary: true }, parameters: { secondAddon: true, }, + decorators: [decoratorFromStoryAnnotations], }; const composedStory = composeStory(Story, meta); @@ -29,11 +50,16 @@ describe('composeStory', () => { expect(composedStory.parameters).toEqual( expect.objectContaining({ ...Story.parameters, ...meta.parameters }) ); + + composedStory(); + + expect(decoratorFromProjectAnnotations).toHaveBeenCalledOnce(); + expect(decoratorFromStoryAnnotations).toHaveBeenCalledOnce(); }); it('should compose with a play function', async () => { const spy = vi.fn(); - const Story = () => {}; + const Story: Story = () => {}; Story.args = { primary: true, }; @@ -53,6 +79,80 @@ describe('composeStory', () => { ); }); + it('should merge parameters with correct precedence in all combinations', async () => { + const storyAnnotations = { render: () => {} }; + const metaAnnotations: Meta = { parameters: { label: 'meta' } }; + const projectAnnotations: Meta = { parameters: { label: 'projectOverrides' } }; + + const storyPrecedence = composeStory( + { ...storyAnnotations, parameters: { label: 'story' } }, + metaAnnotations, + projectAnnotations + ); + expect(storyPrecedence.parameters.label).toEqual('story'); + + const metaPrecedence = composeStory(storyAnnotations, metaAnnotations, projectAnnotations); + expect(metaPrecedence.parameters.label).toEqual('meta'); + + const projectPrecedence = composeStory(storyAnnotations, {}, projectAnnotations); + expect(projectPrecedence.parameters.label).toEqual('projectOverrides'); + + setProjectAnnotations({ parameters: { label: 'setProjectAnnotationsOverrides' } }); + const setProjectAnnotationsPrecedence = composeStory(storyAnnotations, {}, {}); + expect(setProjectAnnotationsPrecedence.parameters.label).toEqual( + 'setProjectAnnotationsOverrides' + ); + }); + + it('should call and compose loaders data', async () => { + const loadSpy = vi.fn(); + const args = { story: 'story' }; + const LoaderStory: Story = { + args, + loaders: [ + async (context) => { + loadSpy(); + expect(context.args).toEqual(args); + return { + foo: 'bar', + }; + }, + ], + render: (_args, { loaded }) => { + expect(loaded).toEqual({ foo: 'bar' }); + }, + }; + + const composedStory = composeStory(LoaderStory, {}); + await composedStory.load(); + composedStory(); + expect(loadSpy).toHaveBeenCalled(); + }); + + it('should work with spies set up in loaders', async () => { + const spyFn = vi.fn(); + + const Story: Story = { + args: { + spyFn, + }, + loaders: [ + async () => { + spyFn.mockReturnValue('mockedData'); + }, + ], + render: (args) => { + const data = args.spyFn(); + expect(data).toBe('mockedData'); + }, + }; + + const composedStory = composeStory(Story, {}); + await composedStory.load(); + composedStory(); + expect(spyFn).toHaveBeenCalled(); + }); + it('should throw an error if Story is undefined', () => { expect(() => { // @ts-expect-error (invalid input) @@ -62,7 +162,7 @@ describe('composeStory', () => { describe('Id of the story', () => { it('is exposed correctly when composeStories is used', () => { - const module = { + const module: StoriesModule = { default: { title: 'Example/Button', }, @@ -72,7 +172,7 @@ describe('composeStory', () => { expect(Primary.id).toBe('example-button--csf-3-primary'); }); it('is exposed correctly when composeStory is used and exportsName is passed', () => { - const module = { + const module: StoriesModule = { default: { title: 'Example/Button', }, @@ -83,7 +183,7 @@ describe('composeStory', () => { }); it("is not unique when composeStory is used and exportsName isn't passed", () => { const Primary = composeStory({ render: () => {} }, {}); - expect(Primary.id).toContain('unknown'); + expect(Primary.id).toContain('composedstory--unnamed-story'); }); }); }); @@ -93,7 +193,7 @@ describe('composeStories', () => { const defaultAnnotations = { render: () => '' }; it('should call composeStoryFn with stories', () => { const composeStorySpy = vi.fn((v) => v); - const module = { + const module: StoriesModule = { default: { title: 'Button', }, @@ -118,7 +218,7 @@ describe('composeStories', () => { it('should not call composeStoryFn for non-story exports', () => { const composeStorySpy = vi.fn((v) => v); - const module = { + const module: StoriesModule = { default: { title: 'Button', excludeStories: /Data/, @@ -131,7 +231,7 @@ describe('composeStories', () => { describe('non-story exports', () => { it('should filter non-story exports with excludeStories', () => { - const StoryModuleWithNonStoryExports = { + const StoryModuleWithNonStoryExports: StoriesModule = { default: { title: 'Some/Component', excludeStories: /.*Data/, @@ -149,7 +249,7 @@ describe('composeStories', () => { }); it('should filter non-story exports with includeStories', () => { - const StoryModuleWithNonStoryExports = { + const StoryModuleWithNonStoryExports: StoriesModule = { default: { title: 'Some/Component', includeStories: /.*Story/, diff --git a/code/lib/preview-api/src/modules/store/csf/portable-stories.ts b/code/lib/preview-api/src/modules/store/csf/portable-stories.ts index 91e4cdc365e1..0974f0908526 100644 --- a/code/lib/preview-api/src/modules/store/csf/portable-stories.ts +++ b/code/lib/preview-api/src/modules/store/csf/portable-stories.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import { isExportStory } from '@storybook/csf'; import type { Renderer, @@ -12,7 +13,7 @@ import type { Parameters, ComposedStoryFn, StrictArgTypes, - ComposedStoryPlayContext, + PlayFunctionContext, } from '@storybook/types'; import { HooksContext } from '../../../addons'; @@ -23,23 +24,28 @@ import { normalizeComponentAnnotations } from './normalizeComponentAnnotations'; import { getValuesFromArgTypes } from './getValuesFromArgTypes'; import { normalizeProjectAnnotations } from './normalizeProjectAnnotations'; -let GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS = composeConfigs([]); +let globalProjectAnnotations: ProjectAnnotations = {}; + +export function getPortableStoryWrapperId(storyId: string) { + return `storybook-story-${storyId}`; +} export function setProjectAnnotations( projectAnnotations: ProjectAnnotations | ProjectAnnotations[] ) { const annotations = Array.isArray(projectAnnotations) ? projectAnnotations : [projectAnnotations]; - GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS = composeConfigs(annotations); + globalProjectAnnotations = composeConfigs(annotations); } export function composeStory( storyAnnotations: LegacyStoryAnnotationsOrFn, componentAnnotations: ComponentAnnotations, - projectAnnotations: ProjectAnnotations = GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS as ProjectAnnotations, - defaultConfig: ProjectAnnotations = {}, + projectAnnotations?: ProjectAnnotations, + defaultConfig?: ProjectAnnotations, exportsName?: string ): ComposedStoryFn> { if (storyAnnotations === undefined) { + // eslint-disable-next-line local-rules/no-uncategorized-errors throw new Error('Expected a story but received undefined.'); } @@ -54,7 +60,7 @@ export function composeStory( storyName, @@ -62,10 +68,9 @@ export function composeStory({ - ...projectAnnotations, - ...defaultConfig, - }); + const normalizedProjectAnnotations = normalizeProjectAnnotations( + composeConfigs([defaultConfig ?? {}, globalProjectAnnotations, projectAnnotations ?? {}]) + ); const story = prepareStory( normalizedStory, @@ -73,11 +78,14 @@ export function composeStory = { hooks: new HooksContext(), - globals: defaultGlobals, + globals: { + ...globalsFromGlobalTypes, + ...normalizedProjectAnnotations.globals, + }, args: { ...story.initialArgs }, viewMode: 'story', loaded: {}, @@ -86,28 +94,39 @@ export function composeStory>) => + story.playFunction!({ + ...context, + ...extraContext, + // if canvasElement is not provided, we default to the root element, which comes from a decorator + // the decorator has to be implemented in the defaultAnnotations of each integrator of portable stories + canvasElement: + extraContext?.canvasElement ?? + globalThis.document?.getElementById(getPortableStoryWrapperId(context.id)), + }) + : undefined; + const composedStory: ComposedStoryFn> = Object.assign( - (extraArgs?: Partial) => { - const finalContext: StoryContext = { - ...context, - args: { ...context.initialArgs, ...extraArgs }, + function storyFn(extraArgs?: Partial) { + context.args = { + ...context.initialArgs, + ...extraArgs, }; - return story.unboundStoryFn(prepareContext(finalContext)); + return story.unboundStoryFn(prepareContext(context)); }, { + id: story.id, storyName, + load: async () => { + const loadedContext = await story.applyLoaders(context); + context.loaded = loadedContext.loaded; + }, args: story.initialArgs as Partial, parameters: story.parameters as Parameters, argTypes: story.argTypes as StrictArgTypes, - id: story.id, - play: story.playFunction - ? ((async (extraContext: ComposedStoryPlayContext) => - story.playFunction!({ - ...context, - ...extraContext, - })) as unknown as ComposedStoryPlayFn>) - : undefined, + play: playFunction as ComposedStoryPlayFn | undefined, } ); @@ -119,7 +138,6 @@ export function composeStories( globalConfig: ProjectAnnotations, composeStoryFn: ComposeStoryFn ) { - // eslint-disable-next-line @typescript-eslint/naming-convention const { default: meta, __esModule, __namedExportsOrder, ...stories } = storiesImport; const composedStories = Object.entries(stories).reduce((storiesMap, [exportsName, story]) => { if (!isExportStory(exportsName, meta)) { diff --git a/code/lib/preview/package.json b/code/lib/preview/package.json index cea251ed2dad..d7f6a6bf9713 100644 --- a/code/lib/preview/package.json +++ b/code/lib/preview/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preview", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/react-dom-shim/package.json b/code/lib/react-dom-shim/package.json index 417322b679f2..52f45ceba617 100644 --- a/code/lib/react-dom-shim/package.json +++ b/code/lib/react-dom-shim/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-dom-shim", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/router/package.json b/code/lib/router/package.json index 28bc6525c2d2..79520d10ffbb 100644 --- a/code/lib/router/package.json +++ b/code/lib/router/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/router", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Core Storybook Router", "keywords": [ "storybook" diff --git a/code/lib/source-loader/package.json b/code/lib/source-loader/package.json index 634c33cf3d8c..dfd1ee424bc4 100644 --- a/code/lib/source-loader/package.json +++ b/code/lib/source-loader/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/source-loader", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Source loader", "keywords": [ "lib", diff --git a/code/lib/telemetry/package.json b/code/lib/telemetry/package.json index bacd55a2207c..430a2add1bdf 100644 --- a/code/lib/telemetry/package.json +++ b/code/lib/telemetry/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/telemetry", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Telemetry logging for crash reports and usage statistics", "keywords": [ "storybook" diff --git a/code/lib/test/package.json b/code/lib/test/package.json index 5258ec216e55..d0ae52a03d6f 100644 --- a/code/lib/test/package.json +++ b/code/lib/test/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/test", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/theming/package.json b/code/lib/theming/package.json index dc6f964b604e..ab85acf2481c 100644 --- a/code/lib/theming/package.json +++ b/code/lib/theming/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/theming", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Core Storybook Components", "keywords": [ "storybook" diff --git a/code/lib/types/package.json b/code/lib/types/package.json index 96f6089b73dc..0eb61cdce388 100644 --- a/code/lib/types/package.json +++ b/code/lib/types/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/types", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Core Storybook TS Types", "keywords": [ "storybook" diff --git a/code/lib/types/src/modules/composedStory.ts b/code/lib/types/src/modules/composedStory.ts index 5ce61bc678e8..b0a7bff6c374 100644 --- a/code/lib/types/src/modules/composedStory.ts +++ b/code/lib/types/src/modules/composedStory.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import type { Renderer, StoryId, StrictArgTypes } from '@storybook/csf'; +import type { PlayFunction, Renderer, StoryId, StrictArgTypes } from '@storybook/csf'; import type { AnnotatedStoryFn, @@ -9,7 +9,6 @@ import type { Parameters, StoryAnnotations, StoryAnnotationsOrFn, - StoryContext, } from './csf'; import type { ProjectAnnotations } from './story'; @@ -22,23 +21,6 @@ export type Store_CSFExports) // or PrimaryButton() - * PrimaryButton.play({ canvasElement: container }) - */ -export type ComposedStoryPlayContext = Partial< - StoryContext & Pick, 'canvasElement'> ->; - -export type ComposedStoryPlayFn = ( - context: ComposedStoryPlayContext -) => Promise | void; - /** * A story function with partial args, used internally by composeStory */ @@ -48,6 +30,14 @@ export type PartialArgsStoryFn = T extends (...args: infer P) => infer R + ? (...args: { [K in keyof P]?: Partial }) => R + : never; + +export type ComposedStoryPlayFn< + TRenderer extends Renderer = Renderer, + TArgs = Args, +> = MakeAllParametersOptional>>; /** * A story that got recomposed for portable stories, containing all the necessary data to be rendered in external environments */ @@ -55,9 +45,10 @@ export type ComposedStoryFn< TRenderer extends Renderer = Renderer, TArgs = Args, > = PartialArgsStoryFn & { - play: ComposedStoryPlayFn | undefined; args: TArgs; id: StoryId; + play?: ComposedStoryPlayFn; + load: () => Promise; storyName: string; parameters: Parameters; argTypes: StrictArgTypes; diff --git a/code/package.json b/code/package.json index a1e4e3953e1e..7b888ff6e9d2 100644 --- a/code/package.json +++ b/code/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/root", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "private": true, "description": "Storybook root", "homepage": "https://storybook.js.org/", @@ -80,7 +80,7 @@ "@storybook/theming": "workspace:*", "@types/node": "^18.0.0", "@vitest/expect@1.1.3": "patch:@vitest/expect@npm%3A1.1.3#~/.yarn/patches/@vitest-expect-npm-1.1.3-2062bf533f.patch", - "esbuild": "^0.18.0", + "esbuild": "^0.20.1", "playwright": "1.36.0", "playwright-core": "1.36.0", "serialize-javascript": "^3.1.0", @@ -190,7 +190,7 @@ "concurrently": "^5.3.0", "cross-env": "^7.0.3", "danger": "^11.2.6", - "esbuild": "^0.18.0", + "esbuild": "^18.0.0 || ^19.0.0 || ^0.20.0", "esbuild-loader": "^3.0.0", "esbuild-plugin-alias": "^0.2.1", "eslint": "^8.56.0", @@ -214,7 +214,7 @@ "react-dom": "^18.2.0", "semver": "^7.3.7", "serve-static": "^1.14.1", - "svelte": "^5.0.0-next.28", + "svelte": "^5.0.0-next.65", "trash": "^7.0.0", "ts-dedent": "^2.0.0", "ts-node": "^10.9.1", diff --git a/code/presets/create-react-app/package.json b/code/presets/create-react-app/package.json index 5275fade6100..ed53ffee0fe9 100644 --- a/code/presets/create-react-app/package.json +++ b/code/presets/create-react-app/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-create-react-app", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for Create React App preset", "keywords": [ "storybook" diff --git a/code/presets/html-webpack/package.json b/code/presets/html-webpack/package.json index c85346ee246a..1ca061b42459 100644 --- a/code/presets/html-webpack/package.json +++ b/code/presets/html-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-html-webpack", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/presets/preact-webpack/package.json b/code/presets/preact-webpack/package.json index 0edddb4d473d..0fff0481f38d 100644 --- a/code/presets/preact-webpack/package.json +++ b/code/presets/preact-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-preact-webpack", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for Preact: Develop Preact Component in isolation.", "keywords": [ "storybook" diff --git a/code/presets/react-webpack/package.json b/code/presets/react-webpack/package.json index bb7d68782f68..dd5f11b03793 100644 --- a/code/presets/react-webpack/package.json +++ b/code/presets/react-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-react-webpack", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for React: Develop React Component in isolation with Hot Reloading", "keywords": [ "storybook" @@ -71,11 +71,13 @@ "@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.0c3f3b7.0", "@types/node": "^18.0.0", "@types/semver": "^7.3.4", + "find-up": "^5.0.0", "fs-extra": "^11.1.0", "magic-string": "^0.30.5", "react-docgen": "^7.0.0", "resolve": "^1.22.8", "semver": "^7.3.7", + "tsconfig-paths": "^4.2.0", "webpack": "5" }, "devDependencies": { diff --git a/code/presets/react-webpack/src/loaders/react-docgen-loader.test.ts b/code/presets/react-webpack/src/loaders/react-docgen-loader.test.ts new file mode 100644 index 000000000000..cb017a7469b7 --- /dev/null +++ b/code/presets/react-webpack/src/loaders/react-docgen-loader.test.ts @@ -0,0 +1,52 @@ +import { getReactDocgenImporter } from './react-docgen-loader'; +import { describe, it, expect, vi } from 'vitest'; + +const reactDocgenMock = vi.hoisted(() => { + return { + makeFsImporter: vi.fn().mockImplementation((fn) => fn), + }; +}); + +const reactDocgenResolverMock = vi.hoisted(() => { + return { + defaultLookupModule: vi.fn(), + }; +}); + +vi.mock('./docgen-resolver', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + defaultLookupModule: reactDocgenResolverMock.defaultLookupModule, + }; +}); + +vi.mock('react-docgen', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + makeFsImporter: reactDocgenMock.makeFsImporter, + }; +}); + +describe('getReactDocgenImporter function', () => { + it('should not map the request if a tsconfig path mapping is not available', () => { + const filename = './src/components/Button.tsx'; + const basedir = '/src'; + const imported = getReactDocgenImporter(undefined); + reactDocgenResolverMock.defaultLookupModule.mockImplementation((filen: string) => filen); + const result = (imported as any)(filename, basedir); + expect(result).toBe(filename); + }); + + it('should map the request', () => { + const mappedFile = './mapped-file.tsx'; + const matchPath = vi.fn().mockReturnValue(mappedFile); + const filename = './src/components/Button.tsx'; + const basedir = '/src'; + const imported = getReactDocgenImporter(matchPath); + reactDocgenResolverMock.defaultLookupModule.mockImplementation((filen: string) => filen); + const result = (imported as any)(filename, basedir); + expect(result).toBe(mappedFile); + }); +}); diff --git a/code/presets/react-webpack/src/loaders/react-docgen-loader.ts b/code/presets/react-webpack/src/loaders/react-docgen-loader.ts index 12ab911fd546..15b71f19bfd5 100644 --- a/code/presets/react-webpack/src/loaders/react-docgen-loader.ts +++ b/code/presets/react-webpack/src/loaders/react-docgen-loader.ts @@ -6,6 +6,8 @@ import { ERROR_CODES, utils, } from 'react-docgen'; +import * as TsconfigPaths from 'tsconfig-paths'; +import findUp from 'find-up'; import MagicString from 'magic-string'; import type { LoaderContext } from 'webpack'; import type { Handler, NodePath, babelTypes as t, Documentation } from 'react-docgen'; @@ -62,6 +64,9 @@ const defaultHandlers = Object.values(docgenHandlers).map((handler) => handler); const defaultResolver = new docgenResolver.FindExportedDefinitionsResolver(); const handlers = [...defaultHandlers, actualNameHandler]; +let tsconfigPathsInitialized = false; +let matchPath: TsconfigPaths.MatchPath | undefined; + export default async function reactDocgenLoader( this: LoaderContext<{ debug: boolean }>, source: string @@ -71,20 +76,28 @@ export default async function reactDocgenLoader( const options = this.getOptions() || {}; const { debug = false } = options; + if (!tsconfigPathsInitialized) { + const tsconfigPath = await findUp('tsconfig.json', { cwd: process.cwd() }); + const tsconfig = TsconfigPaths.loadConfig(tsconfigPath); + + if (tsconfig.resultType === 'success') { + logger.info('Using tsconfig paths for react-docgen'); + matchPath = TsconfigPaths.createMatchPath(tsconfig.absoluteBaseUrl, tsconfig.paths, [ + 'browser', + 'module', + 'main', + ]); + } + + tsconfigPathsInitialized = true; + } + try { const docgenResults = parse(source, { filename: this.resourcePath, resolver: defaultResolver, handlers, - importer: makeFsImporter((filename, basedir) => { - const result = defaultLookupModule(filename, basedir); - - if (RESOLVE_EXTENSIONS.find((ext) => result.endsWith(ext))) { - return result; - } - - throw new ReactDocgenResolveError(filename); - }), + importer: getReactDocgenImporter(matchPath), babelOptions: { babelrc: false, configFile: false, @@ -122,3 +135,24 @@ export default async function reactDocgenLoader( } } } + +export function getReactDocgenImporter(matchingPath: TsconfigPaths.MatchPath | undefined) { + return makeFsImporter((filename, basedir) => { + const mappedFilenameByPaths = (() => { + if (matchingPath) { + const match = matchingPath(filename); + return match || filename; + } else { + return filename; + } + })(); + + const result = defaultLookupModule(mappedFilenameByPaths, basedir); + + if (RESOLVE_EXTENSIONS.find((ext) => result.endsWith(ext))) { + return result; + } + + throw new ReactDocgenResolveError(filename); + }); +} diff --git a/code/presets/server-webpack/package.json b/code/presets/server-webpack/package.json index 0c57f088153d..39e509374b67 100644 --- a/code/presets/server-webpack/package.json +++ b/code/presets/server-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-server-webpack", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/presets/svelte-webpack/package.json b/code/presets/svelte-webpack/package.json index 8e174bae7e10..efcbd040827c 100644 --- a/code/presets/svelte-webpack/package.json +++ b/code/presets/svelte-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-svelte-webpack", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -75,7 +75,7 @@ "typescript": "^5.3.2" }, "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.16", + "svelte": "^4.0.0 || ^5.0.0-next.65", "svelte-loader": "*" }, "engines": { diff --git a/code/presets/vue3-webpack/package.json b/code/presets/vue3-webpack/package.json index 774e7247eafb..f19a6e689a0e 100644 --- a/code/presets/vue3-webpack/package.json +++ b/code/presets/vue3-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-vue3-webpack", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook for Vue 3: Develop Vue 3 Components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/renderers/html/package.json b/code/renderers/html/package.json index ce6c017145aa..d65de0340b40 100644 --- a/code/renderers/html/package.json +++ b/code/renderers/html/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook HTML renderer", "keywords": [ "storybook" diff --git a/code/renderers/preact/package.json b/code/renderers/preact/package.json index 685eb8e50aaa..964a280e875c 100644 --- a/code/renderers/preact/package.json +++ b/code/renderers/preact/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook Preact renderer", "keywords": [ "storybook" diff --git a/code/renderers/react/package.json b/code/renderers/react/package.json index de60e151b77c..a42882c38ebf 100644 --- a/code/renderers/react/package.json +++ b/code/renderers/react/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook React renderer", "keywords": [ "storybook" diff --git a/code/renderers/react/src/__test__/Button.stories.tsx b/code/renderers/react/src/__test__/Button.stories.tsx index 6882b957b136..277f92ddde1f 100644 --- a/code/renderers/react/src/__test__/Button.stories.tsx +++ b/code/renderers/react/src/__test__/Button.stories.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { within, userEvent } from '@storybook/testing-library'; +import { within, userEvent, fn, expect } from '@storybook/test'; import type { StoryFn as CSF2Story, StoryObj as CSF3Story, Meta } from '..'; import type { ButtonProps } from './Button'; @@ -33,14 +33,21 @@ const getCaptionForLocale = (locale: string) => { return 'μ•ˆλ…•ν•˜μ„Έμš”!'; case 'pt': return 'OlΓ‘!'; - default: + case 'en': return 'Hello!'; + default: + return undefined; } }; export const CSF2StoryWithLocale: CSF2Story = (args, { globals: { locale } }) => { const caption = getCaptionForLocale(locale); - return ; + return ( + <> +

locale: {locale}

+ + + ); }; CSF2StoryWithLocale.storyName = 'WithLocale'; @@ -84,7 +91,36 @@ export const CSF3InputFieldFilled: CSF3Story = { play: async ({ canvasElement, step }) => { const canvas = within(canvasElement); await step('Step label', async () => { - await userEvent.type(canvas.getByTestId('input'), 'Hello world!'); + const inputEl = canvas.getByTestId('input'); + await userEvent.type(inputEl, 'Hello world!'); + await expect(inputEl).toHaveValue('Hello world!'); }); }, }; + +const mockFn = fn(); +export const LoaderStory: CSF3Story<{ mockFn: (val: string) => string }> = { + args: { + mockFn, + }, + loaders: [ + async () => { + mockFn.mockReturnValueOnce('mockFn return value'); + return { + value: 'loaded data', + }; + }, + ], + render: (args, { loaded }) => { + const data = args.mockFn('render'); + return ( +
+
{loaded.value}
+
{String(data)}
+
+ ); + }, + play: async () => { + expect(mockFn).toHaveBeenCalledWith('render'); + }, +}; diff --git a/code/renderers/react/src/__test__/__snapshots__/portable-stories.test.tsx.snap b/code/renderers/react/src/__test__/__snapshots__/portable-stories.test.tsx.snap index 2b92b1d68424..2779b21001ab 100644 --- a/code/renderers/react/src/__test__/__snapshots__/portable-stories.test.tsx.snap +++ b/code/renderers/react/src/__test__/__snapshots__/portable-stories.test.tsx.snap @@ -3,94 +3,135 @@ exports[`Renders CSF2Secondary story 1`] = `
- + +
`; -exports[`Renders CSF2StoryWithLocale story 1`] = ` +exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = `
- + +
`; -exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = ` +exports[`Renders CSF3Button story 1`] = `
- + +
`; -exports[`Renders CSF3Button story 1`] = ` +exports[`Renders CSF3ButtonWithRender story 1`] = `
- +
+

+ I am a custom render function +

+ +
+
`; -exports[`Renders CSF3ButtonWithRender story 1`] = ` +exports[`Renders CSF3InputFieldFilled story 1`] = `
-
-

- I am a custom render function -

- +
+
`; -exports[`Renders CSF3InputFieldFilled story 1`] = ` +exports[`Renders CSF3Primary story 1`] = `
- +
+ +
`; -exports[`Renders CSF3Primary story 1`] = ` +exports[`Renders LoaderStory story 1`] = `
- +
+
+ loaded data +
+
+ mockFn return value +
+
+
`; diff --git a/code/renderers/react/src/__test__/portable-stories.test.tsx b/code/renderers/react/src/__test__/portable-stories.test.tsx index afa0b70142e4..f8aae6b849f4 100644 --- a/code/renderers/react/src/__test__/portable-stories.test.tsx +++ b/code/renderers/react/src/__test__/portable-stories.test.tsx @@ -1,5 +1,5 @@ -import { vi, it, expect, afterEach, describe } from 'vitest'; import React from 'react'; +import { vi, it, expect, afterEach, describe } from 'vitest'; import { render, screen, cleanup } from '@testing-library/react'; import { addons } from '@storybook/preview-api'; import type { Meta } from '@storybook/react'; @@ -10,7 +10,7 @@ import type { Button } from './Button'; import * as stories from './Button.stories'; // example with composeStories, returns an object with all stories composed with args/decorators -const { CSF3Primary } = composeStories(stories); +const { CSF3Primary, LoaderStory } = composeStories(stories); // example with composeStory, returns a single story composed with args/decorators const Secondary = composeStory(stories.CSF2Secondary, stories.default); @@ -44,6 +44,15 @@ describe('renders', () => { const buttonElement = getByText(/foo/i); expect(buttonElement).not.toBeNull(); }); + + 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.play!(); + }); }); describe('projectAnnotations', () => { @@ -52,15 +61,24 @@ describe('projectAnnotations', () => { }); it('renders with default projectAnnotations', () => { + setProjectAnnotations([ + { + parameters: { injected: true }, + globalTypes: { + locale: { defaultValue: 'en' }, + }, + }, + ]); const WithEnglishText = composeStory(stories.CSF2StoryWithLocale, stories.default); 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(stories.CSF2StoryWithLocale, stories.default, { - globalTypes: { locale: { defaultValue: 'pt' } } as any, + globals: { locale: 'pt' }, }); const { getByText } = render(); const buttonElement = getByText('OlΓ‘!'); @@ -94,7 +112,18 @@ describe('CSF3', () => { expect(screen.getByTestId('custom-render')).not.toBeNull(); }); - it('renders with play function', async () => { + it('renders with play function without canvas element', async () => { + const CSF3InputFieldFilled = composeStory(stories.CSF3InputFieldFilled, stories.default); + + render(); + + await CSF3InputFieldFilled.play!(); + + 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(stories.CSF3InputFieldFilled, stories.default); const { container } = render(); @@ -139,9 +168,20 @@ describe('ComposeStories types', () => { }); // Batch snapshot testing -const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName, Story]); +const testCases = Object.values(composeStories(stories)).map( + (Story) => [Story.storyName, Story] as [string, typeof Story] +); it.each(testCases)('Renders %s story', async (_storyName, Story) => { cleanup(); - const tree = await render(); - expect(tree.baseElement).toMatchSnapshot(); + + if (_storyName === 'CSF2StoryWithLocale') { + return; + } + + await Story.load(); + + const { baseElement } = await render(); + + await Story.play?.(); + expect(baseElement).toMatchSnapshot(); }); diff --git a/code/renderers/react/src/portable-stories.ts b/code/renderers/react/src/portable-stories.tsx similarity index 93% rename from code/renderers/react/src/portable-stories.ts rename to code/renderers/react/src/portable-stories.tsx index 385e0dc4c804..3493e0f3b2e5 100644 --- a/code/renderers/react/src/portable-stories.ts +++ b/code/renderers/react/src/portable-stories.tsx @@ -1,7 +1,9 @@ +import React from 'react'; import { composeStory as originalComposeStory, composeStories as originalComposeStories, setProjectAnnotations as originalSetProjectAnnotations, + getPortableStoryWrapperId, } from '@storybook/preview-api'; import type { Args, @@ -11,7 +13,7 @@ import type { StoriesWithPartialProps, } from '@storybook/types'; -import { render } from './render'; +import * as reactProjectAnnotations from './entry-preview'; import type { Meta } from './public-types'; import type { ReactRenderer } from './types'; @@ -38,7 +40,16 @@ export function setProjectAnnotations( // This will not be necessary once we have auto preset loading const defaultProjectAnnotations: ProjectAnnotations = { - render, + ...reactProjectAnnotations, + decorators: [ + function addStorybookId(StoryFn, { id }) { + return ( +
+ +
+ ); + }, + ], }; /** diff --git a/code/renderers/server/package.json b/code/renderers/server/package.json index f4c892e2a87a..e17abd07240a 100644 --- a/code/renderers/server/package.json +++ b/code/renderers/server/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/server", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook Server renderer", "keywords": [ "storybook" diff --git a/code/renderers/svelte/package.json b/code/renderers/svelte/package.json index abe9ca50ab14..f33c169654cd 100644 --- a/code/renderers/svelte/package.json +++ b/code/renderers/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook Svelte renderer", "keywords": [ "storybook" @@ -30,16 +30,15 @@ "./dist/entry-preview.mjs": "./dist/entry-preview.mjs", "./dist/entry-preview-docs.mjs": "./dist/entry-preview-docs.mjs", "./package.json": "./package.json", - "./templates/HOC.svelte": "./templates/HOC.svelte", - "./templates/PreviewRender.svelte": "./templates/PreviewRender.svelte", - "./templates/SlotDecorator.svelte": "./templates/SlotDecorator.svelte" + "./internal/PreviewRender.svelte": "./dist/components/PreviewRender.svelte", + "./internal/SlotDecorator.svelte": "./dist/components/SlotDecorator.svelte", + "./internal/createSvelte5Props": "./dist/createSvelte5Props.svelte.js" }, "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", "files": [ "dist/**/*", - "templates/**/*", "template/cli/**/*", "README.md", "*.js", @@ -62,14 +61,15 @@ "type-fest": "~2.19" }, "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.1", + "@sveltejs/vite-plugin-svelte": "^3.0.2", "expect-type": "^0.15.0", - "svelte": "^5.0.0-next.28", - "svelte-check": "^3.6.1", + "fs-extra": "^11.1.0", + "svelte": "^5.0.0-next.65", + "svelte-check": "^3.6.4", "typescript": "^5.3.2" }, "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.16" + "svelte": "^4.0.0 || ^5.0.0-next.65" }, "engines": { "node": ">=18.0.0" @@ -78,6 +78,7 @@ "access": "public" }, "bundler": { + "post": "./scripts/copy-unbundled-to-dist.ts", "entries": [ "./src/index.ts", "./src/preset.ts", diff --git a/code/renderers/svelte/scripts/copy-unbundled-to-dist.ts b/code/renderers/svelte/scripts/copy-unbundled-to-dist.ts new file mode 100644 index 000000000000..a82964679960 --- /dev/null +++ b/code/renderers/svelte/scripts/copy-unbundled-to-dist.ts @@ -0,0 +1,23 @@ +import { copy } from 'fs-extra'; +import { join } from 'path'; + +const src = join(__dirname, '..', 'src'); +const dist = join(__dirname, '..', 'dist'); + +// relative to src directory +const PATHS_TO_COPY = ['createSvelte5Props.svelte.js', 'components']; + +const run = async () => { + console.log('Copying unbundled files to dist...'); + await Promise.all( + PATHS_TO_COPY.map((pathToCopy) => + copy(join(src, pathToCopy), join(dist, pathToCopy), { overwrite: true }) + ) + ); + console.log('Done!'); +}; + +run().catch((e) => { + console.error(e); + process.exitCode = 1; +}); diff --git a/code/renderers/svelte/templates/PreviewRender.svelte b/code/renderers/svelte/src/components/PreviewRender.svelte similarity index 69% rename from code/renderers/svelte/templates/PreviewRender.svelte rename to code/renderers/svelte/src/components/PreviewRender.svelte index 581aa4d78e05..b2900a0ddb24 100644 --- a/code/renderers/svelte/templates/PreviewRender.svelte +++ b/code/renderers/svelte/src/components/PreviewRender.svelte @@ -3,7 +3,7 @@ import { dedent } from 'ts-dedent'; export let name; - export let kind; + export let title; export let storyFn; export let showError; export let storyContext; @@ -17,22 +17,22 @@ on, } = storyFn(); - let firstTime = true; - - // the first time we don't want to call storyFn two times so we just return the values - // we already have from the previous call. If storyFn changes this function will run - // again but this time firstTime will be false - function getStoryFnValue(storyFn){ - if(firstTime){ - firstTime = false; - return { - Component, - props, - on, - } - } - return storyFn(); - } + let firstTime = true; + + // the first time we don't want to call storyFn two times so we just return the values + // we already have from the previous call. If storyFn changes this function will run + // again but this time firstTime will be false + function getStoryFnValue(storyFn) { + if (firstTime) { + firstTime = false; + return { + Component, + props, + on, + }; + } + return storyFn(); + } // reactive, re-render on storyFn change $: ({ Component, props = {}, on } = getStoryFnValue(storyFn)); @@ -45,7 +45,7 @@ if (!Component) { showError({ - title: `Expecting a Svelte component from the story: "${name}" of "${kind}".`, + title: `Expecting a Svelte component from the story: "${name}" of "${title}".`, description: dedent` Did you forget to return the Svelte component configuration from the story? Use "() => ({ Component: YourComponent, props: {} })" diff --git a/code/renderers/svelte/templates/SlotDecorator.svelte b/code/renderers/svelte/src/components/SlotDecorator.svelte similarity index 100% rename from code/renderers/svelte/templates/SlotDecorator.svelte rename to code/renderers/svelte/src/components/SlotDecorator.svelte diff --git a/code/renderers/svelte/src/createSvelte5Props.svelte.js b/code/renderers/svelte/src/createSvelte5Props.svelte.js new file mode 100644 index 000000000000..bbefc19bbf43 --- /dev/null +++ b/code/renderers/svelte/src/createSvelte5Props.svelte.js @@ -0,0 +1,13 @@ +/** + * Turns an object into reactive props in Svelte 5. + * Needs to be in a separate .svelte.js file to ensure Svelte + * compiles it. + * As proposed in https://github.com/sveltejs/svelte/issues/9827#issuecomment-1845589616 + * @template TProps + * @param {TProps} data - The data to create Svelte 5 props from. + * @returns {TProps} - The created Svelte 5 props. + */ +export const createSvelte5Props = (data) => { + const props = $state(data); + return props; +}; diff --git a/code/renderers/svelte/src/decorators.ts b/code/renderers/svelte/src/decorators.ts index 07fb1753b56d..657fa91ff28e 100644 --- a/code/renderers/svelte/src/decorators.ts +++ b/code/renderers/svelte/src/decorators.ts @@ -1,12 +1,13 @@ import type { DecoratorFunction, StoryContext, LegacyStoryFn } from '@storybook/types'; import { sanitizeStoryContextUpdate } from '@storybook/preview-api'; -// ! DO NOT change this SlotDecorator import to a relative path, it will break it. -// ! A relative import will be compiled at build time, and Svelte will be unable to -// ! render the component together with the user's Svelte components -// ! importing from @storybook/svelte will make sure that it is compiled at runtime -// ! with the same bundle as the user's Svelte components -// eslint-disable-next-line import/no-extraneous-dependencies -import SlotDecorator from '@storybook/svelte/templates/SlotDecorator.svelte'; +/* +! DO NOT change this SlotDecorator import to a relative path, it will break it. +! A relative import will be compiled at build time, and Svelte will be unable to +! render the component together with the user's Svelte components +! importing from @storybook/svelte will make sure that it is compiled at runtime +! with the same bundle as the user's Svelte components +*/ +import SlotDecorator from '@storybook/svelte/internal/SlotDecorator.svelte'; import type { SvelteRenderer } from './types'; /** diff --git a/code/renderers/svelte/src/render.ts b/code/renderers/svelte/src/render.ts index 514b3128ed21..28552379bf90 100644 --- a/code/renderers/svelte/src/render.ts +++ b/code/renderers/svelte/src/render.ts @@ -1,50 +1,31 @@ import type { RenderContext, ArgsStoryFn } from '@storybook/types'; import { RESET_STORY_ARGS } from '@storybook/core-events'; -// ! DO NOT change this PreviewRender import to a relative path, it will break it. -// ! A relative import will be compiled at build time, and Svelte will be unable to -// ! render the component together with the user's Svelte components -// ! importing from @storybook/svelte will make sure that it is compiled at runtime -// ! with the same bundle as the user's Svelte components -// eslint-disable-next-line import/no-extraneous-dependencies -import PreviewRender from '@storybook/svelte/templates/PreviewRender.svelte'; +/* +! DO NOT change these PreviewRender and createSvelte5Props imports to relative paths, it will break them. +! Relative imports will be compiled at build time by tsup, but we need Svelte to compile them +! when compiling the rest of the Svelte files. +*/ +import PreviewRender from '@storybook/svelte/internal/PreviewRender.svelte'; +// @ts-expect-error Don't know why TS doesn't pick up the types export here +import { createSvelte5Props } from '@storybook/svelte/internal/createSvelte5Props'; + import { addons } from '@storybook/preview-api'; import * as svelte from 'svelte'; +import { VERSION as SVELTE_VERSION } from 'svelte/compiler'; import type { SvelteRenderer } from './types'; -const componentsByDomElement = new Map< - SvelteRenderer['canvasElement'], - any // ReturnType depends on the version of Svelte v4 or v5 ->(); - -function teardown(canvasElement: SvelteRenderer['canvasElement']) { - if (!componentsByDomElement.has(canvasElement)) { - return; - } - - componentsByDomElement.get(canvasElement)!.$destroy(); - - canvasElement.innerHTML = ''; - componentsByDomElement.delete(canvasElement); -} +const IS_SVELTE_V4 = Number(SVELTE_VERSION[0]) <= 4; -/** - * Mount the PreviewRender component to the provided canvasElement - * Either using the Svelte v4 or v5 API - */ -function createRoot(target: HTMLElement, props: any) { - if ((svelte as any).createRoot) { - // Svelte v5 - return (svelte as any).createRoot(PreviewRender, { - target, - props, - }); +export function renderToCanvas( + renderContext: RenderContext, + canvasElement: SvelteRenderer['canvasElement'] +) { + if (IS_SVELTE_V4) { + return renderToCanvasV4(renderContext, canvasElement); + } else { + return renderToCanvasV5(renderContext, canvasElement); } - // Svelte v4 - return new (PreviewRender as any)({ - target, - props, - }); } /** @@ -61,10 +42,12 @@ addons.getChannel().on(RESET_STORY_ARGS, ({ storyId }) => { storyIdsToRemountFromResetArgsEvent.add(storyId); }); -export function renderToCanvas( +const componentsByDomElementV4 = new Map(); + +function renderToCanvasV4( { storyFn, - kind, + title, name, showMain, showError, @@ -73,43 +56,123 @@ export function renderToCanvas( }: RenderContext, canvasElement: SvelteRenderer['canvasElement'] ) { - const existingComponent = componentsByDomElement.get(canvasElement); + function unmount(canvasElementToUnmount: SvelteRenderer['canvasElement']) { + if (!componentsByDomElementV4.has(canvasElementToUnmount)) { + return; + } + componentsByDomElementV4.get(canvasElementToUnmount)!.$destroy(); + componentsByDomElementV4.delete(canvasElementToUnmount); + canvasElementToUnmount.innerHTML = ''; + } + const existingComponent = componentsByDomElementV4.get(canvasElement); let remount = forceRemount; + if (storyIdsToRemountFromResetArgsEvent.has(storyContext.id)) { + remount = true; + storyIdsToRemountFromResetArgsEvent.delete(storyContext.id); + } + + if (remount) { + unmount(canvasElement); + } + if (!existingComponent || remount) { + const mountedComponent = new PreviewRender({ + target: canvasElement, + props: { + storyFn, + storyContext, + name, + title, + showError, + }, + }); + componentsByDomElementV4.set(canvasElement, mountedComponent); + } else { + existingComponent.$set({ + storyFn, + storyContext, + name, + title, + showError, + }); + } + + showMain(); + + // unmount the component when the story changes + return () => { + unmount(canvasElement); + }; +} + +const componentsByDomElementV5 = new Map< + SvelteRenderer['canvasElement'], + { mountedComponent: ReturnType<(typeof svelte)['mount']>; props: RenderContext } +>(); + +function renderToCanvasV5( + { + storyFn, + title, + name, + showMain, + showError, + storyContext, + forceRemount, + }: RenderContext, + canvasElement: SvelteRenderer['canvasElement'] +) { + function unmount(canvasElementToUnmount: SvelteRenderer['canvasElement']) { + const { mountedComponent } = componentsByDomElementV5.get(canvasElementToUnmount) ?? {}; + if (!mountedComponent) { + return; + } + svelte.unmount(mountedComponent); + componentsByDomElementV5.delete(canvasElementToUnmount); + } + + const existingComponent = componentsByDomElementV5.get(canvasElement); + + let remount = forceRemount; if (storyIdsToRemountFromResetArgsEvent.has(storyContext.id)) { remount = true; storyIdsToRemountFromResetArgsEvent.delete(storyContext.id); } if (remount) { - teardown(canvasElement); + unmount(canvasElement); } if (!existingComponent || remount) { - const createdComponent = createRoot(canvasElement, { + const props = createSvelte5Props({ storyFn, storyContext, name, - kind, + title, showError, }); - componentsByDomElement.set(canvasElement, createdComponent); + const mountedComponent = svelte.mount(PreviewRender, { + target: canvasElement, + props, + }); + componentsByDomElementV5.set(canvasElement, { mountedComponent, props }); } else { - existingComponent.$set({ + // We need to mutate the existing props for Svelte reactivity to work, we can't just re-assign them + Object.assign(existingComponent.props, { storyFn, storyContext, name, - kind, + title, showError, }); } showMain(); - // teardown the component when the story changes + // unmount the component when the story changes return () => { - teardown(canvasElement); + unmount(canvasElement); }; } diff --git a/code/renderers/svelte/template/stories/args.stories.js b/code/renderers/svelte/template/stories/args.stories.js index 7ecee85a982c..493b5519e6e2 100644 --- a/code/renderers/svelte/template/stories/args.stories.js +++ b/code/renderers/svelte/template/stories/args.stories.js @@ -14,14 +14,16 @@ export default { }; export const RemountOnResetStoryArgs = { - play: async ({ canvasElement, id }) => { + play: async ({ canvasElement, id, step }) => { const canvas = await within(canvasElement); const channel = addons.getChannel(); - // Just to ensure the story is always in a clean state from the beginning, not really part of the test - await channel.emit(RESET_STORY_ARGS, { storyId: id }); - await new Promise((resolve) => { - channel.once(STORY_RENDERED, resolve); + await step('Reset story args', async () => { + // Just to ensure the story is always in a clean state from the beginning, not really part of the test + await channel.emit(RESET_STORY_ARGS, { storyId: id }); + await new Promise((resolve) => { + channel.once(STORY_RENDERED, resolve); + }); }); const button = await canvas.getByRole('button'); await expect(button).toHaveTextContent('You clicked: 0'); @@ -29,16 +31,18 @@ export const RemountOnResetStoryArgs = { await userEvent.click(button); await expect(button).toHaveTextContent('You clicked: 1'); - await channel.emit(UPDATE_STORY_ARGS, { storyId: id, updatedArgs: { text: 'Changed' } }); - await new Promise((resolve) => { - channel.once(STORY_RENDERED, resolve); + await step("Update story args with { text: 'Changed' }", async () => { + await channel.emit(UPDATE_STORY_ARGS, { storyId: id, updatedArgs: { text: 'Changed' } }); + await new Promise((resolve) => { + channel.once(STORY_RENDERED, resolve); + }); }); await expect(button).toHaveTextContent('Changed: 1'); // expect that all state and args are reset after RESET_STORY_ARGS because Svelte needs to remount the component // most other renderers would have 'You clicked: 1' here because they don't remount the component // if this doesn't fully remount it would be 'undefined: 1' because undefined args are used as is in Svelte, and the state is kept - await channel.emit(RESET_STORY_ARGS, { storyId: id }); + await step('Reset story args', () => channel.emit(RESET_STORY_ARGS, { storyId: id })); await waitFor(async () => expect(await within(canvasElement).getByRole('button')).toHaveTextContent('You clicked: 0') ); diff --git a/code/renderers/svelte/template/stories/views/ButtonJavaScript.svelte b/code/renderers/svelte/template/stories/views/ButtonJavaScript.svelte index 23af7eaac0f0..800ee79de0f4 100644 --- a/code/renderers/svelte/template/stories/views/ButtonJavaScript.svelte +++ b/code/renderers/svelte/template/stories/views/ButtonJavaScript.svelte @@ -15,7 +15,7 @@ /** * Displays the count */ - export let count = 0; + let count = 0; /** * Button text diff --git a/code/renderers/svelte/templates/HOC.svelte b/code/renderers/svelte/templates/HOC.svelte deleted file mode 100644 index 0b6c3618701e..000000000000 --- a/code/renderers/svelte/templates/HOC.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - \ No newline at end of file diff --git a/code/renderers/vue3/package.json b/code/renderers/vue3/package.json index ec3d8037453a..cc5a72a3cde1 100644 --- a/code/renderers/vue3/package.json +++ b/code/renderers/vue3/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue3", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook Vue 3 renderer", "keywords": [ "storybook" diff --git a/code/renderers/vue3/src/__tests__/composeStories/Button.stories.ts b/code/renderers/vue3/src/__tests__/composeStories/Button.stories.ts index 239416df5c35..0d4623585de0 100644 --- a/code/renderers/vue3/src/__tests__/composeStories/Button.stories.ts +++ b/code/renderers/vue3/src/__tests__/composeStories/Button.stories.ts @@ -1,4 +1,4 @@ -import { userEvent, within } from '@storybook/testing-library'; +import { userEvent, within, expect, fn } from '@storybook/test'; import type { Meta, StoryFn as CSF2Story, StoryObj } from '../..'; import Button from './Button.vue'; @@ -45,8 +45,10 @@ const getCaptionForLocale = (locale: string) => { return 'μ•ˆλ…•ν•˜μ„Έμš”!'; case 'pt': return 'OlΓ‘!'; - default: + case 'en': return 'Hello!'; + default: + return undefined; } }; @@ -58,7 +60,7 @@ export const CSF2StoryWithLocale: CSF2Story = (args, { globals }) => ({ }, template: `

locale: ${globals.locale}

-
`, }); CSF2StoryWithLocale.storyName = 'WithLocale'; @@ -114,7 +116,39 @@ export const CSF3InputFieldFilled: CSF3Story = { play: async ({ canvasElement, step }) => { const canvas = within(canvasElement); await step('Step label', async () => { - await userEvent.type(canvas.getByTestId('input'), 'Hello world!'); + const inputEl = canvas.getByTestId('input'); + await userEvent.type(inputEl, 'Hello world!'); + await expect(inputEl).toHaveValue('Hello world!'); }); }, }; + +const mockFn = fn(); +export const LoaderStory: StoryObj<{ mockFn: (val: string) => string }> = { + args: { + mockFn, + }, + loaders: [ + async () => { + mockFn.mockReturnValueOnce('mockFn return value'); + return { + value: 'loaded data', + }; + }, + ], + render: (args, { loaded }) => ({ + components: { Button }, + setup() { + return { args, data: args.mockFn('render'), loaded: loaded.value }; + }, + template: ` +
+
{{loaded}}
+
{{data}}
+
+ `, + }), + play: async () => { + expect(mockFn).toHaveBeenCalledWith('render'); + }, +}; diff --git a/code/renderers/vue3/src/__tests__/composeStories/__snapshots__/portable-stories.test.ts.snap b/code/renderers/vue3/src/__tests__/composeStories/__snapshots__/portable-stories.test.ts.snap index 75eab08758cf..b6e6feff8f61 100644 --- a/code/renderers/vue3/src/__tests__/composeStories/__snapshots__/portable-stories.test.ts.snap +++ b/code/renderers/vue3/src/__tests__/composeStories/__snapshots__/portable-stories.test.ts.snap @@ -3,56 +3,50 @@ exports[`Renders CSF2Secondary story 1`] = `
- -
- -`; - -exports[`Renders CSF2StoryWithLocale story 1`] = ` - -
-
-

- locale: undefined -

`; -exports[`Renders CSF3Button story 1`] = ` +exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = `
- +
+ +
+
`; -exports[`Renders CSF3ButtonWithRender story 1`] = ` +exports[`Renders CSF3Button story 1`] = `
-
-

- I am a custom render function -

+
+
+
+
+ +`; + exports[`Renders CSF3InputFieldFilled story 1`] = `
- +
+ +
`; @@ -77,12 +101,41 @@ exports[`Renders CSF3InputFieldFilled story 1`] = ` exports[`Renders CSF3Primary story 1`] = `
- + +
+ + +`; + +exports[`Renders LoaderStory story 1`] = ` + +
+
+
+
+ loaded data +
+
+ mockFn return value +
+
+
`; diff --git a/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts b/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts index 4c541e1c4536..7491a376e07c 100644 --- a/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts +++ b/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts @@ -10,50 +10,69 @@ import type Button from './Button.vue'; import { composeStories, composeStory, setProjectAnnotations } from '../../portable-stories'; // example with composeStories, returns an object with all stories composed with args/decorators -const { CSF3Primary } = composeStories(stories); +const { CSF3Primary, LoaderStory } = composeStories(stories); // example with composeStory, returns a single story composed with args/decorators const Secondary = composeStory(stories.CSF2Secondary, stories.default); -it('renders primary button', () => { - render(CSF3Primary({ label: 'Hello world' })); - const buttonElement = screen.getByText(/Hello world/i); - expect(buttonElement).toBeInTheDocument(); -}); +describe('renders', () => { + it('renders primary button', () => { + render(CSF3Primary, { props: { label: 'Hello world' } }); + const buttonElement = screen.getByText(/Hello world/i); + expect(buttonElement).toBeInTheDocument(); + }); -it('reuses args from composed story', () => { - render(Secondary()); - const buttonElement = screen.getByRole('button'); - expect(buttonElement.textContent).toEqual(Secondary.args.label); -}); + it('reuses args from composed story', () => { + render(Secondary); + const buttonElement = screen.getByRole('button'); + expect(buttonElement.textContent).toEqual(Secondary.args.label); + }); -it('myClickEvent handler is called', async () => { - const myClickEventSpy = vi.fn(); - render(Secondary({ onMyClickEvent: myClickEventSpy })); - const buttonElement = screen.getByRole('button'); - buttonElement.click(); - expect(myClickEventSpy).toHaveBeenCalled(); -}); + it('myClickEvent handler is called', async () => { + const myClickEventSpy = vi.fn(); + render(Secondary, { props: { onMyClickEvent: myClickEventSpy } }); + const buttonElement = screen.getByRole('button'); + buttonElement.click(); + expect(myClickEventSpy).toHaveBeenCalled(); + }); -it('reuses args from composeStories', () => { - const { getByText } = render(CSF3Primary()); - const buttonElement = getByText(/foo/i); - expect(buttonElement).toBeInTheDocument(); + it('reuses args from composeStories', () => { + const { getByText } = render(CSF3Primary); + const buttonElement = getByText(/foo/i); + expect(buttonElement).toBeInTheDocument(); + }); + + it('should call and compose loaders data', async () => { + await LoaderStory.load(); + const { getByTestId } = render(LoaderStory); + 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.play!(); + }); }); describe('projectAnnotations', () => { it('renders with default projectAnnotations', () => { + setProjectAnnotations([ + { + parameters: { injected: true }, + globalTypes: { + locale: { defaultValue: 'en' }, + }, + }, + ]); const WithEnglishText = composeStory(stories.CSF2StoryWithLocale, stories.default); - const { getByText } = render(WithEnglishText()); + const { getByText } = render(WithEnglishText); const buttonElement = getByText('Hello!'); expect(buttonElement).toBeInTheDocument(); }); it('renders with custom projectAnnotations via composeStory params', () => { const WithPortugueseText = composeStory(stories.CSF2StoryWithLocale, stories.default, { - globalTypes: { locale: { defaultValue: 'pt' } } as any, + globals: { locale: 'pt' }, }); - const { getByText } = render(WithPortugueseText()); + const { getByText } = render(WithPortugueseText); const buttonElement = getByText('OlΓ‘!'); expect(buttonElement).toBeInTheDocument(); }); @@ -69,7 +88,7 @@ describe('CSF3', () => { it('renders with inferred globalRender', () => { const Primary = composeStory(stories.CSF3Button, stories.default); - render(Primary({ label: 'Hello world' })); + render(Primary, { props: { label: 'Hello world' } }); const buttonElement = screen.getByText(/Hello world/i); expect(buttonElement).toBeInTheDocument(); }); @@ -77,16 +96,16 @@ describe('CSF3', () => { it('renders with custom render function', () => { const Primary = composeStory(stories.CSF3ButtonWithRender, stories.default); - render(Primary()); + render(Primary); expect(screen.getByTestId('custom-render')).toBeInTheDocument(); }); it('renders with play function', async () => { const CSF3InputFieldFilled = composeStory(stories.CSF3InputFieldFilled, stories.default); - const { container } = render(CSF3InputFieldFilled()); + render(CSF3InputFieldFilled); - await CSF3InputFieldFilled.play!({ canvasElement: container as HTMLElement }); + await CSF3InputFieldFilled.play!(); const input = screen.getByTestId('input') as HTMLInputElement; expect(input.value).toEqual('Hello world!'); @@ -103,7 +122,7 @@ it('should pass with decorators that need addons channel', () => { }, ], }); - render(PrimaryWithChannels({ label: 'Hello world' })); + render(PrimaryWithChannels, { props: { label: 'Hello world' } }); const buttonElement = screen.getByText(/Hello world/i); expect(buttonElement).not.toBeNull(); }); @@ -127,12 +146,14 @@ describe('ComposeStories types', () => { // Batch snapshot testing const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName, Story]); it.each(testCases)('Renders %s story', async (_storyName, Story) => { - if (typeof Story === 'string' || _storyName === 'CSF2StoryWithParamsAndDecorator') { + if (typeof Story === 'string' || _storyName === 'CSF2StoryWithLocale') { return; } + await Story.load(); + const { baseElement } = await render(Story); + await Story.play?.(); await new Promise((resolve) => setTimeout(resolve, 0)); - const tree = await render(Story()); - expect(tree.baseElement).toMatchSnapshot(); + expect(baseElement).toMatchSnapshot(); }); diff --git a/code/renderers/vue3/src/portable-stories.ts b/code/renderers/vue3/src/portable-stories.ts index 4e009b25d672..73c7991f16a6 100644 --- a/code/renderers/vue3/src/portable-stories.ts +++ b/code/renderers/vue3/src/portable-stories.ts @@ -2,6 +2,7 @@ import { composeStory as originalComposeStory, composeStories as originalComposeStories, setProjectAnnotations as originalSetProjectAnnotations, + getPortableStoryWrapperId, } from '@storybook/preview-api'; import type { Args, @@ -10,11 +11,22 @@ import type { Store_CSFExports, StoriesWithPartialProps, } from '@storybook/types'; +import { h } from 'vue'; -import * as defaultProjectAnnotations from './render'; +import * as vueProjectAnnotations from './entry-preview'; import type { Meta } from './public-types'; import type { VueRenderer } from './types'; +const defaultProjectAnnotations: ProjectAnnotations = { + ...vueProjectAnnotations, + decorators: [ + function (story, { id }) { + const wrapperProps = { 'data-story': true, id: getPortableStoryWrapperId(id) }; + return h('div', wrapperProps, h(story())); + }, + ], +}; + /** Function that sets the globalConfig of your Storybook. The global config is the preview module of your .storybook folder. * * It should be run a single time, so that your global config (e.g. decorators) is applied to your stories when using `composeStories` or `composeStory`. @@ -53,7 +65,7 @@ export function setProjectAnnotations( * const Primary = composeStory(PrimaryStory, Meta); * * test('renders primary button with Hello World', () => { - * const { getByText } = render(Primary({label: "Hello world"})); + * const { getByText } = render(Primary, { props: { label: "Hello world" } }); * expect(getByText(/Hello world/i)).not.toBeNull(); * }); *``` @@ -69,13 +81,21 @@ export function composeStory( projectAnnotations?: ProjectAnnotations, exportsName?: string ) { - return originalComposeStory( + const composedStory = originalComposeStory( story as StoryAnnotationsOrFn, componentAnnotations, projectAnnotations, defaultProjectAnnotations, exportsName ); + + // Returning h(composedStory) instead makes it an actual Vue component renderable by @testing-library/vue, Playwright CT, etc. + const renderable = (...args: Parameters) => h(composedStory(...args)); + Object.assign(renderable, composedStory); + + // typing this as newable means TS allows it to be used as a JSX element + // TODO: we should do the same for composeStories as well + return renderable as unknown as typeof composedStory & { new (...args: any[]): any }; } /** @@ -95,7 +115,7 @@ export function composeStory( * const { Primary, Secondary } = composeStories(stories); * * test('renders primary button with Hello World', () => { - * const { getByText } = render(Primary({label: "Hello world"})); + * const { getByText } = render(Primary, { props: { label: "Hello world" } }); * expect(getByText(/Hello world/i)).not.toBeNull(); * }); *``` diff --git a/code/renderers/web-components/package.json b/code/renderers/web-components/package.json index fcd133473b18..1483eb3de461 100644 --- a/code/renderers/web-components/package.json +++ b/code/renderers/web-components/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook web-components renderer", "keywords": [ "lit", diff --git a/code/ui/blocks/package.json b/code/ui/blocks/package.json index c48cbadc893a..790612807846 100644 --- a/code/ui/blocks/package.json +++ b/code/ui/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/blocks", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Storybook Doc Blocks", "keywords": [ "storybook" diff --git a/code/ui/blocks/src/components/ArgsTable/ArgRow.tsx b/code/ui/blocks/src/components/ArgsTable/ArgRow.tsx index d6e2c73ad905..8f6b6f2c5f99 100644 --- a/code/ui/blocks/src/components/ArgsTable/ArgRow.tsx +++ b/code/ui/blocks/src/components/ArgsTable/ArgRow.tsx @@ -76,12 +76,18 @@ const StyledTd = styled.td<{ expandable: boolean }>(({ theme, expandable }) => ( paddingLeft: expandable ? '40px !important' : '20px !important', })); +const toSummary = (value: any) => { + if (!value) return value; + const val = typeof value === 'string' ? value : value.name; + return { summary: val }; +}; + export const ArgRow: FC = (props) => { const [isHovered, setIsHovered] = useState(false); const { row, updateArgs, compact, expandable, initialExpandedArgs } = props; const { name, description } = row; const table = (row.table || {}) as TableAnnotation; - const type = table.type || row.type; + const type = table.type || toSummary(row.type); const defaultValue = table.defaultValue || row.defaultValue; const required = row.type?.required; const hasDescription = description != null && description !== ''; diff --git a/code/ui/components/package.json b/code/ui/components/package.json index 248ed68c73c5..fcb4235f18e1 100644 --- a/code/ui/components/package.json +++ b/code/ui/components/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/components", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Core Storybook Components", "keywords": [ "storybook" diff --git a/code/ui/manager/package.json b/code/ui/manager/package.json index b13135e4eb4f..05c4b076ed7b 100644 --- a/code/ui/manager/package.json +++ b/code/ui/manager/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/manager", - "version": "8.0.0-rc.1", + "version": "8.0.0-rc.2", "description": "Core Storybook UI", "keywords": [ "storybook" diff --git a/code/ui/manager/src/components/sidebar/Sidebar.stories.tsx b/code/ui/manager/src/components/sidebar/Sidebar.stories.tsx index 3e7b7e2b23f3..253775bc07c7 100644 --- a/code/ui/manager/src/components/sidebar/Sidebar.stories.tsx +++ b/code/ui/manager/src/components/sidebar/Sidebar.stories.tsx @@ -1,14 +1,14 @@ import React from 'react'; import type { IndexHash, State } from '@storybook/manager-api'; -import { types } from '@storybook/manager-api'; +import { ManagerContext, types } from '@storybook/manager-api'; import type { StoryObj, Meta } from '@storybook/react'; -import { within, userEvent } from '@storybook/testing-library'; +import { within, userEvent, expect } from '@storybook/test'; +import type { Addon_SidebarTopType } from '@storybook/types'; import { Button, IconButton } from '@storybook/components'; import { FaceHappyIcon } from '@storybook/icons'; import { Sidebar, DEFAULT_REF_ID } from './Sidebar'; import { standardData as standardHeaderData } from './Heading.stories'; -import * as ExplorerStories from './Explorer.stories'; import { mockDataset } from './mockdata'; import type { RefType } from './types'; import { LayoutProvider } from '../layout/LayoutProvider'; @@ -19,33 +19,62 @@ const wait = (ms: number) => setTimeout(resolve, ms); }); +const { menu } = standardHeaderData; +const index = mockDataset.withRoot as IndexHash; +const storyId = 'root-1-child-a2--grandchild-a1-1'; + +export const simpleData = { menu, index, storyId }; +export const loadingData = { menu }; + const meta = { component: Sidebar, title: 'Sidebar/Sidebar', excludeStories: /.*Data$/, parameters: { layout: 'fullscreen' }, + args: { + previewInitialized: true, + menu, + extra: [] as Addon_SidebarTopType[], + index: index, + storyId, + refId: DEFAULT_REF_ID, + refs: {}, + status: {}, + }, decorators: [ - ExplorerStories.default.decorators[0], (storyFn) => ( - - - {storyFn()} - + {}, + on: () => {}, + off: () => {}, + getShortcutKeys: () => ({ search: ['control', 'shift', 's'] }), + }, + } as any + } + > + + + {storyFn()} + + ), ], -} as Meta; +} satisfies Meta; export default meta; type Story = StoryObj; -const { menu } = standardHeaderData; -const index = mockDataset.withRoot as IndexHash; -const storyId = 'root-1-child-a2--grandchild-a1-1'; - -export const simpleData = { menu, index, storyId }; -export const loadingData = { menu }; - const refs: Record = { optimized: { id: 'optimized', @@ -57,6 +86,7 @@ const refs: Record = { }, }; +// eslint-disable-next-line local-rules/no-uncategorized-errors const indexError = new Error('Failed to load index'); const refsError = { @@ -75,146 +105,56 @@ const refsEmpty = { }, }; -export const Simple: Story = { - args: { previewInitialized: true }, - render: (args) => ( - - ), -}; +export const Simple: Story = {}; export const Loading: Story = { - args: { previewInitialized: false }, - render: (args) => ( - - ), + args: { + previewInitialized: false, + index: undefined, + }, }; export const Empty: Story = { args: { - previewInitialized: true, + index: {}, }, - render: (args) => ( - - ), }; export const IndexError: Story = { args: { - previewInitialized: true, + indexError, }, - render: (args) => ( - - ), }; export const WithRefs: Story = { args: { - previewInitialized: true, + refs, }, - render: (args) => ( - - ), }; export const LoadingWithRefs: Story = { args: { - previewInitialized: false, + ...Loading.args, + refs, }, - render: (args) => ( - - ), }; export const LoadingWithRefError: Story = { args: { - previewInitialized: false, + ...Loading.args, + refs: refsError, }, - render: (args) => ( - - ), }; export const WithRefEmpty: Story = { args: { - previewInitialized: true, + ...Empty.args, + refs: refsEmpty, }, - render: (args) => ( - - ), }; export const StatusesCollapsed: Story = { args: { - previewInitialized: true, status: Object.entries(index).reduce((acc, [id, item]) => { if (item.type !== 'story') { return acc; @@ -232,17 +172,6 @@ export const StatusesCollapsed: Story = { return acc; }, {}), }, - render: (args) => ( - - ), }; export const StatusesOpen: Story = { @@ -267,7 +196,7 @@ export const StatusesOpen: Story = { export const Searching: Story = { ...StatusesOpen, - parameters: { theme: 'light', chromatic: { delay: 2200 } }, + parameters: { chromatic: { delay: 2200 } }, play: async ({ canvasElement, step }) => { await step('wait 2000ms', () => wait(2000)); const canvas = await within(canvasElement); @@ -279,52 +208,92 @@ export const Searching: Story = { export const Bottom: Story = { args: { - previewInitialized: true, + bottom: [ + { + id: '1', + type: types.experimental_SIDEBAR_BOTTOM, + render: () => ( + + ), + }, + { + id: '2', + type: types.experimental_SIDEBAR_BOTTOM, + render: () => ( + + ), + }, + { + id: '3', + type: types.experimental_SIDEBAR_BOTTOM, + render: () => ( + + {' '} + + + ), + }, + ], + }, +}; + +/** + * Given the following sequence of events: + * 1. Story is selected at the top of the sidebar + * 2. The sidebar is scrolled to the bottom + * 3. Some re-rendering happens because of a changed state/prop + * The sidebar should remain scrolled to the bottom + */ +export const Scrolled: Story = { + parameters: { + // we need a very short viewport + viewport: { + defaultViewport: 'mobile1', + defaultOrientation: 'landscape', + }, + }, + args: { + storyId: 'group-1--child-b1', + }, + render: (args) => { + const [, setState] = React.useState(0); + return ( + <> + + + + ); + }, + play: async ({ canvasElement, step }) => { + const canvas = await within(canvasElement); + const scrollable = await canvasElement.querySelector('[data-radix-scroll-area-viewport]'); + await step('expand component', async () => { + const componentNode = await canvas.queryAllByText('Child A2')[1]; + userEvent.click(componentNode); + }); + await wait(100); + await step('scroll to bottom', async () => { + scrollable.scrollTo(0, scrollable.scrollHeight); + }); + await step('toggle parent state', async () => { + const button = await canvas.findByRole('button', { name: 'Change state' }); + button.click(); + }); + await wait(100); + + // expect the scrollable to be scrolled to the bottom + expect(scrollable.scrollTop).toBe(scrollable.scrollHeight - scrollable.clientHeight); }, - parameters: { theme: 'light' }, - render: (args) => ( - ( - - ), - }, - { - id: '2', - type: types.experimental_SIDEBAR_BOTTOM, - render: () => ( - - ), - }, - { - id: '3', - type: types.experimental_SIDEBAR_BOTTOM, - render: () => ( - - {' '} - - - ), - }, - ]} - /> - ), }; diff --git a/code/ui/manager/src/components/sidebar/Sidebar.tsx b/code/ui/manager/src/components/sidebar/Sidebar.tsx index 3ea23f6b48a5..f881e3d68d4d 100644 --- a/code/ui/manager/src/components/sidebar/Sidebar.tsx +++ b/code/ui/manager/src/components/sidebar/Sidebar.tsx @@ -17,7 +17,7 @@ import { Explorer } from './Explorer'; import { Search } from './Search'; import { SearchResults } from './SearchResults'; -import type { Refs, CombinedDataset, Selection } from './types'; +import type { CombinedDataset, Selection } from './types'; import { useLastViewed } from './useLastViewed'; import { MEDIA_DESKTOP_BREAKPOINT } from '../../constants'; @@ -79,20 +79,26 @@ const Swap = React.memo(function Swap({ }); const useCombination = ( - defaultRefData: API_LoadedRefData & { status: State['status'] }, - refs: Refs + index: SidebarProps['index'], + indexError: SidebarProps['indexError'], + previewInitialized: SidebarProps['previewInitialized'], + status: SidebarProps['status'], + refs: SidebarProps['refs'] ): CombinedDataset => { const hash = useMemo( () => ({ [DEFAULT_REF_ID]: { - ...defaultRefData, + index, + indexError, + previewInitialized, + status, title: null, id: DEFAULT_REF_ID, url: 'iframe.html', }, ...refs, }), - [refs, defaultRefData] + [refs, index, indexError, previewInitialized, status] ); return useMemo(() => ({ hash, entries: Object.entries(hash) }), [hash]); }; @@ -126,7 +132,7 @@ export const Sidebar = React.memo(function Sidebar({ onMenuClick, }: SidebarProps) { const selected: Selection = useMemo(() => storyId && { storyId, refId }, [storyId, refId]); - const dataset = useCombination({ index, indexError, previewInitialized, status }, refs); + const dataset = useCombination(index, indexError, previewInitialized, status, refs); const isLoading = !index && !indexError; const lastViewedProps = useLastViewed(selected); diff --git a/code/ui/manager/src/globals/exports.ts b/code/ui/manager/src/globals/exports.ts index b8e2d5b14868..079340369fcb 100644 --- a/code/ui/manager/src/globals/exports.ts +++ b/code/ui/manager/src/globals/exports.ts @@ -130,6 +130,7 @@ export default { ], '@storybook/core-events': [ 'CHANNEL_CREATED', + 'CHANNEL_WS_DISCONNECT', 'CONFIG_ERROR', 'CURRENT_STORY_WAS_SET', 'DOCS_PREPARED', diff --git a/code/yarn.lock b/code/yarn.lock index 5955691d40e7..6c439a2c9c5e 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -2583,156 +2583,163 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/android-arm64@npm:0.18.20" +"@esbuild/aix-ppc64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/aix-ppc64@npm:0.20.1" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/android-arm64@npm:0.20.1" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/android-arm@npm:0.18.20" +"@esbuild/android-arm@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/android-arm@npm:0.20.1" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/android-x64@npm:0.18.20" +"@esbuild/android-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/android-x64@npm:0.20.1" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/darwin-arm64@npm:0.18.20" +"@esbuild/darwin-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/darwin-arm64@npm:0.20.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/darwin-x64@npm:0.18.20" +"@esbuild/darwin-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/darwin-x64@npm:0.20.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/freebsd-arm64@npm:0.18.20" +"@esbuild/freebsd-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/freebsd-arm64@npm:0.20.1" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/freebsd-x64@npm:0.18.20" +"@esbuild/freebsd-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/freebsd-x64@npm:0.20.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-arm64@npm:0.18.20" +"@esbuild/linux-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-arm64@npm:0.20.1" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-arm@npm:0.18.20" +"@esbuild/linux-arm@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-arm@npm:0.20.1" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-ia32@npm:0.18.20" +"@esbuild/linux-ia32@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-ia32@npm:0.20.1" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-loong64@npm:0.18.20" +"@esbuild/linux-loong64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-loong64@npm:0.20.1" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-mips64el@npm:0.18.20" +"@esbuild/linux-mips64el@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-mips64el@npm:0.20.1" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-ppc64@npm:0.18.20" +"@esbuild/linux-ppc64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-ppc64@npm:0.20.1" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-riscv64@npm:0.18.20" +"@esbuild/linux-riscv64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-riscv64@npm:0.20.1" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-s390x@npm:0.18.20" +"@esbuild/linux-s390x@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-s390x@npm:0.20.1" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-x64@npm:0.18.20" +"@esbuild/linux-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-x64@npm:0.20.1" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/netbsd-x64@npm:0.18.20" +"@esbuild/netbsd-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/netbsd-x64@npm:0.20.1" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/openbsd-x64@npm:0.18.20" +"@esbuild/openbsd-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/openbsd-x64@npm:0.20.1" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/sunos-x64@npm:0.18.20" +"@esbuild/sunos-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/sunos-x64@npm:0.20.1" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/win32-arm64@npm:0.18.20" +"@esbuild/win32-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/win32-arm64@npm:0.20.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/win32-ia32@npm:0.18.20" +"@esbuild/win32-ia32@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/win32-ia32@npm:0.20.1" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/win32-x64@npm:0.18.20" +"@esbuild/win32-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/win32-x64@npm:0.20.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -5470,7 +5477,7 @@ __metadata: "@yarnpkg/esbuild-plugin-pnp": "npm:^3.0.0-rc.10" browser-assert: "npm:^1.2.1" ejs: "npm:^3.1.8" - esbuild: "npm:^0.18.0" + esbuild: "npm:^18.0.0 || ^19.0.0 || ^0.20.0" esbuild-plugin-alias: "npm:^0.2.1" express: "npm:^4.17.3" fs-extra: "npm:^11.1.0" @@ -5670,7 +5677,7 @@ __metadata: mdast-util-mdx-jsx: "npm:^3.0.0" mdast-util-mdxjs-esm: "npm:^2.0.1" prettier: "npm:^3.1.1" - recast: "npm:^0.23.1" + recast: "npm:^0.23.5" remark: "npm:^15.0.1" remark-mdx: "npm:^3.0.0" tiny-invariant: "npm:^1.3.1" @@ -5736,7 +5743,7 @@ __metadata: "@yarnpkg/libzip": "npm:2.3.0" chalk: "npm:^4.1.0" cross-spawn: "npm:^7.0.3" - esbuild: "npm:^0.18.0" + esbuild: "npm:^18.0.0 || ^19.0.0 || ^0.20.0" esbuild-register: "npm:^3.5.0" execa: "npm:^5.0.0" file-system-cache: "npm:2.3.0" @@ -5871,7 +5878,7 @@ __metadata: "@types/js-yaml": "npm:^4.0.5" fs-extra: "npm:^11.1.0" js-yaml: "npm:^4.1.0" - recast: "npm:^0.23.1" + recast: "npm:^0.23.5" ts-dedent: "npm:^2.0.0" typescript: "npm:^5.3.2" languageName: unknown @@ -6339,11 +6346,13 @@ __metadata: "@storybook/react-docgen-typescript-plugin": "npm:1.0.6--canary.9.0c3f3b7.0" "@types/node": "npm:^18.0.0" "@types/semver": "npm:^7.3.4" + find-up: "npm:^5.0.0" fs-extra: "npm:^11.1.0" magic-string: "npm:^0.30.5" react-docgen: "npm:^7.0.0" resolve: "npm:^1.22.8" semver: "npm:^7.3.7" + tsconfig-paths: "npm:^4.2.0" typescript: "npm:^5.3.2" webpack: "npm:5" peerDependencies: @@ -6385,7 +6394,7 @@ __metadata: ts-dedent: "npm:^2.0.0" typescript: "npm:^5.3.2" peerDependencies: - svelte: ^4.0.0 || ^5.0.0-next.16 + svelte: ^4.0.0 || ^5.0.0-next.65 svelte-loader: "*" languageName: unknown linkType: soft @@ -6487,11 +6496,14 @@ __metadata: "@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.3.0" "@rollup/pluginutils": "npm:^5.0.2" "@storybook/builder-vite": "workspace:*" + "@storybook/node-logger": "workspace:*" "@storybook/react": "workspace:*" "@types/node": "npm:^18.0.0" + find-up: "npm:^5.0.0" magic-string: "npm:^0.30.0" react-docgen: "npm:^7.0.0" resolve: "npm:^1.22.8" + tsconfig-paths: "npm:^4.2.0" typescript: "npm:^5.3.2" vite: "npm:^4.0.0" peerDependencies: @@ -6669,7 +6681,7 @@ __metadata: concurrently: "npm:^5.3.0" cross-env: "npm:^7.0.3" danger: "npm:^11.2.6" - esbuild: "npm:^0.18.0" + esbuild: "npm:^18.0.0 || ^19.0.0 || ^0.20.0" esbuild-loader: "npm:^3.0.0" esbuild-plugin-alias: "npm:^0.2.1" eslint: "npm:^8.56.0" @@ -6693,7 +6705,7 @@ __metadata: react-dom: "npm:^18.2.0" semver: "npm:^7.3.7" serve-static: "npm:^1.14.1" - svelte: "npm:^5.0.0-next.28" + svelte: "npm:^5.0.0-next.65" trash: "npm:^7.0.0" ts-dedent: "npm:^2.0.0" ts-node: "npm:^10.9.1" @@ -6793,7 +6805,7 @@ __metadata: "@sveltejs/vite-plugin-svelte": "npm:^3.0.1" "@types/node": "npm:^18.0.0" magic-string: "npm:^0.30.0" - svelte: "npm:^5.0.0-next.28" + svelte: "npm:^5.0.0-next.65" svelte-preprocess: "npm:^5.1.1" sveltedoc-parser: "npm:^4.2.1" ts-dedent: "npm:^2.2.0" @@ -6801,7 +6813,7 @@ __metadata: vite: "npm:^4.0.0" peerDependencies: "@sveltejs/vite-plugin-svelte": ^2.0.0 || ^3.0.0 - svelte: ^4.0.0 || ^5.0.0-next.16 + svelte: ^4.0.0 || ^5.0.0-next.65 vite: ^4.0.0 || ^5.0.0 languageName: unknown linkType: soft @@ -6818,7 +6830,7 @@ __metadata: svelte-loader: "npm:^3.1.9" typescript: "npm:^5.3.2" peerDependencies: - svelte: ^4.0.0 || ^5.0.0-next.16 + svelte: ^4.0.0 || ^5.0.0-next.65 svelte-loader: "*" languageName: unknown linkType: soft @@ -6833,16 +6845,17 @@ __metadata: "@storybook/global": "npm:^5.0.0" "@storybook/preview-api": "workspace:*" "@storybook/types": "workspace:*" - "@sveltejs/vite-plugin-svelte": "npm:^3.0.1" + "@sveltejs/vite-plugin-svelte": "npm:^3.0.2" expect-type: "npm:^0.15.0" - svelte: "npm:^5.0.0-next.28" - svelte-check: "npm:^3.6.1" + fs-extra: "npm:^11.1.0" + svelte: "npm:^5.0.0-next.65" + svelte-check: "npm:^3.6.4" sveltedoc-parser: "npm:^4.2.1" ts-dedent: "npm:^2.0.0" type-fest: "npm:~2.19" typescript: "npm:^5.3.2" peerDependencies: - svelte: ^4.0.0 || ^5.0.0-next.16 + svelte: ^4.0.0 || ^5.0.0-next.65 languageName: unknown linkType: soft @@ -6858,7 +6871,7 @@ __metadata: typescript: "npm:^5.3.2" vite: "npm:^4.0.0" peerDependencies: - svelte: ^4.0.0 || ^5.0.0-next.16 + svelte: ^4.0.0 || ^5.0.0-next.65 vite: ^4.0.0 || ^5.0.0 languageName: unknown linkType: soft @@ -7079,7 +7092,7 @@ __metadata: languageName: node linkType: hard -"@sveltejs/vite-plugin-svelte-inspector@npm:^2.0.0-next.0 || ^2.0.0": +"@sveltejs/vite-plugin-svelte-inspector@npm:^2.0.0": version: 2.0.0 resolution: "@sveltejs/vite-plugin-svelte-inspector@npm:2.0.0" dependencies: @@ -7092,11 +7105,11 @@ __metadata: languageName: node linkType: hard -"@sveltejs/vite-plugin-svelte@npm:^3.0.1": - version: 3.0.1 - resolution: "@sveltejs/vite-plugin-svelte@npm:3.0.1" +"@sveltejs/vite-plugin-svelte@npm:^3.0.1, @sveltejs/vite-plugin-svelte@npm:^3.0.2": + version: 3.0.2 + resolution: "@sveltejs/vite-plugin-svelte@npm:3.0.2" dependencies: - "@sveltejs/vite-plugin-svelte-inspector": "npm:^2.0.0-next.0 || ^2.0.0" + "@sveltejs/vite-plugin-svelte-inspector": "npm:^2.0.0" debug: "npm:^4.3.4" deepmerge: "npm:^4.3.1" kleur: "npm:^4.1.5" @@ -7106,7 +7119,7 @@ __metadata: peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 vite: ^5.0.0 - checksum: 889d41014d4cc5dfb578cb0a80e64f72c0f8c143e9a299c3a4e2372fd582d982ce118dad5e158e0b747d1df7354a909ed9490b1adcd1bf982b56c82fffd4652c + checksum: 7150877f61b65a51d55916fccacb7851859b0aab9c7e4f591c98b6775a7e55f5410cc854add4c427f99978f115a92ac75f116a813b67814cc7801daac1b78439 languageName: node linkType: hard @@ -7569,10 +7582,10 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.1": - version: 1.0.2 - resolution: "@types/estree@npm:1.0.2" - checksum: 4b5c601d435ea8e2205458de15fd1556b5ae6c9a8323bad8a940ea502d6c824664faca94234c0bf76bf9c87cbf6ac41abee550c9e20433256549d589c9b543bd +"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.1, @types/estree@npm:^1.0.5": + version: 1.0.5 + resolution: "@types/estree@npm:1.0.5" + checksum: b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d languageName: node linkType: hard @@ -9493,12 +9506,12 @@ __metadata: languageName: node linkType: hard -"acorn-typescript@npm:^1.4.11": - version: 1.4.12 - resolution: "acorn-typescript@npm:1.4.12" +"acorn-typescript@npm:^1.4.13": + version: 1.4.13 + resolution: "acorn-typescript@npm:1.4.13" peerDependencies: acorn: ">=8.9.0" - checksum: a3b33ed0dc321e3364da507a3decec96423736384068c88fea5ea57aeae864ea115a6c4a20b3ace71b75f4901b0657bec82d83aab30a8ad0dfc4bfc0d8337546 + checksum: f2f17cf03379d63beeb007f0feea02cebbd9af261f6b5619ea7345b177bd7a5f99752927cbf652baa3fc97962ae4561592093ab0a1c3e00ca4f354ba23c557ae languageName: node linkType: hard @@ -9525,12 +9538,12 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.0.0, acorn@npm:^8.10.0, acorn@npm:^8.11.2, acorn@npm:^8.4.1, acorn@npm:^8.6.0, acorn@npm:^8.7.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": - version: 8.11.2 - resolution: "acorn@npm:8.11.2" +"acorn@npm:^8.0.0, acorn@npm:^8.10.0, acorn@npm:^8.11.2, acorn@npm:^8.11.3, acorn@npm:^8.4.1, acorn@npm:^8.6.0, acorn@npm:^8.7.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": + version: 8.11.3 + resolution: "acorn@npm:8.11.3" bin: acorn: bin/acorn - checksum: a3ed76c761b75ec54b1ec3068fb7f113a182e95aea7f322f65098c2958d232e3d211cb6dac35ff9c647024b63714bc528a26d54a925d1fef2c25585b4c8e4017 + checksum: 3ff155f8812e4a746fee8ecff1f227d527c4c45655bb1fad6347c3cb58e46190598217551b1500f18542d2bbe5c87120cb6927f5a074a59166fbdd9468f0a299 languageName: node linkType: hard @@ -14212,33 +14225,36 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.18.0": - version: 0.18.20 - resolution: "esbuild@npm:0.18.20" - dependencies: - "@esbuild/android-arm": "npm:0.18.20" - "@esbuild/android-arm64": "npm:0.18.20" - "@esbuild/android-x64": "npm:0.18.20" - "@esbuild/darwin-arm64": "npm:0.18.20" - "@esbuild/darwin-x64": "npm:0.18.20" - "@esbuild/freebsd-arm64": "npm:0.18.20" - "@esbuild/freebsd-x64": "npm:0.18.20" - "@esbuild/linux-arm": "npm:0.18.20" - "@esbuild/linux-arm64": "npm:0.18.20" - "@esbuild/linux-ia32": "npm:0.18.20" - "@esbuild/linux-loong64": "npm:0.18.20" - "@esbuild/linux-mips64el": "npm:0.18.20" - "@esbuild/linux-ppc64": "npm:0.18.20" - "@esbuild/linux-riscv64": "npm:0.18.20" - "@esbuild/linux-s390x": "npm:0.18.20" - "@esbuild/linux-x64": "npm:0.18.20" - "@esbuild/netbsd-x64": "npm:0.18.20" - "@esbuild/openbsd-x64": "npm:0.18.20" - "@esbuild/sunos-x64": "npm:0.18.20" - "@esbuild/win32-arm64": "npm:0.18.20" - "@esbuild/win32-ia32": "npm:0.18.20" - "@esbuild/win32-x64": "npm:0.18.20" +"esbuild@npm:^0.20.1": + version: 0.20.1 + resolution: "esbuild@npm:0.20.1" + dependencies: + "@esbuild/aix-ppc64": "npm:0.20.1" + "@esbuild/android-arm": "npm:0.20.1" + "@esbuild/android-arm64": "npm:0.20.1" + "@esbuild/android-x64": "npm:0.20.1" + "@esbuild/darwin-arm64": "npm:0.20.1" + "@esbuild/darwin-x64": "npm:0.20.1" + "@esbuild/freebsd-arm64": "npm:0.20.1" + "@esbuild/freebsd-x64": "npm:0.20.1" + "@esbuild/linux-arm": "npm:0.20.1" + "@esbuild/linux-arm64": "npm:0.20.1" + "@esbuild/linux-ia32": "npm:0.20.1" + "@esbuild/linux-loong64": "npm:0.20.1" + "@esbuild/linux-mips64el": "npm:0.20.1" + "@esbuild/linux-ppc64": "npm:0.20.1" + "@esbuild/linux-riscv64": "npm:0.20.1" + "@esbuild/linux-s390x": "npm:0.20.1" + "@esbuild/linux-x64": "npm:0.20.1" + "@esbuild/netbsd-x64": "npm:0.20.1" + "@esbuild/openbsd-x64": "npm:0.20.1" + "@esbuild/sunos-x64": "npm:0.20.1" + "@esbuild/win32-arm64": "npm:0.20.1" + "@esbuild/win32-ia32": "npm:0.20.1" + "@esbuild/win32-x64": "npm:0.20.1" dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true "@esbuild/android-arm": optional: true "@esbuild/android-arm64": @@ -14285,7 +14301,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 473b1d92842f50a303cf948a11ebd5f69581cd254d599dd9d62f9989858e0533f64e83b723b5e1398a5b488c0f5fd088795b4235f65ecaf4f007d4b79f04bc88 + checksum: 7e0303cb80defd55f3f7b85108081afc9c2f3852dda13bf70975a89210f20cd658fc02540d34247401806cb069c4ec489f7cf0df833e040ee361826484926c3a languageName: node linkType: hard @@ -18210,7 +18226,7 @@ __metadata: languageName: node linkType: hard -"is-reference@npm:^3.0.0, is-reference@npm:^3.0.1": +"is-reference@npm:^3.0.0, is-reference@npm:^3.0.1, is-reference@npm:^3.0.2": version: 3.0.2 resolution: "is-reference@npm:3.0.2" dependencies: @@ -24975,16 +24991,16 @@ __metadata: languageName: node linkType: hard -"recast@npm:^0.23.1, recast@npm:^0.23.3": - version: 0.23.4 - resolution: "recast@npm:0.23.4" +"recast@npm:^0.23.1, recast@npm:^0.23.3, recast@npm:^0.23.5": + version: 0.23.5 + resolution: "recast@npm:0.23.5" dependencies: - assert: "npm:^2.0.0" ast-types: "npm:^0.16.1" esprima: "npm:~4.0.0" source-map: "npm:~0.6.1" + tiny-invariant: "npm:^1.3.3" tslib: "npm:^2.0.1" - checksum: d719633be8029e28f23b8191d4a525c5dbdac721792ab3cb5e9dfcf1694fb93f3c147b186916195a9c7fa0711f1e4990ba457cdcee02faed3899d4a80da1bd1f + checksum: 21dc93910d12c71da77072afc3d5d4cdf97783776842efa6fd2cd7c2798d3622ace5d2f05ca5133141ef93de8a0512cbe191fe835f325bd1722f186fe449d11a languageName: node linkType: hard @@ -27585,9 +27601,9 @@ __metadata: languageName: node linkType: hard -"svelte-check@npm:^3.6.1": - version: 3.6.2 - resolution: "svelte-check@npm:3.6.2" +"svelte-check@npm:^3.6.4": + version: 3.6.4 + resolution: "svelte-check@npm:3.6.4" dependencies: "@jridgewell/trace-mapping": "npm:^0.3.17" chokidar: "npm:^3.4.1" @@ -27601,7 +27617,7 @@ __metadata: svelte: ^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 bin: svelte-check: bin/svelte-check - checksum: 3f389df29268d4df9b561d0b206566e827af84923c70150b2dadfd407bcbdaccbfd561bd8b93884597de62477d62826ff1a5108854641078b692130441a49a55 + checksum: acbcc04c8c6ab7baee7ccf36ca134dcabe49fae103aa92661e7f80e01216623363fb794fec9a3f794f7003d55629373567ff485925dc33272f48cea63e7b2452 languageName: node linkType: hard @@ -27710,23 +27726,24 @@ __metadata: languageName: node linkType: hard -"svelte@npm:^5.0.0-next.28": - version: 5.0.0-next.28 - resolution: "svelte@npm:5.0.0-next.28" +"svelte@npm:^5.0.0-next.65": + version: 5.0.0-next.65 + resolution: "svelte@npm:5.0.0-next.65" dependencies: "@ampproject/remapping": "npm:^2.2.1" "@jridgewell/sourcemap-codec": "npm:^1.4.15" - acorn: "npm:^8.10.0" - acorn-typescript: "npm:^1.4.11" + "@types/estree": "npm:^1.0.5" + acorn: "npm:^8.11.3" + acorn-typescript: "npm:^1.4.13" aria-query: "npm:^5.3.0" axobject-query: "npm:^4.0.0" esm-env: "npm:^1.0.0" esrap: "npm:^1.2.1" - is-reference: "npm:^3.0.1" + is-reference: "npm:^3.0.2" locate-character: "npm:^3.0.0" - magic-string: "npm:^0.30.4" - zimmerframe: "npm:^1.1.0" - checksum: d309cd3d1a9fe16c67a626af867288b02f6e7c49311c851aeb0f36feb5ab9603ca5594338fb933dbbada41b26faea6dcef52ed6ab3e86f54626545e53059eb28 + magic-string: "npm:^0.30.5" + zimmerframe: "npm:^1.1.2" + checksum: 6a686847f887d2871eabce4888916cba6aec5bae924a76fd01f4098db1c0053d4e5d6434070d0a048eac75eaddd4fd40e3fae625a0253464f7baa6b0f147f209 languageName: node linkType: hard @@ -28012,10 +28029,10 @@ __metadata: languageName: node linkType: hard -"tiny-invariant@npm:^1.3.1": - version: 1.3.1 - resolution: "tiny-invariant@npm:1.3.1" - checksum: 5b87c1d52847d9452b60d0dcb77011b459044e0361ca8253bfe7b43d6288106e12af926adb709a6fc28900e3864349b91dad9a4ac93c39aa15f360b26c2ff4db +"tiny-invariant@npm:^1.3.1, tiny-invariant@npm:^1.3.3": + version: 1.3.3 + resolution: "tiny-invariant@npm:1.3.3" + checksum: 65af4a07324b591a059b35269cd696aba21bef2107f29b9f5894d83cc143159a204b299553435b03874ebb5b94d019afa8b8eff241c8a4cfee95872c2e1c1c4a languageName: node linkType: hard @@ -28380,7 +28397,7 @@ __metadata: languageName: node linkType: hard -"tsconfig-paths@npm:^4.0.0, tsconfig-paths@npm:^4.1.2": +"tsconfig-paths@npm:^4.0.0, tsconfig-paths@npm:^4.1.2, tsconfig-paths@npm:^4.2.0": version: 4.2.0 resolution: "tsconfig-paths@npm:4.2.0" dependencies: @@ -30838,10 +30855,10 @@ __metadata: languageName: node linkType: hard -"zimmerframe@npm:^1.1.0": - version: 1.1.0 - resolution: "zimmerframe@npm:1.1.0" - checksum: dffe3f555bb000176ed9c7577e0fb0c3eddeceb6df9bb3ff870995bac3a51b40fab5443bd3dc47ce91c1f8ecf07282742efb80771ae6a088edc0340bb217f93d +"zimmerframe@npm:^1.1.2": + version: 1.1.2 + resolution: "zimmerframe@npm:1.1.2" + checksum: 8f693609c31cbb4449db223acd61661bc93b73e615f9db6fb8c86d4ceea84ca54cbbeebcf53cf74c22a1f923b92abd18e97988a5e175c76b6ab17238e5593a9d languageName: node linkType: hard diff --git a/docs/faq.md b/docs/faq.md index 1cceba208102..af3aa33158c9 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -2,36 +2,7 @@ title: 'Frequently Asked Questions' --- -Here are some answers to frequently asked questions. If you have a question, you can ask it by opening an issue on the [Storybook Repository](https://github.com/storybookjs/storybook/). - -- [Error: No angular.json file found](#error-no-angularjson-file-found) -- [How can I opt-out of Angular Ivy?](#how-can-i-opt-out-of-angular-ivy) -- [How can I opt-out of Angular ngcc?](#how-can-i-opt-out-of-angular-ngcc) -- [How can I run coverage tests with Create React App and leave out stories?](#how-can-i-run-coverage-tests-with-create-react-app-and-leave-out-stories) -- [How do I setup Storybook to share Webpack configuration with Next.js?](#how-do-i-setup-storybook-to-share-webpack-configuration-with-nextjs) -- [How do I fix module resolution in special environments?](#how-do-i-fix-module-resolution-in-special-environments) -- [How do I setup the new React Context Root API with Storybook?](#how-do-i-setup-the-new-react-context-root-api-with-storybook) -- [Why is there no addons channel?](#why-is-there-no-addons-channel) -- [Why aren't Controls visible in the Canvas panel but visible in Docs?](#why-arent-controls-visible-in-the-canvas-panel-but-visible-in-docs) -- [Why aren't the addons working in a composed Storybook?](#why-arent-the-addons-working-in-a-composed-storybook) -- [Can I have a Storybook with no local stories?](#can-i-have-a-storybook-with-no-local-stories) -- [Which community addons are compatible with the latest version of Storybook?](#which-community-addons-are-compatible-with-the-latest-version-of-storybook) -- [Is it possible to browse the documentation for past versions of Storybook?](#is-it-possible-to-browse-the-documentation-for-past-versions-of-storybook) -- [What icons are available for my toolbar or my addon?](#what-icons-are-available-for-my-toolbar-or-my-addon) -- [I see a "No Preview" error with a Storybook production build](#i-see-a-no-preview-error-with-a-storybook-production-build) -- [Can I use Storybook with Vue 2?](#can-i-use-storybook-with-vue-2) -- [Why aren't my code blocks highlighted with Storybook MDX](#why-arent-my-code-blocks-highlighted-with-storybook-mdx) -- [Why aren't my MDX stories working in Storybook?](#why-arent-my-mdx-stories-working-in-storybook) -- [Why are my mocked GraphQL queries failing with Storybook's MSW addon?](#why-are-my-mocked-graphql-queries-failing-with-storybooks-msw-addon) -- [Can I use other GraphQL providers with Storybook's MSW addon?](#can-i-use-other-graphql-providers-with-storybooks-msw-addon) -- [Can I mock GraphQL mutations with Storybook's MSW addon?](#can-i-mock-graphql-mutations-with-storybooks-msw-addon) -- [How can my code detect if it is running in Storybook?](#how-can-my-code-detect-if-it-is-running-in-storybook) -- [Why are my stories not showing up correctly when using certain characters?](#why-are-my-stories-not-showing-up-correctly-when-using-certain-characters) -- [Why are the TypeScript examples and documentation using `as` for type safety?](#why-are-the-typescript-examples-and-documentation-using-as-for-type-safety) -- [Why is Storybook's source loader returning undefined with curried functions?](#why-is-storybooks-source-loader-returning-undefined-with-curried-functions) -- [Why are my args no longer displaying the default values?](#why-are-my-args-no-longer-displaying-the-default-values) -- [Why isn't Storybook's test runner working?](#why-isnt-storybooks-test-runner-working) -- [How does Storybook handle environment variables?](#how-does-storybook-handle-environment-variables) +Here are some answers to frequently asked questions. If you have a question, you can ask it in our [GitHub discussions](https://github.com/storybookjs/storybook/discussions/new?category=help). ## Error: No angular.json file found @@ -222,7 +193,7 @@ Starting with Storybook version 6.0, we've introduced some great features aimed With this, we would like to point out that if you plan on using addons created by our fantastic community, you need to consider that some of those addons might be working with an outdated version of Storybook. -We're actively working to provide a better way to address this situation, but in the meantime, we would ask for a bit of caution on your end so that you don't run into unexpected problems. Let us know by creating an issue in the [Storybook repo](https://github.com/storybookjs/storybook/issues) so that we can gather information and create a curated list with those addons to help not only you but the rest of the community. +We're actively working to provide a better way to address this situation, but in the meantime, we'd like to ask for a bit of caution on your end so that you don't run into unexpected problems. Let us know by leaving a comment in the following [GitHub issue](https://github.com/storybookjs/storybook/issues/26031) so that we can gather information and expand the current list of addons that need to be updated to work with the latest version of Storybook. ## Is it possible to browse the documentation for past versions of Storybook? @@ -465,30 +436,6 @@ You can do this by checking for the `IS_STORYBOOK` global variable, which will e Storybook allows you to use most characters while naming your stories. Still, specific characters (e.g., `#`) can lead to issues when Storybook generates the internal identifier for the story, leading to collisions and incorrectly outputting the correct story. We recommend using such characters sparsely. -## Why are the TypeScript examples and documentation using `as` for type safety? - -We're aware that the default Typescript story construct might seem outdated and could potentially introduce a less than ideal way of handling type safety and strictness and could be rewritten as such: - -```ts -// Button.stories.ts|tsx - -import React from 'react'; -import type { ComponentStory, ComponentMeta } from '@storybook/react'; - -const StoryMeta: ComponentMeta = { - /* πŸ‘‡ The title prop is optional. - * See https://storybook.js.org/docs/configure/#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'Button', - component: Button, -}; - -export default meta; -``` - -Although valid, it introduces additional boilerplate code to the story definition. Instead, we're working towards implementing a safer mechanism based on what's currently being discussed in the following [issue](https://github.com/microsoft/TypeScript/issues/7481). Once the feature is released, we'll migrate our existing examples and documentation accordingly. - ## Why is Storybook's source loader returning undefined with curried functions? This is a known issue with Storybook. If you're interested in getting it fixed, open an issue with a [working reproduction](./contribute/how-to-reproduce.md) so that it can be triaged and fixed in future releases. diff --git a/docs/snippets/common/main-config-vite-final-env.js.mdx b/docs/snippets/common/main-config-vite-final-env.js.mdx index 73926f408471..e1ddc08d9644 100644 --- a/docs/snippets/common/main-config-vite-final-env.js.mdx +++ b/docs/snippets/common/main-config-vite-final-env.js.mdx @@ -1,14 +1,14 @@ ```js // .storybook/main.js|ts -import { mergeConfig } from 'vite'; - export default { stories: ['../src/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], core: { builder: '@storybook/builder-vite', }, async viteFinal(config, { configType }) { + const { mergeConfig } = await import('vite'); + if (configType === 'DEVELOPMENT') { // Your development configuration goes here } diff --git a/docs/snippets/common/main-config-vite-final.js.mdx b/docs/snippets/common/main-config-vite-final.js.mdx index b2987dcd412c..dbb5d70555ba 100644 --- a/docs/snippets/common/main-config-vite-final.js.mdx +++ b/docs/snippets/common/main-config-vite-final.js.mdx @@ -1,13 +1,13 @@ ```js // .storybook/main.js -import { mergeConfig } from 'vite'; - export default { // Replace your-framework with the framework you are using (e.g., react-vite, vue3-vite) framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], async viteFinal(config, { configType }) { + const { mergeConfig } = await import('vite'); + if (configType === 'DEVELOPMENT') { // Your development configuration goes here } diff --git a/docs/snippets/common/main-config-vite-final.ts-4-9.mdx b/docs/snippets/common/main-config-vite-final.ts-4-9.mdx index 42d7f8cf8b9d..f5f36cdafdba 100644 --- a/docs/snippets/common/main-config-vite-final.ts-4-9.mdx +++ b/docs/snippets/common/main-config-vite-final.ts-4-9.mdx @@ -4,13 +4,13 @@ // Replace your-framework with the framework you are using (e.g., react-vite, vue3-vite) import type { StorybookConfig } from '@storybook/your-framework'; -import { mergeConfig } from 'vite'; - const config: StorybookConfig = { // Replace your-framework with the framework you are using (e.g., react-vite, vue3-vite) framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], async viteFinal(config, { configType }) { + const { mergeConfig } = await import('vite'); + if (configType === 'DEVELOPMENT') { // Your development configuration goes here } diff --git a/docs/snippets/common/main-config-vite-final.ts.mdx b/docs/snippets/common/main-config-vite-final.ts.mdx index a82973f98afc..2df5faf42cd5 100644 --- a/docs/snippets/common/main-config-vite-final.ts.mdx +++ b/docs/snippets/common/main-config-vite-final.ts.mdx @@ -3,12 +3,13 @@ // Replace your-framework with the framework you are using (e.g., react-vite, vue3-vite) import type { StorybookConfig } from '@storybook/your-framework'; -import { mergeConfig } from 'vite'; const config = { framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], async viteFinal(config, { configType }) { + const { mergeConfig } = await import('vite'); + if (configType === 'DEVELOPMENT') { // Your development configuration goes here } diff --git a/docs/snippets/common/portable-stories-jest-snapshot-test.js.mdx b/docs/snippets/common/portable-stories-jest-snapshot-test.js.mdx index 8a6f022a9492..4078691b055d 100644 --- a/docs/snippets/common/portable-stories-jest-snapshot-test.js.mdx +++ b/docs/snippets/common/portable-stories-jest-snapshot-test.js.mdx @@ -25,7 +25,7 @@ const compose = (entry) => { function getAllStoryFiles() { // Place the glob you want to match your stories files const storyFiles = glob.sync( - path.join(__dirname, 'stories/**/*.(stories|story).@(js|jsx|mjs|ts|tsx)'), + path.join(__dirname, 'stories/**/*.{stories,story}.{js,jsx,mjs,ts,tsx}'), ); return storyFiles.map((filePath) => { diff --git a/docs/snippets/common/portable-stories-jest-snapshot-test.ts.mdx b/docs/snippets/common/portable-stories-jest-snapshot-test.ts.mdx index 45bcd3d4f7f1..07bb7a277c60 100644 --- a/docs/snippets/common/portable-stories-jest-snapshot-test.ts.mdx +++ b/docs/snippets/common/portable-stories-jest-snapshot-test.ts.mdx @@ -33,7 +33,7 @@ const compose = (entry: StoryFile): ReturnType> function getAllStoryFiles() { // Place the glob you want to match your stories files const storyFiles = glob.sync( - path.join(__dirname, 'stories/**/*.(stories|story).@(js|jsx|mjs|ts|tsx)'), + path.join(__dirname, 'stories/**/*.{stories,story}.{js,jsx,mjs,ts,tsx}'), ); return storyFiles.map((filePath) => { diff --git a/docs/snippets/common/storybook-vite-builder-aliasing.js.mdx b/docs/snippets/common/storybook-vite-builder-aliasing.js.mdx index ea5f2e7550de..c6dfb19d7581 100644 --- a/docs/snippets/common/storybook-vite-builder-aliasing.js.mdx +++ b/docs/snippets/common/storybook-vite-builder-aliasing.js.mdx @@ -1,8 +1,6 @@ ```js // .storybook/main.js|ts -import { mergeConfig } from 'vite'; - export default { stories: ['../src/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], addons: ['@storybook/addon-links', '@storybook/addon-essentials'], @@ -11,6 +9,8 @@ export default { }, async viteFinal(config) { // Merge custom configuration into the default config + const { mergeConfig } = await import('vite'); + return mergeConfig(config, { // Add dependencies to pre-optimization optimizeDeps: { diff --git a/docs/versions/next.json b/docs/versions/next.json index a1c6e9350e50..aefc27f51f56 100644 --- a/docs/versions/next.json +++ b/docs/versions/next.json @@ -1 +1 @@ -{"version":"8.0.0-rc.1","info":{"plain":"- CLI: Fix addon compatibility check error reporting in storybook dev - [#26258](https://github.com/storybookjs/storybook/pull/26258), thanks [@yannbf](https://github.com/yannbf)!\n- Onboarding: Fix manager dist reference - [#26282](https://github.com/storybookjs/storybook/pull/26282), thanks [@shilman](https://github.com/shilman)!\n- ReactVite: Docgen ignore un-parsable files - [#26254](https://github.com/storybookjs/storybook/pull/26254), thanks [@ndelangen](https://github.com/ndelangen)!"}} +{"version":"8.0.0-rc.2","info":{"plain":"- CLI: Add @storybook/addons automigration - [#26295](https://github.com/storybookjs/storybook/pull/26295), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)!\n- CLI: Fix vite config automigration to resolve from project root - [#26262](https://github.com/storybookjs/storybook/pull/26262), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)!\n- CLI: Improve `add` command & add tests - [#26298](https://github.com/storybookjs/storybook/pull/26298), thanks [@ndelangen](https://github.com/ndelangen)!\n- CLI: Update minimum Node.js version requirement - [#26312](https://github.com/storybookjs/storybook/pull/26312), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)!\n- CSF-tools/Codemods: Upgrade recast - [#26286](https://github.com/storybookjs/storybook/pull/26286), thanks [@43081j](https://github.com/43081j)!\n- Controls: Fix type summary when table.type unset - [#26283](https://github.com/storybookjs/storybook/pull/26283), thanks [@shilman](https://github.com/shilman)!\n- Core: Add event when serverChannel disconnects - [#26322](https://github.com/storybookjs/storybook/pull/26322), thanks [@ndelangen](https://github.com/ndelangen)!\n- Core: Fix composition of storybooks on same origin - [#26304](https://github.com/storybookjs/storybook/pull/26304), thanks [@ndelangen](https://github.com/ndelangen)!\n- Portable stories: Improve existing APIs, add loaders support - [#26267](https://github.com/storybookjs/storybook/pull/26267), thanks [@yannbf](https://github.com/yannbf)!\n- React: Handle TypeScript path aliases in react-docgen loader - [#26273](https://github.com/storybookjs/storybook/pull/26273), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)!\n- Svelte: Support `5.0.0-next.65` prerelease - [#26188](https://github.com/storybookjs/storybook/pull/26188), thanks [@JReinhold](https://github.com/JReinhold)!\n- Upgrade: Add missing isUpgrade parameter to automigrate function - [#26293](https://github.com/storybookjs/storybook/pull/26293), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)!\n- Vue: Return component from `composeStory` - [#26317](https://github.com/storybookjs/storybook/pull/26317), thanks [@JReinhold](https://github.com/JReinhold)!"}} diff --git a/scripts/package.json b/scripts/package.json index 52d2013ec288..542bca5d2fd0 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -55,7 +55,7 @@ }, "resolutions": { "@testing-library/jest-dom": "^5.11.9", - "esbuild": "^0.18.0", + "esbuild": "^0.20.1", "serialize-javascript": "^3.1.0", "type-fest": "~2.19" }, @@ -122,7 +122,7 @@ "detect-port": "^1.3.0", "ejs": "^3.1.8", "ejs-lint": "^2.0.0", - "esbuild": "^0.18.0", + "esbuild": "^0.20.1", "esbuild-plugin-alias": "^0.2.1", "esbuild-register": "^3.5.0", "eslint": "^8.56.0", @@ -160,7 +160,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "read-pkg-up": "^7.0.1", - "recast": "^0.23.1", + "recast": "^0.23.5", "remark": "^14.0.3", "remark-cli": "^12.0.0", "remark-lint": "^9.1.2", diff --git a/scripts/release/generate-pr-description.ts b/scripts/release/generate-pr-description.ts index 438c13fb4a91..7b4e397dea2c 100644 --- a/scripts/release/generate-pr-description.ts +++ b/scripts/release/generate-pr-description.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import chalk from 'chalk'; import program from 'commander'; import { z } from 'zod'; @@ -65,7 +64,6 @@ export const mapToChangelist = ({ }): string => { return changes .filter((change) => { - // eslint-disable-next-line no-restricted-syntax for (const titleToIgnore of CHANGE_TITLES_TO_IGNORE) { if (change.title?.match(titleToIgnore)) { return false; @@ -227,7 +225,7 @@ export const generateNonReleaseDescription = ( - Merge this PR - [Follow the run of the publish action](https://github.com/storybookjs/storybook/actions/workflows/publish.yml)` // don't mention contributors in the release PR, to avoid spamming them - .replaceAll('[@', '[@ ') + .replaceAll('@', '') .replaceAll('"', '\\"') .replaceAll('`', '\\`') .replaceAll("'", "\\'") diff --git a/scripts/release/utils/get-changes.ts b/scripts/release/utils/get-changes.ts index 416ea624fb50..8d2811d486db 100644 --- a/scripts/release/utils/get-changes.ts +++ b/scripts/release/utils/get-changes.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import chalk from 'chalk'; import semver from 'semver'; import type { PullRequestInfo } from './get-github-info'; @@ -206,11 +205,11 @@ export const getChangelogText = ({ return entry.labels?.some((label) => Object.keys(RELEASED_LABELS).includes(label)); }) .map((entry) => { - const { title, links } = entry; - const { pull, commit, user } = links; + const { title, user, links } = entry; + const { pull, commit } = links; return pull - ? `- ${title} - ${pull}, thanks ${user}!` - : `- ⚠️ _Direct commit_ ${title} - ${commit} by ${user}`; + ? `- ${title} - ${pull}, thanks @${user}!` + : `- ⚠️ _Direct commit_ ${title} - ${commit} by @${user}`; }) .sort(); const text = [heading, '', ...formattedEntries].join('\n'); diff --git a/scripts/release/utils/get-github-info.ts b/scripts/release/utils/get-github-info.ts index bdfe995a9001..e124026d7133 100644 --- a/scripts/release/utils/get-github-info.ts +++ b/scripts/release/utils/get-github-info.ts @@ -73,7 +73,7 @@ function makeQuery(repos: ReposWithCommitsAndPRsToFetch) { nodes { name } - } + } mergeCommit { commitUrl oid diff --git a/scripts/sandbox/generate.ts b/scripts/sandbox/generate.ts index 27f17f8c7ad0..410d49373d93 100755 --- a/scripts/sandbox/generate.ts +++ b/scripts/sandbox/generate.ts @@ -27,6 +27,13 @@ import { REPROS_DIRECTORY, LOCAL_REGISTRY_URL, } from '../utils/constants'; +import * as ghActions from '@actions/core'; +import dedent from 'ts-dedent'; + +const isCI = process.env.GITHUB_ACTIONS === 'true'; + +class BeforeScriptExecutionError extends Error {} +class StorybookInitError extends Error {} const sbInit = async ( cwd: string, @@ -148,76 +155,150 @@ const runGenerators = async ( const limit = pLimit(1); - await Promise.all( + const generationResults = await Promise.allSettled( generators.map(({ dirName, name, script, expected, env }) => limit(async () => { - let flags: string[] = []; - if (expected.renderer === '@storybook/html') flags = ['--type html']; - else if (expected.renderer === '@storybook/server') flags = ['--type server']; - - const time = process.hrtime(); - console.log(`🧬 Generating ${name}`); - const baseDir = join(REPROS_DIRECTORY, dirName); const beforeDir = join(baseDir, BEFORE_DIR_NAME); - await emptyDir(baseDir); - - // We do the creation inside a temp dir to avoid yarn container problems - const createBaseDir = directory(); - if (!script.includes('pnp')) { - await setupYarn({ cwd: createBaseDir }); - } - - const createBeforeDir = join(createBaseDir, BEFORE_DIR_NAME); - - // Some tools refuse to run inside an existing directory and replace the contents, - // where as others are very picky about what directories can be called. So we need to - // handle different modes of operation. - if (script.includes('{{beforeDir}}')) { - const scriptWithBeforeDir = script.replaceAll('{{beforeDir}}', BEFORE_DIR_NAME); - await runCommand( - scriptWithBeforeDir, - { - cwd: createBaseDir, - timeout: SCRIPT_TIMEOUT, - }, - debug + try { + let flags: string[] = []; + if (expected.renderer === '@storybook/html') flags = ['--type html']; + else if (expected.renderer === '@storybook/server') flags = ['--type server']; + + const time = process.hrtime(); + console.log(`🧬 Generating ${name} (${{ dirName }})`); + await emptyDir(baseDir); + + // We do the creation inside a temp dir to avoid yarn container problems + const createBaseDir = directory(); + if (!script.includes('pnp')) { + await setupYarn({ cwd: createBaseDir }); + } + + const createBeforeDir = join(createBaseDir, BEFORE_DIR_NAME); + + // Some tools refuse to run inside an existing directory and replace the contents, + // where as others are very picky about what directories can be called. So we need to + // handle different modes of operation. + try { + if (script.includes('{{beforeDir}}')) { + const scriptWithBeforeDir = script.replaceAll('{{beforeDir}}', BEFORE_DIR_NAME); + await runCommand( + scriptWithBeforeDir, + { + cwd: createBaseDir, + timeout: SCRIPT_TIMEOUT, + }, + debug + ); + } else { + await ensureDir(createBeforeDir); + await runCommand(script, { cwd: createBeforeDir, timeout: SCRIPT_TIMEOUT }, debug); + } + } catch (error) { + const message = `❌ Failed to execute before-script for template: ${name} (${dirName})`; + if (isCI) { + ghActions.error(dedent`${message} + ${(error as any).stack}`); + } else { + console.error(message); + console.error(error); + } + throw new BeforeScriptExecutionError(message, { cause: error }); + } + + await localizeYarnConfigFiles(createBaseDir, createBeforeDir); + + // Now move the created before dir into it's final location and add storybook + await move(createBeforeDir, beforeDir); + + // Make sure there are no git projects in the folder + await remove(join(beforeDir, '.git')); + + try { + await addStorybook({ baseDir, localRegistry, flags, debug, env }); + } catch (error) { + const message = `❌ Failed to initialize Storybook in template: ${name} (${dirName})`; + if (isCI) { + ghActions.error(dedent`${message} + ${(error as any).stack}`); + } else { + console.error(message); + console.error(error); + } + throw new StorybookInitError(message, { + cause: error, + }); + } + await addDocumentation(baseDir, { name, dirName }); + + console.log( + `βœ… Generated ${name} (${dirName}) in ./${relative( + process.cwd(), + baseDir + )} successfully in ${prettyTime(process.hrtime(time))}` ); - } else { - await ensureDir(createBeforeDir); - await runCommand(script, { cwd: createBeforeDir, timeout: SCRIPT_TIMEOUT }, debug); + } catch (error) { + throw error; + } finally { + // Remove node_modules to save space and avoid GH actions failing + // They're not uploaded to the git sandboxes repo anyway + if (process.env.CLEANUP_SANDBOX_NODE_MODULES) { + console.log(`πŸ—‘οΈ Removing ${join(beforeDir, 'node_modules')}`); + await remove(join(beforeDir, 'node_modules')); + console.log(`πŸ—‘οΈ Removing ${join(baseDir, AFTER_DIR_NAME, 'node_modules')}`); + await remove(join(baseDir, AFTER_DIR_NAME, 'node_modules')); + } } + }) + ) + ); - await localizeYarnConfigFiles(createBaseDir, createBeforeDir); - - // Now move the created before dir into it's final location and add storybook - await move(createBeforeDir, beforeDir); + const hasGenerationErrors = generationResults.some((result) => result.status === 'rejected'); - // Make sure there are no git projects in the folder - await remove(join(beforeDir, '.git')); + if (!isCI) { + if (hasGenerationErrors) { + throw new Error(`Some sandboxes failed to generate`); + } + return; + } - await addStorybook({ baseDir, localRegistry, flags, debug, env }); + ghActions.summary.addHeading('Sandbox generation summary'); - await addDocumentation(baseDir, { name, dirName }); + if (!hasGenerationErrors) { + await ghActions.summary.addRaw('βœ… Success!').write(); + return; + } - // Remove node_modules to save space and avoid GH actions failing - // They're not uploaded to the git sandboxes repo anyway - if (process.env.CLEANUP_SANDBOX_NODE_MODULES) { - console.log(`πŸ—‘οΈ Removing ${join(beforeDir, 'node_modules')}`); - await remove(join(beforeDir, 'node_modules')); - console.log(`πŸ—‘οΈ Removing ${join(baseDir, AFTER_DIR_NAME, 'node_modules')}`); - await remove(join(baseDir, AFTER_DIR_NAME, 'node_modules')); + await ghActions.summary + .addRaw('Some sandboxes failed, see the job log for detailed errors') + .addTable([ + [ + { data: 'Name', header: true }, + { data: 'Key', header: true }, + { data: 'Result', header: true }, + ], + ...generationResults.map((result, index) => { + const { name, dirName } = generators[index]; + const row = [name, `\`${dirName}\``]; + if (result.status === 'fulfilled') { + row.push('🟒 Pass'); + return row; } + const generationError = (result as PromiseRejectedResult).reason as Error; + if (generationError instanceof BeforeScriptExecutionError) { + row.push('πŸ”΄ Failed to execute before script'); + } else if (generationError instanceof StorybookInitError) { + row.push('πŸ”΄ Failed to initialize Storybook'); + } else { + row.push('πŸ”΄ Failed with unknown error'); + } + return row; + }), + ]) + .write(); - console.log( - `βœ… Created ${dirName} in ./${relative( - process.cwd(), - baseDir - )} successfully in ${prettyTime(process.hrtime(time))}` - ); - }) - ) - ); + throw new Error(`Some sandboxes failed to generate`); }; export const options = createOptions({ @@ -278,7 +359,7 @@ if (esMain(import.meta.url)) { .action((optionValues) => { generate(optionValues) .catch((e) => { - console.trace(e); + console.error(e); process.exit(1); }) .then(() => { diff --git a/scripts/sandbox/publish.ts b/scripts/sandbox/publish.ts index e4307690efdd..334e8a9177eb 100755 --- a/scripts/sandbox/publish.ts +++ b/scripts/sandbox/publish.ts @@ -1,14 +1,15 @@ import program from 'commander'; -import { join } from 'path'; +import { dirname, join, relative } from 'path'; import { existsSync } from 'fs'; import * as tempy from 'tempy'; -import { copy, emptyDir, readdir, remove, stat, writeFile } from 'fs-extra'; +import { copy, emptyDir, remove, writeFile } from 'fs-extra'; import { execaCommand } from 'execa'; import { getTemplatesData, renderTemplate } from './utils/template'; // eslint-disable-next-line import/no-cycle import { commitAllToGit } from './utils/git'; import { REPROS_DIRECTORY } from '../utils/constants'; +import { glob } from 'glob'; export const logger = console; @@ -31,15 +32,21 @@ const publish = async (options: PublishOptions & { tmpFolder: string }) => { // otherwise old files will stick around and result inconsistent states logger.log(`πŸ—‘ Delete existing template dirs from clone`); - const files = await Promise.all( - ( - await readdir(REPROS_DIRECTORY) - ).map(async (f) => ({ path: f, stats: await stat(join(REPROS_DIRECTORY, f)) })) - ); + + // empty all existing directories for sandboxes that have a successful after-storybook directory await Promise.all( - files - .filter(({ stats, path }) => stats.isDirectory && !path.startsWith('.')) - .map(async ({ path }) => emptyDir(join(tmpFolder, path))) + // find all successfully generated after-storybook/README.md files + // eg. /home/repros/react-vite/default-ts/after-storybook/README.md + // README.md being the last file generated, thus representing a successful generation + (await glob(join(REPROS_DIRECTORY, '**', 'after-storybook/README.md'))).map((readmePath) => { + // get the after-storybook path relative to the source 'repros' directory + // eg. ./react-vite/default-ts/after-storybook + const pathRelativeToSource = relative(REPROS_DIRECTORY, dirname(readmePath)); + // get the actual path to the corresponding sandbox directory in the clone + // eg. /home/sandboxes-clone/react-vite/default-ts + const sandboxDirectoryToEmpty = join(tmpFolder, pathRelativeToSource, '..'); + return emptyDir(sandboxDirectoryToEmpty); + }) ); logger.log(`🚚 Moving template files into the repository`); diff --git a/scripts/yarn.lock b/scripts/yarn.lock index fd8ab47be372..4bd7c8b5c0c3 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -1490,156 +1490,163 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/android-arm64@npm:0.18.20" +"@esbuild/aix-ppc64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/aix-ppc64@npm:0.20.1" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/android-arm64@npm:0.20.1" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/android-arm@npm:0.18.20" +"@esbuild/android-arm@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/android-arm@npm:0.20.1" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/android-x64@npm:0.18.20" +"@esbuild/android-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/android-x64@npm:0.20.1" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/darwin-arm64@npm:0.18.20" +"@esbuild/darwin-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/darwin-arm64@npm:0.20.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/darwin-x64@npm:0.18.20" +"@esbuild/darwin-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/darwin-x64@npm:0.20.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/freebsd-arm64@npm:0.18.20" +"@esbuild/freebsd-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/freebsd-arm64@npm:0.20.1" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/freebsd-x64@npm:0.18.20" +"@esbuild/freebsd-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/freebsd-x64@npm:0.20.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-arm64@npm:0.18.20" +"@esbuild/linux-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-arm64@npm:0.20.1" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-arm@npm:0.18.20" +"@esbuild/linux-arm@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-arm@npm:0.20.1" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-ia32@npm:0.18.20" +"@esbuild/linux-ia32@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-ia32@npm:0.20.1" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-loong64@npm:0.18.20" +"@esbuild/linux-loong64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-loong64@npm:0.20.1" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-mips64el@npm:0.18.20" +"@esbuild/linux-mips64el@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-mips64el@npm:0.20.1" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-ppc64@npm:0.18.20" +"@esbuild/linux-ppc64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-ppc64@npm:0.20.1" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-riscv64@npm:0.18.20" +"@esbuild/linux-riscv64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-riscv64@npm:0.20.1" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-s390x@npm:0.18.20" +"@esbuild/linux-s390x@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-s390x@npm:0.20.1" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-x64@npm:0.18.20" +"@esbuild/linux-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-x64@npm:0.20.1" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/netbsd-x64@npm:0.18.20" +"@esbuild/netbsd-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/netbsd-x64@npm:0.20.1" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/openbsd-x64@npm:0.18.20" +"@esbuild/openbsd-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/openbsd-x64@npm:0.20.1" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/sunos-x64@npm:0.18.20" +"@esbuild/sunos-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/sunos-x64@npm:0.20.1" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/win32-arm64@npm:0.18.20" +"@esbuild/win32-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/win32-arm64@npm:0.20.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/win32-ia32@npm:0.18.20" +"@esbuild/win32-ia32@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/win32-ia32@npm:0.20.1" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/win32-x64@npm:0.18.20" +"@esbuild/win32-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/win32-x64@npm:0.20.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -2762,7 +2769,7 @@ __metadata: detect-port: "npm:^1.3.0" ejs: "npm:^3.1.8" ejs-lint: "npm:^2.0.0" - esbuild: "npm:^0.18.0" + esbuild: "npm:^0.20.1" esbuild-plugin-alias: "npm:^0.2.1" esbuild-register: "npm:^3.5.0" eslint: "npm:^8.56.0" @@ -2800,7 +2807,7 @@ __metadata: react: "npm:^18.2.0" react-dom: "npm:^18.2.0" read-pkg-up: "npm:^7.0.1" - recast: "npm:^0.23.1" + recast: "npm:^0.23.5" remark: "npm:^14.0.3" remark-cli: "npm:^12.0.0" remark-lint: "npm:^9.1.2" @@ -4490,19 +4497,6 @@ __metadata: languageName: node linkType: hard -"assert@npm:^2.0.0": - version: 2.1.0 - resolution: "assert@npm:2.1.0" - dependencies: - call-bind: "npm:^1.0.2" - is-nan: "npm:^1.3.2" - object-is: "npm:^1.1.5" - object.assign: "npm:^4.1.4" - util: "npm:^0.12.5" - checksum: 7271a5da883c256a1fa690677bf1dd9d6aa882139f2bed1cd15da4f9e7459683e1da8e32a203d6cc6767e5e0f730c77a9532a87b896b4b0af0dd535f668775f0 - languageName: node - linkType: hard - "assertion-error@npm:^1.1.0": version: 1.1.0 resolution: "assertion-error@npm:1.1.0" @@ -6567,33 +6561,36 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.18.0": - version: 0.18.20 - resolution: "esbuild@npm:0.18.20" - dependencies: - "@esbuild/android-arm": "npm:0.18.20" - "@esbuild/android-arm64": "npm:0.18.20" - "@esbuild/android-x64": "npm:0.18.20" - "@esbuild/darwin-arm64": "npm:0.18.20" - "@esbuild/darwin-x64": "npm:0.18.20" - "@esbuild/freebsd-arm64": "npm:0.18.20" - "@esbuild/freebsd-x64": "npm:0.18.20" - "@esbuild/linux-arm": "npm:0.18.20" - "@esbuild/linux-arm64": "npm:0.18.20" - "@esbuild/linux-ia32": "npm:0.18.20" - "@esbuild/linux-loong64": "npm:0.18.20" - "@esbuild/linux-mips64el": "npm:0.18.20" - "@esbuild/linux-ppc64": "npm:0.18.20" - "@esbuild/linux-riscv64": "npm:0.18.20" - "@esbuild/linux-s390x": "npm:0.18.20" - "@esbuild/linux-x64": "npm:0.18.20" - "@esbuild/netbsd-x64": "npm:0.18.20" - "@esbuild/openbsd-x64": "npm:0.18.20" - "@esbuild/sunos-x64": "npm:0.18.20" - "@esbuild/win32-arm64": "npm:0.18.20" - "@esbuild/win32-ia32": "npm:0.18.20" - "@esbuild/win32-x64": "npm:0.18.20" +"esbuild@npm:^0.20.1": + version: 0.20.1 + resolution: "esbuild@npm:0.20.1" + dependencies: + "@esbuild/aix-ppc64": "npm:0.20.1" + "@esbuild/android-arm": "npm:0.20.1" + "@esbuild/android-arm64": "npm:0.20.1" + "@esbuild/android-x64": "npm:0.20.1" + "@esbuild/darwin-arm64": "npm:0.20.1" + "@esbuild/darwin-x64": "npm:0.20.1" + "@esbuild/freebsd-arm64": "npm:0.20.1" + "@esbuild/freebsd-x64": "npm:0.20.1" + "@esbuild/linux-arm": "npm:0.20.1" + "@esbuild/linux-arm64": "npm:0.20.1" + "@esbuild/linux-ia32": "npm:0.20.1" + "@esbuild/linux-loong64": "npm:0.20.1" + "@esbuild/linux-mips64el": "npm:0.20.1" + "@esbuild/linux-ppc64": "npm:0.20.1" + "@esbuild/linux-riscv64": "npm:0.20.1" + "@esbuild/linux-s390x": "npm:0.20.1" + "@esbuild/linux-x64": "npm:0.20.1" + "@esbuild/netbsd-x64": "npm:0.20.1" + "@esbuild/openbsd-x64": "npm:0.20.1" + "@esbuild/sunos-x64": "npm:0.20.1" + "@esbuild/win32-arm64": "npm:0.20.1" + "@esbuild/win32-ia32": "npm:0.20.1" + "@esbuild/win32-x64": "npm:0.20.1" dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true "@esbuild/android-arm": optional: true "@esbuild/android-arm64": @@ -6640,7 +6637,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 473b1d92842f50a303cf948a11ebd5f69581cd254d599dd9d62f9989858e0533f64e83b723b5e1398a5b488c0f5fd088795b4235f65ecaf4f007d4b79f04bc88 + checksum: 7e0303cb80defd55f3f7b85108081afc9c2f3852dda13bf70975a89210f20cd658fc02540d34247401806cb069c4ec489f7cf0df833e040ee361826484926c3a languageName: node linkType: hard @@ -8943,16 +8940,6 @@ __metadata: languageName: node linkType: hard -"is-nan@npm:^1.3.2": - version: 1.3.2 - resolution: "is-nan@npm:1.3.2" - dependencies: - call-bind: "npm:^1.0.0" - define-properties: "npm:^1.1.3" - checksum: 8bfb286f85763f9c2e28ea32e9127702fe980ffd15fa5d63ade3be7786559e6e21355d3625dd364c769c033c5aedf0a2ed3d4025d336abf1b9241e3d9eddc5b0 - languageName: node - linkType: hard - "is-natural-number@npm:^4.0.1": version: 4.0.1 resolution: "is-natural-number@npm:4.0.1" @@ -12752,16 +12739,16 @@ __metadata: languageName: node linkType: hard -"recast@npm:^0.23.1": - version: 0.23.4 - resolution: "recast@npm:0.23.4" +"recast@npm:^0.23.5": + version: 0.23.5 + resolution: "recast@npm:0.23.5" dependencies: - assert: "npm:^2.0.0" ast-types: "npm:^0.16.1" esprima: "npm:~4.0.0" source-map: "npm:~0.6.1" + tiny-invariant: "npm:^1.3.3" tslib: "npm:^2.0.1" - checksum: d719633be8029e28f23b8191d4a525c5dbdac721792ab3cb5e9dfcf1694fb93f3c147b186916195a9c7fa0711f1e4990ba457cdcee02faed3899d4a80da1bd1f + checksum: 21dc93910d12c71da77072afc3d5d4cdf97783776842efa6fd2cd7c2798d3622ace5d2f05ca5133141ef93de8a0512cbe191fe835f325bd1722f186fe449d11a languageName: node linkType: hard @@ -14576,10 +14563,10 @@ __metadata: languageName: node linkType: hard -"tiny-invariant@npm:^1.3.1": - version: 1.3.1 - resolution: "tiny-invariant@npm:1.3.1" - checksum: 5b87c1d52847d9452b60d0dcb77011b459044e0361ca8253bfe7b43d6288106e12af926adb709a6fc28900e3864349b91dad9a4ac93c39aa15f360b26c2ff4db +"tiny-invariant@npm:^1.3.1, tiny-invariant@npm:^1.3.3": + version: 1.3.3 + resolution: "tiny-invariant@npm:1.3.3" + checksum: 65af4a07324b591a059b35269cd696aba21bef2107f29b9f5894d83cc143159a204b299553435b03874ebb5b94d019afa8b8eff241c8a4cfee95872c2e1c1c4a languageName: node linkType: hard @@ -15437,7 +15424,7 @@ __metadata: languageName: node linkType: hard -"util@npm:^0.12.4, util@npm:^0.12.5": +"util@npm:^0.12.4": version: 0.12.5 resolution: "util@npm:0.12.5" dependencies: