From e2a04bbed1d58d9d11d343d2bf10c15daf09d1f9 Mon Sep 17 00:00:00 2001
From: Valentin Palkovic <valentin@chromatic.com>
Date: Wed, 10 Jul 2024 11:39:57 +0200
Subject: [PATCH 01/38] Build: Update git push commands in publish.yml

---
 .github/workflows/publish.yml | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index c48b3a2df59d..73d56f904f4a 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -163,8 +163,8 @@ jobs:
         run: |
           git checkout next
           git pull
-          git push --force origin latest-release
-          git push --force origin main
+          git push origin --force next:latest-release
+          git push origin --force next:main
 
       - name: Sync CHANGELOG.md from `main` to `next`
         if: steps.target.outputs.target == 'main'
@@ -178,6 +178,7 @@ jobs:
           git commit -m "Update CHANGELOG.md for v${{ steps.version.outputs.current-version }} [skip ci]" || true
           git push origin next
 
+      # TODO: remove this step - @JReinhold
       - name: Sync version JSONs from `next-release` to `main`
         if: github.ref_name == 'next-release'
         working-directory: .

From 3ac991538f1903499bf6fa2aba9f90e974769c30 Mon Sep 17 00:00:00 2001
From: Tobias Diez <code@tobiasdiez.de>
Date: Sat, 3 Aug 2024 13:25:14 +0200
Subject: [PATCH 02/38] Build: Add packageManager property

---
 package.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 8328557199b0..f782b5a070a6 100644
--- a/package.json
+++ b/package.json
@@ -15,5 +15,6 @@
     "vite-ecosystem-ci:before-test": "node ./scripts/vite-ecosystem-ci/before-test.js && cd ./sandbox/react-vite-default-ts && yarn install",
     "vite-ecosystem-ci:build": "yarn task --task sandbox --template react-vite/default-ts",
     "vite-ecosystem-ci:test": "yarn task --task test-runner-dev --template react-vite/default-ts --start-from=dev"
-  }
+  },
+  "packageManager": "yarn@1.22.19"
 }

From 517e869aecfd39b6aa42366acbc0c1d2f3a8ea87 Mon Sep 17 00:00:00 2001
From: Tobias Diez <code@tobiasdiez.de>
Date: Sat, 3 Aug 2024 13:31:07 +0200
Subject: [PATCH 03/38] update yarn

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index f782b5a070a6..10d365dd855d 100644
--- a/package.json
+++ b/package.json
@@ -16,5 +16,5 @@
     "vite-ecosystem-ci:build": "yarn task --task sandbox --template react-vite/default-ts",
     "vite-ecosystem-ci:test": "yarn task --task test-runner-dev --template react-vite/default-ts --start-from=dev"
   },
-  "packageManager": "yarn@1.22.19"
+  "packageManager": "yarn@4.4.0+sha512.91d93b445d9284e7ed52931369bc89a663414e5582d00eea45c67ddc459a2582919eece27c412d6ffd1bd0793ff35399381cb229326b961798ce4f4cc60ddfdb"
 }

From 0545c3c86830cd4d12da6428210be02cbac60832 Mon Sep 17 00:00:00 2001
From: Tobias Diez <code@tobiasdiez.de>
Date: Sat, 3 Aug 2024 13:52:07 +0200
Subject: [PATCH 04/38] Add packageManager also to scripts

---
 scripts/package.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/scripts/package.json b/scripts/package.json
index 1d1e1662a7b2..d308a3ea5fd7 100644
--- a/scripts/package.json
+++ b/scripts/package.json
@@ -188,6 +188,7 @@
     "verdaccio": "^5.31.1",
     "verdaccio-auth-memory": "^10.2.2"
   },
+  "packageManager": "yarn@4.4.0+sha512.91d93b445d9284e7ed52931369bc89a663414e5582d00eea45c67ddc459a2582919eece27c412d6ffd1bd0793ff35399381cb229326b961798ce4f4cc60ddfdb",
   "engines": {
     "node": ">=18.0.0"
   }

From 7e10dd7fdac5e53896ddf0f25f1225909fcdbc68 Mon Sep 17 00:00:00 2001
From: Mikhail Shipov <mshipov@yandex.ru>
Date: Wed, 7 Aug 2024 12:12:22 +0300
Subject: [PATCH 05/38] Fix docs scroll for non-ascii anchors

---
 code/lib/blocks/src/blocks/DocsContainer.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/code/lib/blocks/src/blocks/DocsContainer.tsx b/code/lib/blocks/src/blocks/DocsContainer.tsx
index e5ad466f7f6a..a18f0cc4ad06 100644
--- a/code/lib/blocks/src/blocks/DocsContainer.tsx
+++ b/code/lib/blocks/src/blocks/DocsContainer.tsx
@@ -38,7 +38,7 @@ export const DocsContainer: FC<PropsWithChildren<DocsContainerProps>> = ({
     try {
       url = new URL(globalWindow.parent.location.toString());
       if (url.hash) {
-        const element = document.getElementById(url.hash.substring(1));
+        const element = document.getElementById(decodeURIComponent(url.hash.substring(1)));
         if (element) {
           // Introducing a delay to ensure scrolling works when it's a full refresh.
           setTimeout(() => {

From aec16d5b490d238050b6bdec3d169c77ed70498d Mon Sep 17 00:00:00 2001
From: 43081j <43081j@users.noreply.github.com>
Date: Sat, 10 Aug 2024 00:44:22 +0100
Subject: [PATCH 06/38] chore: upgrade `commander` dependency

Upgrades `commander` to latest version across the repo.
---
 code/core/package.json                       |  2 +-
 code/core/src/cli/bin/index.ts               |  9 ++++----
 code/lib/cli-storybook/package.json          |  2 +-
 code/lib/cli-storybook/src/bin/index.ts      |  5 ++---
 code/lib/create-storybook/package.json       |  2 +-
 code/lib/create-storybook/src/bin/index.ts   |  2 +-
 code/yarn.lock                               | 20 ++++++++---------
 scripts/build-package.ts                     |  2 +-
 scripts/check-package.ts                     |  2 +-
 scripts/package.json                         |  2 +-
 scripts/release/cancel-preparation-runs.ts   |  2 +-
 scripts/release/generate-pr-description.ts   |  2 +-
 scripts/release/is-pr-frozen.ts              |  2 +-
 scripts/release/is-prerelease.ts             |  2 +-
 scripts/release/is-version-published.ts      |  2 +-
 scripts/release/label-patches.ts             |  2 +-
 scripts/release/pick-patches.ts              |  2 +-
 scripts/release/publish.ts                   |  2 +-
 scripts/release/unreleased-changes-exists.ts |  2 +-
 scripts/release/version.ts                   |  2 +-
 scripts/release/write-changelog.ts           |  2 +-
 scripts/run-registry.ts                      |  2 +-
 scripts/sandbox/publish.ts                   |  2 +-
 scripts/utils/options.ts                     | 14 ++++++++----
 scripts/yarn.lock                            | 23 +++++++-------------
 25 files changed, 54 insertions(+), 57 deletions(-)

diff --git a/code/core/package.json b/code/core/package.json
index 400c9c392e52..fe8864a9e4d1 100644
--- a/code/core/package.json
+++ b/code/core/package.json
@@ -337,7 +337,7 @@
     "chai": "^4.4.1",
     "chalk": "^5.3.0",
     "cli-table3": "^0.6.1",
-    "commander": "^6.2.1",
+    "commander": "^12.1.0",
     "comment-parser": "^1.4.1",
     "compression": "^1.7.4",
     "copy-to-clipboard": "^3.3.1",
diff --git a/code/core/src/cli/bin/index.ts b/code/core/src/cli/bin/index.ts
index 38cbce23efb1..20cc55c1d809 100644
--- a/code/core/src/cli/bin/index.ts
+++ b/code/core/src/cli/bin/index.ts
@@ -4,7 +4,7 @@ import { addToGlobalContext } from '@storybook/core/telemetry';
 import { logger } from '@storybook/core/node-logger';
 
 import chalk from 'chalk';
-import program from 'commander';
+import { program } from 'commander';
 import { findPackageSync } from 'fd-package-json';
 import leven from 'leven';
 import invariant from 'tiny-invariant';
@@ -69,7 +69,7 @@ command('dev')
     'URL path to be appended when visiting Storybook for the first time'
   )
   .action(async (options) => {
-    logger.setLevel(program.loglevel);
+    logger.setLevel(options.loglevel);
     consoleLogger.log(chalk.bold(`${pkg.name} v${pkg.version}`) + chalk.reset('\n'));
 
     // The key is the field created in `options` variable for
@@ -109,7 +109,7 @@ command('build')
   .option('--test', 'Build stories optimized for testing purposes.')
   .action(async (options) => {
     process.env.NODE_ENV = process.env.NODE_ENV || 'production';
-    logger.setLevel(program.loglevel);
+    logger.setLevel(options.loglevel);
     consoleLogger.log(chalk.bold(`${pkg.name} v${pkg.version}\n`));
 
     // The key is the field created in `options` variable for
@@ -132,8 +132,7 @@ program.on('command:*', ([invalidCmd]) => {
     ' Invalid command: %s.\n See --help for a list of available commands.',
     invalidCmd
   );
-  // eslint-disable-next-line no-underscore-dangle
-  const availableCommands = program.commands.map((cmd) => cmd._name);
+  const availableCommands = program.commands.map((cmd) => cmd.name());
   const suggestion = availableCommands.find((cmd) => leven(cmd, invalidCmd) < 3);
   if (suggestion) {
     consoleLogger.info(`\n Did you mean ${suggestion}?`);
diff --git a/code/lib/cli-storybook/package.json b/code/lib/cli-storybook/package.json
index 9e46b26fabd6..307d56c0f688 100644
--- a/code/lib/cli-storybook/package.json
+++ b/code/lib/cli-storybook/package.json
@@ -45,7 +45,7 @@
     "@storybook/codemod": "workspace:*",
     "@types/semver": "^7.3.4",
     "chalk": "^4.1.0",
-    "commander": "^6.2.1",
+    "commander": "^12.1.0",
     "create-storybook": "workspace:*",
     "cross-spawn": "^7.0.3",
     "envinfo": "^7.7.3",
diff --git a/code/lib/cli-storybook/src/bin/index.ts b/code/lib/cli-storybook/src/bin/index.ts
index de33b71dbc62..34f70b540a0b 100644
--- a/code/lib/cli-storybook/src/bin/index.ts
+++ b/code/lib/cli-storybook/src/bin/index.ts
@@ -8,7 +8,7 @@ import { logger } from 'storybook/internal/node-logger';
 import { addToGlobalContext, telemetry } from 'storybook/internal/telemetry';
 
 import chalk from 'chalk';
-import program from 'commander';
+import { program } from 'commander';
 import envinfo from 'envinfo';
 import { findPackageSync } from 'fd-package-json';
 import leven from 'leven';
@@ -186,8 +186,7 @@ program.on('command:*', ([invalidCmd]) => {
     ' Invalid command: %s.\n See --help for a list of available commands.',
     invalidCmd
   );
-  // eslint-disable-next-line no-underscore-dangle
-  const availableCommands = program.commands.map((cmd) => cmd._name);
+  const availableCommands = program.commands.map((cmd) => cmd.name());
   const suggestion = availableCommands.find((cmd) => leven(cmd, invalidCmd) < 3);
   if (suggestion) {
     consoleLogger.info(`\n Did you mean ${suggestion}?`);
diff --git a/code/lib/create-storybook/package.json b/code/lib/create-storybook/package.json
index 778c28b8d975..8ae33bc65441 100644
--- a/code/lib/create-storybook/package.json
+++ b/code/lib/create-storybook/package.json
@@ -57,7 +57,7 @@
   "dependencies": {
     "@types/semver": "^7.3.4",
     "chalk": "^4.1.0",
-    "commander": "^6.2.1",
+    "commander": "^12.1.0",
     "execa": "^5.0.0",
     "fd-package-json": "^1.2.0",
     "find-up": "^5.0.0",
diff --git a/code/lib/create-storybook/src/bin/index.ts b/code/lib/create-storybook/src/bin/index.ts
index 758365d3b1ca..187e2811c38f 100644
--- a/code/lib/create-storybook/src/bin/index.ts
+++ b/code/lib/create-storybook/src/bin/index.ts
@@ -1,7 +1,7 @@
 import { versions } from 'storybook/internal/common';
 import { addToGlobalContext } from 'storybook/internal/telemetry';
 
-import program from 'commander';
+import { program } from 'commander';
 import { findPackageSync } from 'fd-package-json';
 import invariant from 'tiny-invariant';
 
diff --git a/code/yarn.lock b/code/yarn.lock
index 48f25f218799..a5c4beca95d8 100644
--- a/code/yarn.lock
+++ b/code/yarn.lock
@@ -5664,7 +5664,7 @@ __metadata:
     "@types/semver": "npm:^7.3.4"
     boxen: "npm:^7.1.1"
     chalk: "npm:^4.1.0"
-    commander: "npm:^6.2.1"
+    commander: "npm:^12.1.0"
     create-storybook: "workspace:*"
     cross-spawn: "npm:^7.0.3"
     envinfo: "npm:^7.7.3"
@@ -5838,7 +5838,7 @@ __metadata:
     chai: "npm:^4.4.1"
     chalk: "npm:^5.3.0"
     cli-table3: "npm:^0.6.1"
-    commander: "npm:^6.2.1"
+    commander: "npm:^12.1.0"
     comment-parser: "npm:^1.4.1"
     compression: "npm:^1.7.4"
     copy-to-clipboard: "npm:^3.3.1"
@@ -11657,6 +11657,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"commander@npm:^12.1.0":
+  version: 12.1.0
+  resolution: "commander@npm:12.1.0"
+  checksum: 10c0/6e1996680c083b3b897bfc1cfe1c58dfbcd9842fd43e1aaf8a795fbc237f65efcc860a3ef457b318e73f29a4f4a28f6403c3d653d021d960e4632dd45bde54a9
+  languageName: node
+  linkType: hard
+
 "commander@npm:^2.18.0, commander@npm:^2.19.0, commander@npm:^2.20.0":
   version: 2.20.3
   resolution: "commander@npm:2.20.3"
@@ -11671,13 +11678,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"commander@npm:^6.2.1":
-  version: 6.2.1
-  resolution: "commander@npm:6.2.1"
-  checksum: 10c0/85748abd9d18c8bc88febed58b98f66b7c591d9b5017cad459565761d7b29ca13b7783ea2ee5ce84bf235897333706c4ce29adf1ce15c8252780e7000e2ce9ea
-  languageName: node
-  linkType: hard
-
 "commander@npm:^8.3.0":
   version: 8.3.0
   resolution: "commander@npm:8.3.0"
@@ -12066,7 +12066,7 @@ __metadata:
     "@types/util-deprecate": "npm:^1.0.0"
     boxen: "npm:^7.1.1"
     chalk: "npm:^4.1.0"
-    commander: "npm:^6.2.1"
+    commander: "npm:^12.1.0"
     execa: "npm:^5.0.0"
     fd-package-json: "npm:^1.2.0"
     find-up: "npm:^5.0.0"
diff --git a/scripts/build-package.ts b/scripts/build-package.ts
index dda542237300..2e54ea02a19a 100644
--- a/scripts/build-package.ts
+++ b/scripts/build-package.ts
@@ -1,5 +1,5 @@
 import chalk from 'chalk';
-import program from 'commander';
+import { program } from 'commander';
 import { execaCommand } from 'execa';
 import { readJSON } from 'fs-extra';
 import { posix, resolve, sep } from 'path';
diff --git a/scripts/check-package.ts b/scripts/check-package.ts
index 4a54ef43a897..eed9a442b6c4 100644
--- a/scripts/check-package.ts
+++ b/scripts/check-package.ts
@@ -2,7 +2,7 @@
 // without having to build dts files for all packages in the monorepo.
 // It is not implemented yet for angular, svelte and vue.
 import chalk from 'chalk';
-import program from 'commander';
+import { program } from 'commander';
 import { execaCommand } from 'execa';
 import { readJSON } from 'fs-extra';
 import { resolve } from 'path';
diff --git a/scripts/package.json b/scripts/package.json
index 61ae03ea736e..e08aeb213c6b 100644
--- a/scripts/package.json
+++ b/scripts/package.json
@@ -100,7 +100,7 @@
     "chalk": "^4.1.0",
     "chromatic": "^11.5.5",
     "codecov": "^3.8.1",
-    "commander": "^6.2.1",
+    "commander": "^12.1.0",
     "cross-env": "^7.0.3",
     "cross-spawn": "^7.0.3",
     "danger": "^12.3.3",
diff --git a/scripts/release/cancel-preparation-runs.ts b/scripts/release/cancel-preparation-runs.ts
index 86cc0fb1d476..8359be287b92 100644
--- a/scripts/release/cancel-preparation-runs.ts
+++ b/scripts/release/cancel-preparation-runs.ts
@@ -3,7 +3,7 @@
  * It will fetch all active runs for the preparation workflows, and cancel them.
  */
 import chalk from 'chalk';
-import program from 'commander';
+import { program } from 'commander';
 import { dedent } from 'ts-dedent';
 
 import { esMain } from '../utils/esmain';
diff --git a/scripts/release/generate-pr-description.ts b/scripts/release/generate-pr-description.ts
index 38f6a9639d01..f1a79da2d6db 100644
--- a/scripts/release/generate-pr-description.ts
+++ b/scripts/release/generate-pr-description.ts
@@ -1,6 +1,6 @@
 import { setOutput } from '@actions/core';
 import chalk from 'chalk';
-import program from 'commander';
+import { program } from 'commander';
 import semver from 'semver';
 import { dedent } from 'ts-dedent';
 import { z } from 'zod';
diff --git a/scripts/release/is-pr-frozen.ts b/scripts/release/is-pr-frozen.ts
index fd632e10bce9..304e00d41b62 100644
--- a/scripts/release/is-pr-frozen.ts
+++ b/scripts/release/is-pr-frozen.ts
@@ -1,6 +1,6 @@
 import { setOutput } from '@actions/core';
 import chalk from 'chalk';
-import program from 'commander';
+import { program } from 'commander';
 import { readJson } from 'fs-extra';
 import path from 'path';
 
diff --git a/scripts/release/is-prerelease.ts b/scripts/release/is-prerelease.ts
index d154991b27cc..d92f17279b82 100644
--- a/scripts/release/is-prerelease.ts
+++ b/scripts/release/is-prerelease.ts
@@ -1,6 +1,6 @@
 import { setOutput } from '@actions/core';
 import chalk from 'chalk';
-import program from 'commander';
+import { program } from 'commander';
 import semver from 'semver';
 
 import { esMain } from '../utils/esmain';
diff --git a/scripts/release/is-version-published.ts b/scripts/release/is-version-published.ts
index 90d46399ab90..6af757eb654c 100644
--- a/scripts/release/is-version-published.ts
+++ b/scripts/release/is-version-published.ts
@@ -1,6 +1,6 @@
 import { setOutput } from '@actions/core';
 import chalk from 'chalk';
-import program from 'commander';
+import { program } from 'commander';
 
 import { esMain } from '../utils/esmain';
 import { getCurrentVersion } from './get-current-version';
diff --git a/scripts/release/label-patches.ts b/scripts/release/label-patches.ts
index a0d82f44fe60..c1d8fa945e11 100644
--- a/scripts/release/label-patches.ts
+++ b/scripts/release/label-patches.ts
@@ -1,4 +1,4 @@
-import program from 'commander';
+import { program } from 'commander';
 import ora from 'ora';
 import { v4 as uuidv4 } from 'uuid';
 
diff --git a/scripts/release/pick-patches.ts b/scripts/release/pick-patches.ts
index 49bb55317529..824f8c6d0cd9 100644
--- a/scripts/release/pick-patches.ts
+++ b/scripts/release/pick-patches.ts
@@ -1,6 +1,6 @@
 import { setOutput } from '@actions/core';
 import chalk from 'chalk';
-import program from 'commander';
+import { program } from 'commander';
 import ora from 'ora';
 import invariant from 'tiny-invariant';
 
diff --git a/scripts/release/publish.ts b/scripts/release/publish.ts
index 0e0136107649..976a1670d393 100644
--- a/scripts/release/publish.ts
+++ b/scripts/release/publish.ts
@@ -1,5 +1,5 @@
 import chalk from 'chalk';
-import program from 'commander';
+import { program } from 'commander';
 import { execaCommand } from 'execa';
 import { readJson } from 'fs-extra';
 import pRetry from 'p-retry';
diff --git a/scripts/release/unreleased-changes-exists.ts b/scripts/release/unreleased-changes-exists.ts
index e52dd198b05c..49c50ccd7d42 100644
--- a/scripts/release/unreleased-changes-exists.ts
+++ b/scripts/release/unreleased-changes-exists.ts
@@ -1,6 +1,6 @@
 import { setOutput } from '@actions/core';
 import chalk from 'chalk';
-import program from 'commander';
+import { program } from 'commander';
 import { intersection } from 'lodash';
 import { z } from 'zod';
 
diff --git a/scripts/release/version.ts b/scripts/release/version.ts
index 6574e4609ec9..9659f84f6e95 100644
--- a/scripts/release/version.ts
+++ b/scripts/release/version.ts
@@ -1,6 +1,6 @@
 import { setOutput } from '@actions/core';
 import chalk from 'chalk';
-import program from 'commander';
+import { program } from 'commander';
 import { execaCommand } from 'execa';
 import { readFile, readJson, writeFile, writeJson } from 'fs-extra';
 import path from 'path';
diff --git a/scripts/release/write-changelog.ts b/scripts/release/write-changelog.ts
index 43a03c8c497f..cacbb9bfa507 100644
--- a/scripts/release/write-changelog.ts
+++ b/scripts/release/write-changelog.ts
@@ -1,5 +1,5 @@
 import chalk from 'chalk';
-import program from 'commander';
+import { program } from 'commander';
 import { readFile, writeFile, writeJson } from 'fs-extra';
 import path from 'path';
 import semver from 'semver';
diff --git a/scripts/run-registry.ts b/scripts/run-registry.ts
index bb8aa97048fc..415c7695eb39 100755
--- a/scripts/run-registry.ts
+++ b/scripts/run-registry.ts
@@ -1,6 +1,6 @@
 import chalk from 'chalk';
 import { exec } from 'child_process';
-import program from 'commander';
+import { program } from 'commander';
 import { execa, execaSync } from 'execa';
 import { pathExists, readJSON, remove } from 'fs-extra';
 import { mkdir } from 'fs/promises';
diff --git a/scripts/sandbox/publish.ts b/scripts/sandbox/publish.ts
index ad8f5f11fcd8..7ddb549c3900 100755
--- a/scripts/sandbox/publish.ts
+++ b/scripts/sandbox/publish.ts
@@ -1,4 +1,4 @@
-import program from 'commander';
+import { program } from 'commander';
 import { execaCommand } from 'execa';
 import { existsSync } from 'fs';
 import { copy, emptyDir, remove, writeFile } from 'fs-extra';
diff --git a/scripts/utils/options.ts b/scripts/utils/options.ts
index a36f286b4141..3f9ce68fa4de 100644
--- a/scripts/utils/options.ts
+++ b/scripts/utils/options.ts
@@ -2,7 +2,7 @@
  * Use commander and prompts to gather a list of options for a script
  */
 import chalk from 'chalk';
-import program from 'commander';
+import { program } from 'commander';
 // eslint-disable-next-line import/extensions
 import kebabCase from 'lodash/kebabCase.js';
 import prompts from 'prompts';
@@ -116,8 +116,14 @@ function longFlag(key: OptionId, option: Option) {
   return inverse ? `no-${kebabCase(key)}` : kebabCase(key);
 }
 
-function optionFlags(key: OptionId, option: Option) {
-  const base = `-${shortFlag(key, option)}, --${longFlag(key, option)}`;
+function optionFlags(key: OptionId, option: Option, existingOptions: program.Option[]) {
+  const optionShortFlag = `-${shortFlag(key, option)}`;
+  let base;
+  if (existingOptions.some((opt) => opt.short === optionShortFlag)) {
+    base = `--${longFlag(key, option)}`;
+  } else {
+    base = `${optionShortFlag}, --${longFlag(key, option)}`;
+  }
   if (option.type === 'string' || option.type === 'string[]') {
     return `${base} <${key}>`;
   }
@@ -131,7 +137,7 @@ export function getOptions<TOptions extends OptionSpecifier>(
 ): MaybeOptionValues<TOptions> {
   Object.entries(options)
     .reduce((acc, [key, option]) => {
-      const flags = optionFlags(key, option);
+      const flags = optionFlags(key, option, acc.options);
 
       if (option.type === 'boolean') {
         return acc.option(flags, option.description, !!option.inverse);
diff --git a/scripts/yarn.lock b/scripts/yarn.lock
index a12ddedfc07e..fb4f6de2cdfc 100644
--- a/scripts/yarn.lock
+++ b/scripts/yarn.lock
@@ -1536,7 +1536,7 @@ __metadata:
     chalk: "npm:^4.1.0"
     chromatic: "npm:^11.5.5"
     codecov: "npm:^3.8.1"
-    commander: "npm:^6.2.1"
+    commander: "npm:^12.1.0"
     cross-env: "npm:^7.0.3"
     cross-spawn: "npm:^7.0.3"
     danger: "npm:^12.3.3"
@@ -4124,6 +4124,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"commander@npm:^12.1.0, commander@npm:~12.1.0":
+  version: 12.1.0
+  resolution: "commander@npm:12.1.0"
+  checksum: 10c0/6e1996680c083b3b897bfc1cfe1c58dfbcd9842fd43e1aaf8a795fbc237f65efcc860a3ef457b318e73f29a4f4a28f6403c3d653d021d960e4632dd45bde54a9
+  languageName: node
+  linkType: hard
+
 "commander@npm:^2.18.0, commander@npm:^2.8.1":
   version: 2.20.3
   resolution: "commander@npm:2.20.3"
@@ -4138,20 +4145,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"commander@npm:^6.2.1":
-  version: 6.2.1
-  resolution: "commander@npm:6.2.1"
-  checksum: 10c0/85748abd9d18c8bc88febed58b98f66b7c591d9b5017cad459565761d7b29ca13b7783ea2ee5ce84bf235897333706c4ce29adf1ce15c8252780e7000e2ce9ea
-  languageName: node
-  linkType: hard
-
-"commander@npm:~12.1.0":
-  version: 12.1.0
-  resolution: "commander@npm:12.1.0"
-  checksum: 10c0/6e1996680c083b3b897bfc1cfe1c58dfbcd9842fd43e1aaf8a795fbc237f65efcc860a3ef457b318e73f29a4f4a28f6403c3d653d021d960e4632dd45bde54a9
-  languageName: node
-  linkType: hard
-
 "compare-versions@npm:^3.6.0":
   version: 3.6.0
   resolution: "compare-versions@npm:3.6.0"

From fdf569b7e4bfd657fa52b324d97e77ce43be693d Mon Sep 17 00:00:00 2001
From: Norbert de Langen <ndelangen@me.com>
Date: Tue, 13 Aug 2024 09:27:01 +0200
Subject: [PATCH 07/38] fixes

---
 scripts/utils/options.ts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/scripts/utils/options.ts b/scripts/utils/options.ts
index 3f9ce68fa4de..6c6a6e4b2dd1 100644
--- a/scripts/utils/options.ts
+++ b/scripts/utils/options.ts
@@ -2,7 +2,7 @@
  * Use commander and prompts to gather a list of options for a script
  */
 import chalk from 'chalk';
-import { program } from 'commander';
+import { type Command, type Option as CommanderOption, program } from 'commander';
 // eslint-disable-next-line import/extensions
 import kebabCase from 'lodash/kebabCase.js';
 import prompts from 'prompts';
@@ -116,7 +116,7 @@ function longFlag(key: OptionId, option: Option) {
   return inverse ? `no-${kebabCase(key)}` : kebabCase(key);
 }
 
-function optionFlags(key: OptionId, option: Option, existingOptions: program.Option[]) {
+function optionFlags(key: OptionId, option: Option, existingOptions: CommanderOption[]) {
   const optionShortFlag = `-${shortFlag(key, option)}`;
   let base;
   if (existingOptions.some((opt) => opt.short === optionShortFlag)) {
@@ -131,7 +131,7 @@ function optionFlags(key: OptionId, option: Option, existingOptions: program.Opt
 }
 
 export function getOptions<TOptions extends OptionSpecifier>(
-  command: program.Command,
+  command: Command,
   options: TOptions,
   argv: string[]
 ): MaybeOptionValues<TOptions> {

From 0f6a2d36751592a8915e0937591a918d9e96fcf7 Mon Sep 17 00:00:00 2001
From: Norbert de Langen <ndelangen@me.com>
Date: Tue, 13 Aug 2024 13:14:04 +0200
Subject: [PATCH 08/38] fixes

---
 scripts/build-package.ts | 5 +++--
 scripts/check-package.ts | 5 +++--
 scripts/run-registry.ts  | 6 ++++--
 3 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/scripts/build-package.ts b/scripts/build-package.ts
index 2e54ea02a19a..60ac5b012e13 100644
--- a/scripts/build-package.ts
+++ b/scripts/build-package.ts
@@ -70,9 +70,10 @@ async function run() {
     .parse(process.argv);
 
   Object.keys(tasks).forEach((key) => {
+    const opts = program.opts();
     // checks if a flag is passed e.g. yarn build --@storybook/addon-docs --watch
-    const containsFlag = program.rawArgs.includes(tasks[key].suffix);
-    tasks[key].value = containsFlag || program.all;
+    const containsFlag = program.args.includes(tasks[key].suffix);
+    tasks[key].value = containsFlag || opts.all;
   });
 
   let selection;
diff --git a/scripts/check-package.ts b/scripts/check-package.ts
index eed9a442b6c4..c8873e6cf846 100644
--- a/scripts/check-package.ts
+++ b/scripts/check-package.ts
@@ -60,9 +60,10 @@ async function run() {
     .parse(process.argv);
 
   Object.keys(tasks).forEach((key) => {
+    const opts = program.opts();
     // checks if a flag is passed e.g. yarn check --@storybook/addon-docs --watch
-    const containsFlag = program.rawArgs.includes(tasks[key].suffix);
-    tasks[key].value = containsFlag || program.all;
+    const containsFlag = program.args.includes(tasks[key].suffix);
+    tasks[key].value = containsFlag || opts.all;
   });
 
   let selection;
diff --git a/scripts/run-registry.ts b/scripts/run-registry.ts
index aef5cf34ff9c..c01191f838a9 100755
--- a/scripts/run-registry.ts
+++ b/scripts/run-registry.ts
@@ -25,6 +25,8 @@ const logger = console;
 
 const root = resolvePath(__dirname, '..');
 
+const opts = program.opts();
+
 const startVerdaccio = async () => {
   const ready = {
     proxy: false,
@@ -193,13 +195,13 @@ const run = async () => {
 
   logger.log(`📦 found ${packages.length} storybook packages at version ${chalk.blue(version)}`);
 
-  if (program.publish) {
+  if (opts.publish) {
     await publish(packages, 'http://localhost:6002');
   }
 
   await execa('npx', ['rimraf', '.npmrc'], { cwd: root });
 
-  if (!program.open) {
+  if (!opts.open) {
     verdaccioServer.close();
     process.exit(0);
   }

From a80b43fca917738b29194364374576e74516e567 Mon Sep 17 00:00:00 2001
From: Norbert de Langen <ndelangen@me.com>
Date: Wed, 14 Aug 2024 09:20:59 +0200
Subject: [PATCH 09/38] dedupe and ignore ts error

---
 code/yarn.lock           | 348 +--------------------------------------
 scripts/utils/options.ts |   2 +-
 2 files changed, 4 insertions(+), 346 deletions(-)

diff --git a/code/yarn.lock b/code/yarn.lock
index c287ebf96f37..065f16d15192 100644
--- a/code/yarn.lock
+++ b/code/yarn.lock
@@ -2459,15 +2459,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@emnapi/runtime@npm:^1.1.0":
-  version: 1.1.1
-  resolution: "@emnapi/runtime@npm:1.1.1"
-  dependencies:
-    tslib: "npm:^2.4.0"
-  checksum: 10c0/c11ee57abf0ec643e64ccdace4b4fcc0b0c7b1117a191f969e84ae3669841aa90d2c17fa35b73f5a66fc0c843c8caca7bf11187faaeaa526bcfb7dbfb9b85de9
-  languageName: node
-  linkType: hard
-
 "@emnapi/runtime@npm:^1.1.1":
   version: 1.2.0
   resolution: "@emnapi/runtime@npm:1.2.0"
@@ -3390,18 +3381,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@img/sharp-darwin-arm64@npm:0.33.3":
-  version: 0.33.3
-  resolution: "@img/sharp-darwin-arm64@npm:0.33.3"
-  dependencies:
-    "@img/sharp-libvips-darwin-arm64": "npm:1.0.2"
-  dependenciesMeta:
-    "@img/sharp-libvips-darwin-arm64":
-      optional: true
-  conditions: os=darwin & cpu=arm64
-  languageName: node
-  linkType: hard
-
 "@img/sharp-darwin-arm64@npm:0.33.4":
   version: 0.33.4
   resolution: "@img/sharp-darwin-arm64@npm:0.33.4"
@@ -3414,18 +3393,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@img/sharp-darwin-x64@npm:0.33.3":
-  version: 0.33.3
-  resolution: "@img/sharp-darwin-x64@npm:0.33.3"
-  dependencies:
-    "@img/sharp-libvips-darwin-x64": "npm:1.0.2"
-  dependenciesMeta:
-    "@img/sharp-libvips-darwin-x64":
-      optional: true
-  conditions: os=darwin & cpu=x64
-  languageName: node
-  linkType: hard
-
 "@img/sharp-darwin-x64@npm:0.33.4":
   version: 0.33.4
   resolution: "@img/sharp-darwin-x64@npm:0.33.4"
@@ -3494,18 +3461,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@img/sharp-linux-arm64@npm:0.33.3":
-  version: 0.33.3
-  resolution: "@img/sharp-linux-arm64@npm:0.33.3"
-  dependencies:
-    "@img/sharp-libvips-linux-arm64": "npm:1.0.2"
-  dependenciesMeta:
-    "@img/sharp-libvips-linux-arm64":
-      optional: true
-  conditions: os=linux & cpu=arm64 & libc=glibc
-  languageName: node
-  linkType: hard
-
 "@img/sharp-linux-arm64@npm:0.33.4":
   version: 0.33.4
   resolution: "@img/sharp-linux-arm64@npm:0.33.4"
@@ -3518,18 +3473,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@img/sharp-linux-arm@npm:0.33.3":
-  version: 0.33.3
-  resolution: "@img/sharp-linux-arm@npm:0.33.3"
-  dependencies:
-    "@img/sharp-libvips-linux-arm": "npm:1.0.2"
-  dependenciesMeta:
-    "@img/sharp-libvips-linux-arm":
-      optional: true
-  conditions: os=linux & cpu=arm & libc=glibc
-  languageName: node
-  linkType: hard
-
 "@img/sharp-linux-arm@npm:0.33.4":
   version: 0.33.4
   resolution: "@img/sharp-linux-arm@npm:0.33.4"
@@ -3542,18 +3485,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@img/sharp-linux-s390x@npm:0.33.3":
-  version: 0.33.3
-  resolution: "@img/sharp-linux-s390x@npm:0.33.3"
-  dependencies:
-    "@img/sharp-libvips-linux-s390x": "npm:1.0.2"
-  dependenciesMeta:
-    "@img/sharp-libvips-linux-s390x":
-      optional: true
-  conditions: os=linux & cpu=s390x & libc=glibc
-  languageName: node
-  linkType: hard
-
 "@img/sharp-linux-s390x@npm:0.33.4":
   version: 0.33.4
   resolution: "@img/sharp-linux-s390x@npm:0.33.4"
@@ -3566,18 +3497,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@img/sharp-linux-x64@npm:0.33.3":
-  version: 0.33.3
-  resolution: "@img/sharp-linux-x64@npm:0.33.3"
-  dependencies:
-    "@img/sharp-libvips-linux-x64": "npm:1.0.2"
-  dependenciesMeta:
-    "@img/sharp-libvips-linux-x64":
-      optional: true
-  conditions: os=linux & cpu=x64 & libc=glibc
-  languageName: node
-  linkType: hard
-
 "@img/sharp-linux-x64@npm:0.33.4":
   version: 0.33.4
   resolution: "@img/sharp-linux-x64@npm:0.33.4"
@@ -3590,18 +3509,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@img/sharp-linuxmusl-arm64@npm:0.33.3":
-  version: 0.33.3
-  resolution: "@img/sharp-linuxmusl-arm64@npm:0.33.3"
-  dependencies:
-    "@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.2"
-  dependenciesMeta:
-    "@img/sharp-libvips-linuxmusl-arm64":
-      optional: true
-  conditions: os=linux & cpu=arm64 & libc=musl
-  languageName: node
-  linkType: hard
-
 "@img/sharp-linuxmusl-arm64@npm:0.33.4":
   version: 0.33.4
   resolution: "@img/sharp-linuxmusl-arm64@npm:0.33.4"
@@ -3614,18 +3521,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@img/sharp-linuxmusl-x64@npm:0.33.3":
-  version: 0.33.3
-  resolution: "@img/sharp-linuxmusl-x64@npm:0.33.3"
-  dependencies:
-    "@img/sharp-libvips-linuxmusl-x64": "npm:1.0.2"
-  dependenciesMeta:
-    "@img/sharp-libvips-linuxmusl-x64":
-      optional: true
-  conditions: os=linux & cpu=x64 & libc=musl
-  languageName: node
-  linkType: hard
-
 "@img/sharp-linuxmusl-x64@npm:0.33.4":
   version: 0.33.4
   resolution: "@img/sharp-linuxmusl-x64@npm:0.33.4"
@@ -3638,15 +3533,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@img/sharp-wasm32@npm:0.33.3":
-  version: 0.33.3
-  resolution: "@img/sharp-wasm32@npm:0.33.3"
-  dependencies:
-    "@emnapi/runtime": "npm:^1.1.0"
-  conditions: cpu=wasm32
-  languageName: node
-  linkType: hard
-
 "@img/sharp-wasm32@npm:0.33.4":
   version: 0.33.4
   resolution: "@img/sharp-wasm32@npm:0.33.4"
@@ -3656,13 +3542,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@img/sharp-win32-ia32@npm:0.33.3":
-  version: 0.33.3
-  resolution: "@img/sharp-win32-ia32@npm:0.33.3"
-  conditions: os=win32 & cpu=ia32
-  languageName: node
-  linkType: hard
-
 "@img/sharp-win32-ia32@npm:0.33.4":
   version: 0.33.4
   resolution: "@img/sharp-win32-ia32@npm:0.33.4"
@@ -3670,13 +3549,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@img/sharp-win32-x64@npm:0.33.3":
-  version: 0.33.3
-  resolution: "@img/sharp-win32-x64@npm:0.33.3"
-  conditions: os=win32 & cpu=x64
-  languageName: node
-  linkType: hard
-
 "@img/sharp-win32-x64@npm:0.33.4":
   version: 0.33.4
   resolution: "@img/sharp-win32-x64@npm:0.33.4"
@@ -3939,13 +3811,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@next/env@npm:14.1.0":
-  version: 14.1.0
-  resolution: "@next/env@npm:14.1.0"
-  checksum: 10c0/f45ce1e3dad87cdbddc58b06bd411f44a6d21dfc2c344d02a5e1b07f56fbc9a39e192c0b0917df9f2e9e4e2156306a8c78f173ca4b53932c2793e67797462a23
-  languageName: node
-  linkType: hard
-
 "@next/env@npm:14.2.5, @next/env@npm:^14.2.5":
   version: 14.2.5
   resolution: "@next/env@npm:14.2.5"
@@ -3953,13 +3818,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@next/swc-darwin-arm64@npm:14.1.0":
-  version: 14.1.0
-  resolution: "@next/swc-darwin-arm64@npm:14.1.0"
-  conditions: os=darwin & cpu=arm64
-  languageName: node
-  linkType: hard
-
 "@next/swc-darwin-arm64@npm:14.2.5":
   version: 14.2.5
   resolution: "@next/swc-darwin-arm64@npm:14.2.5"
@@ -3967,13 +3825,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@next/swc-darwin-x64@npm:14.1.0":
-  version: 14.1.0
-  resolution: "@next/swc-darwin-x64@npm:14.1.0"
-  conditions: os=darwin & cpu=x64
-  languageName: node
-  linkType: hard
-
 "@next/swc-darwin-x64@npm:14.2.5":
   version: 14.2.5
   resolution: "@next/swc-darwin-x64@npm:14.2.5"
@@ -3981,13 +3832,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@next/swc-linux-arm64-gnu@npm:14.1.0":
-  version: 14.1.0
-  resolution: "@next/swc-linux-arm64-gnu@npm:14.1.0"
-  conditions: os=linux & cpu=arm64 & libc=glibc
-  languageName: node
-  linkType: hard
-
 "@next/swc-linux-arm64-gnu@npm:14.2.5":
   version: 14.2.5
   resolution: "@next/swc-linux-arm64-gnu@npm:14.2.5"
@@ -3995,13 +3839,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@next/swc-linux-arm64-musl@npm:14.1.0":
-  version: 14.1.0
-  resolution: "@next/swc-linux-arm64-musl@npm:14.1.0"
-  conditions: os=linux & cpu=arm64 & libc=musl
-  languageName: node
-  linkType: hard
-
 "@next/swc-linux-arm64-musl@npm:14.2.5":
   version: 14.2.5
   resolution: "@next/swc-linux-arm64-musl@npm:14.2.5"
@@ -4009,13 +3846,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@next/swc-linux-x64-gnu@npm:14.1.0":
-  version: 14.1.0
-  resolution: "@next/swc-linux-x64-gnu@npm:14.1.0"
-  conditions: os=linux & cpu=x64 & libc=glibc
-  languageName: node
-  linkType: hard
-
 "@next/swc-linux-x64-gnu@npm:14.2.5":
   version: 14.2.5
   resolution: "@next/swc-linux-x64-gnu@npm:14.2.5"
@@ -4023,13 +3853,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@next/swc-linux-x64-musl@npm:14.1.0":
-  version: 14.1.0
-  resolution: "@next/swc-linux-x64-musl@npm:14.1.0"
-  conditions: os=linux & cpu=x64 & libc=musl
-  languageName: node
-  linkType: hard
-
 "@next/swc-linux-x64-musl@npm:14.2.5":
   version: 14.2.5
   resolution: "@next/swc-linux-x64-musl@npm:14.2.5"
@@ -4037,13 +3860,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@next/swc-win32-arm64-msvc@npm:14.1.0":
-  version: 14.1.0
-  resolution: "@next/swc-win32-arm64-msvc@npm:14.1.0"
-  conditions: os=win32 & cpu=arm64
-  languageName: node
-  linkType: hard
-
 "@next/swc-win32-arm64-msvc@npm:14.2.5":
   version: 14.2.5
   resolution: "@next/swc-win32-arm64-msvc@npm:14.2.5"
@@ -4051,13 +3867,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@next/swc-win32-ia32-msvc@npm:14.1.0":
-  version: 14.1.0
-  resolution: "@next/swc-win32-ia32-msvc@npm:14.1.0"
-  conditions: os=win32 & cpu=ia32
-  languageName: node
-  linkType: hard
-
 "@next/swc-win32-ia32-msvc@npm:14.2.5":
   version: 14.2.5
   resolution: "@next/swc-win32-ia32-msvc@npm:14.2.5"
@@ -4065,13 +3874,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@next/swc-win32-x64-msvc@npm:14.1.0":
-  version: 14.1.0
-  resolution: "@next/swc-win32-x64-msvc@npm:14.1.0"
-  conditions: os=win32 & cpu=x64
-  languageName: node
-  linkType: hard
-
 "@next/swc-win32-x64-msvc@npm:14.2.5":
   version: 14.2.5
   resolution: "@next/swc-win32-x64-msvc@npm:14.2.5"
@@ -7238,15 +7040,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@swc/helpers@npm:0.5.2":
-  version: 0.5.2
-  resolution: "@swc/helpers@npm:0.5.2"
-  dependencies:
-    tslib: "npm:^2.4.0"
-  checksum: 10c0/b6fa49bcf6c00571d0eb7837b163f8609960d4d77538160585e27ed167361e9776bd6e5eb9646ffac2fb4d43c58df9ca50dab9d96ab097e6591bc82a75fd1164
-  languageName: node
-  linkType: hard
-
 "@swc/helpers@npm:0.5.5":
   version: 0.5.5
   resolution: "@swc/helpers@npm:0.5.5"
@@ -17020,18 +16813,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"image-size@npm:^1.0.0":
-  version: 1.0.2
-  resolution: "image-size@npm:1.0.2"
-  dependencies:
-    queue: "npm:6.0.2"
-  bin:
-    image-size: bin/image-size.js
-  checksum: 10c0/df518606c75d0ee12a6d7e822a64ef50d9eabbb303dcee8c9df06bad94e49b4d4680b9003968203f239ff39a9cc51d4ff1781cd331cc0a4b3b858d9fc9836c68
-  languageName: node
-  linkType: hard
-
-"image-size@npm:^1.1.1":
+"image-size@npm:^1.0.0, image-size@npm:^1.1.1":
   version: 1.1.1
   resolution: "image-size@npm:1.1.1"
   dependencies:
@@ -20907,62 +20689,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"next@npm:^14.1.0":
-  version: 14.1.0
-  resolution: "next@npm:14.1.0"
-  dependencies:
-    "@next/env": "npm:14.1.0"
-    "@next/swc-darwin-arm64": "npm:14.1.0"
-    "@next/swc-darwin-x64": "npm:14.1.0"
-    "@next/swc-linux-arm64-gnu": "npm:14.1.0"
-    "@next/swc-linux-arm64-musl": "npm:14.1.0"
-    "@next/swc-linux-x64-gnu": "npm:14.1.0"
-    "@next/swc-linux-x64-musl": "npm:14.1.0"
-    "@next/swc-win32-arm64-msvc": "npm:14.1.0"
-    "@next/swc-win32-ia32-msvc": "npm:14.1.0"
-    "@next/swc-win32-x64-msvc": "npm:14.1.0"
-    "@swc/helpers": "npm:0.5.2"
-    busboy: "npm:1.6.0"
-    caniuse-lite: "npm:^1.0.30001579"
-    graceful-fs: "npm:^4.2.11"
-    postcss: "npm:8.4.31"
-    styled-jsx: "npm:5.1.1"
-  peerDependencies:
-    "@opentelemetry/api": ^1.1.0
-    react: ^18.2.0
-    react-dom: ^18.2.0
-    sass: ^1.3.0
-  dependenciesMeta:
-    "@next/swc-darwin-arm64":
-      optional: true
-    "@next/swc-darwin-x64":
-      optional: true
-    "@next/swc-linux-arm64-gnu":
-      optional: true
-    "@next/swc-linux-arm64-musl":
-      optional: true
-    "@next/swc-linux-x64-gnu":
-      optional: true
-    "@next/swc-linux-x64-musl":
-      optional: true
-    "@next/swc-win32-arm64-msvc":
-      optional: true
-    "@next/swc-win32-ia32-msvc":
-      optional: true
-    "@next/swc-win32-x64-msvc":
-      optional: true
-  peerDependenciesMeta:
-    "@opentelemetry/api":
-      optional: true
-    sass:
-      optional: true
-  bin:
-    next: dist/bin/next
-  checksum: 10c0/dbb1ef8d22eec29a9127d28ed46eb34f14e3f7f7b4e4b91dc96027feb4d9ead554a804275484d9a54026e6e55d632d3997561e598c1fb8e8956e77614f39765f
-  languageName: node
-  linkType: hard
-
-"next@npm:^14.2.5":
+"next@npm:^14.1.0, next@npm:^14.2.5":
   version: 14.2.5
   resolution: "next@npm:14.2.5"
   dependencies:
@@ -25191,76 +24918,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"sharp@npm:^0.33.3":
-  version: 0.33.3
-  resolution: "sharp@npm:0.33.3"
-  dependencies:
-    "@img/sharp-darwin-arm64": "npm:0.33.3"
-    "@img/sharp-darwin-x64": "npm:0.33.3"
-    "@img/sharp-libvips-darwin-arm64": "npm:1.0.2"
-    "@img/sharp-libvips-darwin-x64": "npm:1.0.2"
-    "@img/sharp-libvips-linux-arm": "npm:1.0.2"
-    "@img/sharp-libvips-linux-arm64": "npm:1.0.2"
-    "@img/sharp-libvips-linux-s390x": "npm:1.0.2"
-    "@img/sharp-libvips-linux-x64": "npm:1.0.2"
-    "@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.2"
-    "@img/sharp-libvips-linuxmusl-x64": "npm:1.0.2"
-    "@img/sharp-linux-arm": "npm:0.33.3"
-    "@img/sharp-linux-arm64": "npm:0.33.3"
-    "@img/sharp-linux-s390x": "npm:0.33.3"
-    "@img/sharp-linux-x64": "npm:0.33.3"
-    "@img/sharp-linuxmusl-arm64": "npm:0.33.3"
-    "@img/sharp-linuxmusl-x64": "npm:0.33.3"
-    "@img/sharp-wasm32": "npm:0.33.3"
-    "@img/sharp-win32-ia32": "npm:0.33.3"
-    "@img/sharp-win32-x64": "npm:0.33.3"
-    color: "npm:^4.2.3"
-    detect-libc: "npm:^2.0.3"
-    semver: "npm:^7.6.0"
-  dependenciesMeta:
-    "@img/sharp-darwin-arm64":
-      optional: true
-    "@img/sharp-darwin-x64":
-      optional: true
-    "@img/sharp-libvips-darwin-arm64":
-      optional: true
-    "@img/sharp-libvips-darwin-x64":
-      optional: true
-    "@img/sharp-libvips-linux-arm":
-      optional: true
-    "@img/sharp-libvips-linux-arm64":
-      optional: true
-    "@img/sharp-libvips-linux-s390x":
-      optional: true
-    "@img/sharp-libvips-linux-x64":
-      optional: true
-    "@img/sharp-libvips-linuxmusl-arm64":
-      optional: true
-    "@img/sharp-libvips-linuxmusl-x64":
-      optional: true
-    "@img/sharp-linux-arm":
-      optional: true
-    "@img/sharp-linux-arm64":
-      optional: true
-    "@img/sharp-linux-s390x":
-      optional: true
-    "@img/sharp-linux-x64":
-      optional: true
-    "@img/sharp-linuxmusl-arm64":
-      optional: true
-    "@img/sharp-linuxmusl-x64":
-      optional: true
-    "@img/sharp-wasm32":
-      optional: true
-    "@img/sharp-win32-ia32":
-      optional: true
-    "@img/sharp-win32-x64":
-      optional: true
-  checksum: 10c0/12f5203426595b4e64c807162a6d52358b591d25fbb414a51fe38861584759fba38485be951ed98d15be3dfe21f2def5336f78ca35bf8bbd22d88cc78ca03f2a
-  languageName: node
-  linkType: hard
-
-"sharp@npm:^0.33.4":
+"sharp@npm:^0.33.3, sharp@npm:^0.33.4":
   version: 0.33.4
   resolution: "sharp@npm:0.33.4"
   dependencies:
diff --git a/scripts/utils/options.ts b/scripts/utils/options.ts
index 6c6a6e4b2dd1..2c6f63ff00d6 100644
--- a/scripts/utils/options.ts
+++ b/scripts/utils/options.ts
@@ -137,7 +137,7 @@ export function getOptions<TOptions extends OptionSpecifier>(
 ): MaybeOptionValues<TOptions> {
   Object.entries(options)
     .reduce((acc, [key, option]) => {
-      const flags = optionFlags(key, option, acc.options);
+      const flags = optionFlags(key, option, acc.options as any);
 
       if (option.type === 'boolean') {
         return acc.option(flags, option.description, !!option.inverse);

From 296dfd2622a5366c60ce85bda2b0215f18e3380e Mon Sep 17 00:00:00 2001
From: Norbert de Langen <ndelangen@me.com>
Date: Wed, 14 Aug 2024 11:07:48 +0200
Subject: [PATCH 10/38] fix some nextjs failure, maybe due to some dep dedupe

---
 code/frameworks/nextjs/src/routing/app-router-provider.tsx | 1 +
 1 file changed, 1 insertion(+)

diff --git a/code/frameworks/nextjs/src/routing/app-router-provider.tsx b/code/frameworks/nextjs/src/routing/app-router-provider.tsx
index 5685ec8bbf85..9f37f5768e15 100644
--- a/code/frameworks/nextjs/src/routing/app-router-provider.tsx
+++ b/code/frameworks/nextjs/src/routing/app-router-provider.tsx
@@ -101,6 +101,7 @@ export const AppRouterProvider: React.FC<React.PropsWithChildren<AppRouterProvid
                   childNodes: new Map(),
                   tree,
                   url: pathname,
+                  loading: null,
                 }}
               >
                 {children}

From 6c4a442b5c1484e50412934bcf4e52c34e5125a6 Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Fri, 16 Aug 2024 10:50:54 +0200
Subject: [PATCH 11/38] set default viewport if applicable

---
 .../vitest/src/plugin/viewports.test.ts       | 111 ++++++++++++++++++
 code/addons/vitest/src/plugin/viewports.ts    |  29 +++--
 2 files changed, 129 insertions(+), 11 deletions(-)
 create mode 100644 code/addons/vitest/src/plugin/viewports.test.ts

diff --git a/code/addons/vitest/src/plugin/viewports.test.ts b/code/addons/vitest/src/plugin/viewports.test.ts
new file mode 100644
index 000000000000..114de8cee33b
--- /dev/null
+++ b/code/addons/vitest/src/plugin/viewports.test.ts
@@ -0,0 +1,111 @@
+/* eslint-disable no-underscore-dangle */
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+
+import { page } from '@vitest/browser/context';
+
+import { DEFAULT_VIEWPORT_DIMENSIONS, type ViewportsParam, setViewport } from './viewports';
+import { INITIAL_VIEWPORTS } from '../../../viewport/src/defaults';
+
+vi.mock('@vitest/browser/context', () => ({
+  page: {
+    viewport: vi.fn(),
+  },
+}));
+
+describe('setViewport', () => {
+  beforeEach(() => {
+    vi.clearAllMocks();
+    globalThis.__vitest_browser__ = true;
+  });
+
+  afterEach(() => {
+    globalThis.__vitest_browser__ = false;
+  });
+
+  it('should do nothing if __vitest_browser__ is false', async () => {
+    globalThis.__vitest_browser__ = false;
+
+    const result = await setViewport();
+    expect(result).toBeNull();
+    expect(page.viewport).not.toHaveBeenCalled();
+  });
+
+  it('should set the viewport to the specified dimensions from INITIAL_VIEWPORTS', async () => {
+    const viewportsParam: any = {
+      // supported by default in addon viewports
+      defaultViewport: 'ipad',
+    };
+
+    await setViewport(viewportsParam);
+    expect(page.viewport).toHaveBeenCalledWith(768, 1024);
+  });
+
+  it('should set the viewport to the specified dimensions if defaultViewport is valid', async () => {
+    const viewportsParam: ViewportsParam = {
+      defaultViewport: 'small',
+      viewports: {
+        small: {
+          name: 'Small screen',
+          type: 'mobile',
+          styles: {
+            width: '375px',
+            height: '667px',
+          },
+        },
+      },
+    };
+
+    await setViewport(viewportsParam);
+    expect(page.viewport).toHaveBeenCalledWith(375, 667);
+  });
+
+  it('should set the viewport to DEFAULT_VIEWPORT_DIMENSIONS if defaultViewport has unparseable styles', async () => {
+    const viewportsParam: ViewportsParam = {
+      defaultViewport: 'oddSizes',
+      viewports: {
+        oddSizes: {
+          name: 'foo',
+          type: 'other',
+          styles: {
+            width: 'calc(100vw - 20px)',
+            height: '100%',
+          },
+        },
+      },
+    };
+
+    await setViewport(viewportsParam);
+    expect(page.viewport).toHaveBeenCalledWith(
+      DEFAULT_VIEWPORT_DIMENSIONS.width,
+      DEFAULT_VIEWPORT_DIMENSIONS.height
+    );
+  });
+
+  it('should merge provided viewports with initial viewports', async () => {
+    const viewportsParam: ViewportsParam = {
+      defaultViewport: 'customViewport',
+      viewports: {
+        customViewport: {
+          name: 'Custom Viewport',
+          type: 'mobile',
+          styles: {
+            width: '800px',
+            height: '600px',
+          },
+        },
+      },
+    };
+
+    await setViewport(viewportsParam);
+    expect(page.viewport).toHaveBeenCalledWith(800, 600);
+  });
+
+  it('should fallback to DEFAULT_VIEWPORT_DIMENSIONS if defaultViewport does not exist', async () => {
+    const viewportsParam: any = {
+      defaultViewport: 'nonExistentViewport',
+    };
+
+    await setViewport(viewportsParam);
+    expect(page.viewport).toHaveBeenCalledWith(1200, 900);
+  });
+});
diff --git a/code/addons/vitest/src/plugin/viewports.ts b/code/addons/vitest/src/plugin/viewports.ts
index ec9fa8706f5f..5dad238a9bb6 100644
--- a/code/addons/vitest/src/plugin/viewports.ts
+++ b/code/addons/vitest/src/plugin/viewports.ts
@@ -9,33 +9,40 @@ declare global {
   var __vitest_browser__: boolean;
 }
 
-interface ViewportsParam {
+export interface ViewportsParam {
   defaultViewport: string;
   viewports: ViewportMap;
 }
 
+export const DEFAULT_VIEWPORT_DIMENSIONS = {
+  width: 1200,
+  height: 900,
+};
+
 export const setViewport = async (viewportsParam: ViewportsParam = {} as ViewportsParam) => {
   const defaultViewport = viewportsParam.defaultViewport;
-
-  if (!page || !globalThis.__vitest_browser__ || !defaultViewport) {
-    return null;
-  }
+  if (!page || !globalThis.__vitest_browser__ || !defaultViewport) return null;
 
   const viewports = {
     ...INITIAL_VIEWPORTS,
     ...viewportsParam.viewports,
   };
 
+  let viewportWidth = DEFAULT_VIEWPORT_DIMENSIONS.width;
+  let viewportHeight = DEFAULT_VIEWPORT_DIMENSIONS.height;
+
   if (defaultViewport in viewports) {
     const styles = viewports[defaultViewport].styles as ViewportStyles;
     if (styles?.width && styles?.height) {
-      const { width, height } = {
-        width: Number.parseInt(styles.width, 10),
-        height: Number.parseInt(styles.height, 10),
-      };
-      await page.viewport(width, height);
+      const validPixelOrNumber = /^\d+(px)?$/;
+
+      // if both dimensions are not valid numbers e.g. 'calc(100vh - 10px)' or '100%', use the default dimensions instead
+      if (validPixelOrNumber.test(styles.width) && validPixelOrNumber.test(styles.height)) {
+        viewportWidth = Number.parseInt(styles.width, 10);
+        viewportHeight = Number.parseInt(styles.height, 10);
+      }
     }
   }
 
-  return null;
+  return page.viewport(viewportWidth, viewportHeight);
 };

From b4fc809a39f72f76a98f6a45824a9f0e72352cac Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Fri, 16 Aug 2024 11:48:06 +0200
Subject: [PATCH 12/38] fix lint issues

---
 code/addons/vitest/src/plugin/viewports.test.ts | 1 -
 code/addons/vitest/src/plugin/viewports.ts      | 5 ++++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/code/addons/vitest/src/plugin/viewports.test.ts b/code/addons/vitest/src/plugin/viewports.test.ts
index 114de8cee33b..ef65a287fe42 100644
--- a/code/addons/vitest/src/plugin/viewports.test.ts
+++ b/code/addons/vitest/src/plugin/viewports.test.ts
@@ -4,7 +4,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
 import { page } from '@vitest/browser/context';
 
 import { DEFAULT_VIEWPORT_DIMENSIONS, type ViewportsParam, setViewport } from './viewports';
-import { INITIAL_VIEWPORTS } from '../../../viewport/src/defaults';
 
 vi.mock('@vitest/browser/context', () => ({
   page: {
diff --git a/code/addons/vitest/src/plugin/viewports.ts b/code/addons/vitest/src/plugin/viewports.ts
index 5dad238a9bb6..ced337bb23eb 100644
--- a/code/addons/vitest/src/plugin/viewports.ts
+++ b/code/addons/vitest/src/plugin/viewports.ts
@@ -21,7 +21,10 @@ export const DEFAULT_VIEWPORT_DIMENSIONS = {
 
 export const setViewport = async (viewportsParam: ViewportsParam = {} as ViewportsParam) => {
   const defaultViewport = viewportsParam.defaultViewport;
-  if (!page || !globalThis.__vitest_browser__ || !defaultViewport) return null;
+
+  if (!page || !globalThis.__vitest_browser__ || !defaultViewport) {
+    return null;
+  }
 
   const viewports = {
     ...INITIAL_VIEWPORTS,

From 25d2a8e1802677c2d3b09d511d815ce3b1e5beec Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Fri, 16 Aug 2024 12:17:14 +0200
Subject: [PATCH 13/38] refactor return type

---
 code/addons/vitest/src/plugin/viewports.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/code/addons/vitest/src/plugin/viewports.ts b/code/addons/vitest/src/plugin/viewports.ts
index ced337bb23eb..747a90b57bc8 100644
--- a/code/addons/vitest/src/plugin/viewports.ts
+++ b/code/addons/vitest/src/plugin/viewports.ts
@@ -23,7 +23,7 @@ export const setViewport = async (viewportsParam: ViewportsParam = {} as Viewpor
   const defaultViewport = viewportsParam.defaultViewport;
 
   if (!page || !globalThis.__vitest_browser__ || !defaultViewport) {
-    return null;
+    return;
   }
 
   const viewports = {
@@ -47,5 +47,5 @@ export const setViewport = async (viewportsParam: ViewportsParam = {} as Viewpor
     }
   }
 
-  return page.viewport(viewportWidth, viewportHeight);
+  await page.viewport(viewportWidth, viewportHeight);
 };

From 0b6f53d9afc3235560239a733c69bef8abbfd760 Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Fri, 16 Aug 2024 12:45:04 +0200
Subject: [PATCH 14/38] Web Components: Introduce setProjectAnnotations API

---
 code/renderers/web-components/src/index.ts    |  1 +
 .../web-components/src/portable-stories.ts    | 41 +++++++++++++++++++
 2 files changed, 42 insertions(+)
 create mode 100644 code/renderers/web-components/src/portable-stories.ts

diff --git a/code/renderers/web-components/src/index.ts b/code/renderers/web-components/src/index.ts
index 58076b752f2b..0399ac731032 100644
--- a/code/renderers/web-components/src/index.ts
+++ b/code/renderers/web-components/src/index.ts
@@ -7,6 +7,7 @@ const { window, EventSource } = global;
 
 export * from './public-types';
 export * from './framework-api';
+export * from './portable-stories';
 
 // TODO: disable HMR and do full page loads because of customElements.define
 if (typeof module !== 'undefined' && module?.hot?.decline) {
diff --git a/code/renderers/web-components/src/portable-stories.ts b/code/renderers/web-components/src/portable-stories.ts
new file mode 100644
index 000000000000..c5eb2c9883cd
--- /dev/null
+++ b/code/renderers/web-components/src/portable-stories.ts
@@ -0,0 +1,41 @@
+import {
+  setProjectAnnotations as originalSetProjectAnnotations,
+  setDefaultProjectAnnotations,
+} from 'storybook/internal/preview-api';
+import type {
+  NamedOrDefaultProjectAnnotations,
+  NormalizedProjectAnnotations,
+} from 'storybook/internal/types';
+
+import * as webComponentsAnnotations from './entry-preview';
+import type { WebComponentsRenderer } from './types';
+
+/**
+ * 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`.
+ *
+ * Example:
+ *
+ * ```jsx
+ * // setup-file.js
+ * import { setProjectAnnotations } from '@storybook/web-components';
+ * import projectAnnotations from './.storybook/preview';
+ *
+ * setProjectAnnotations(projectAnnotations);
+ * ```
+ *
+ * @param projectAnnotations - E.g. (import projectAnnotations from '../.storybook/preview')
+ */
+export function setProjectAnnotations(
+  projectAnnotations:
+    | NamedOrDefaultProjectAnnotations<any>
+    | NamedOrDefaultProjectAnnotations<any>[]
+): NormalizedProjectAnnotations<WebComponentsRenderer> {
+  setDefaultProjectAnnotations(webComponentsAnnotations);
+  return originalSetProjectAnnotations(
+    projectAnnotations
+  ) as NormalizedProjectAnnotations<WebComponentsRenderer>;
+}

From 18df2162707fbd7145d2850851140ab999fde83c Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Fri, 16 Aug 2024 12:45:31 +0200
Subject: [PATCH 15/38] Angular: Introduce setProjectAnnotations API

---
 code/frameworks/angular/src/client/index.ts   |  1 +
 .../angular/src/client/portable-stories.ts    | 42 +++++++++++++++++++
 2 files changed, 43 insertions(+)
 create mode 100644 code/frameworks/angular/src/client/portable-stories.ts

diff --git a/code/frameworks/angular/src/client/index.ts b/code/frameworks/angular/src/client/index.ts
index 45388bcad324..221d365718d5 100644
--- a/code/frameworks/angular/src/client/index.ts
+++ b/code/frameworks/angular/src/client/index.ts
@@ -2,6 +2,7 @@
 import './globals';
 
 export * from './public-types';
+export * from './portable-stories';
 
 export type { StoryFnAngularReturnType as IStory } from './types';
 
diff --git a/code/frameworks/angular/src/client/portable-stories.ts b/code/frameworks/angular/src/client/portable-stories.ts
new file mode 100644
index 000000000000..4f4246ddf3e4
--- /dev/null
+++ b/code/frameworks/angular/src/client/portable-stories.ts
@@ -0,0 +1,42 @@
+import {
+  setProjectAnnotations as originalSetProjectAnnotations,
+  setDefaultProjectAnnotations,
+} from 'storybook/internal/preview-api';
+import {
+  NamedOrDefaultProjectAnnotations,
+  NormalizedProjectAnnotations,
+} from 'storybook/internal/types';
+
+import * as INTERNAL_DEFAULT_PROJECT_ANNOTATIONS from './render';
+import { AngularRenderer } from './types';
+
+/**
+ * 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`.
+ *
+ * Example:
+ *
+ * ```jsx
+ * // setup-file.js
+ * import { setProjectAnnotations } from '@storybook/angular';
+ *
+ * import projectAnnotations from './.storybook/preview';
+ *
+ * setProjectAnnotations(projectAnnotations);
+ * ```
+ *
+ * @param projectAnnotations - E.g. (import projectAnnotations from '../.storybook/preview')
+ */
+export function setProjectAnnotations(
+  projectAnnotations:
+    | NamedOrDefaultProjectAnnotations<any>
+    | NamedOrDefaultProjectAnnotations<any>[]
+): NormalizedProjectAnnotations<AngularRenderer> {
+  setDefaultProjectAnnotations(INTERNAL_DEFAULT_PROJECT_ANNOTATIONS);
+  return originalSetProjectAnnotations(
+    projectAnnotations
+  ) as NormalizedProjectAnnotations<AngularRenderer>;
+}

From 0809eb5e2a4df94b79c0348ad2915d3e918fb410 Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Fri, 16 Aug 2024 12:45:59 +0200
Subject: [PATCH 16/38] HTML: Introduce setProjectAnnotations API

---
 code/renderers/html/src/index.ts            |  1 +
 code/renderers/html/src/portable-stories.ts | 41 +++++++++++++++++++++
 2 files changed, 42 insertions(+)
 create mode 100644 code/renderers/html/src/portable-stories.ts

diff --git a/code/renderers/html/src/index.ts b/code/renderers/html/src/index.ts
index 13fea98639ff..80c844382e83 100644
--- a/code/renderers/html/src/index.ts
+++ b/code/renderers/html/src/index.ts
@@ -2,6 +2,7 @@
 import './globals';
 
 export * from './public-types';
+export * from './portable-stories';
 
 // optimization: stop HMR propagation in webpack
 
diff --git a/code/renderers/html/src/portable-stories.ts b/code/renderers/html/src/portable-stories.ts
new file mode 100644
index 000000000000..f19a5e0d6561
--- /dev/null
+++ b/code/renderers/html/src/portable-stories.ts
@@ -0,0 +1,41 @@
+import {
+  setProjectAnnotations as originalSetProjectAnnotations,
+  setDefaultProjectAnnotations,
+} from 'storybook/internal/preview-api';
+import type {
+  NamedOrDefaultProjectAnnotations,
+  NormalizedProjectAnnotations,
+} from 'storybook/internal/types';
+
+import * as INTERNAL_DEFAULT_PROJECT_ANNOTATIONS from './entry-preview';
+import type { HtmlRenderer } from './types';
+
+/**
+ * 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`.
+ *
+ * Example:
+ *
+ * ```jsx
+ * // setup-file.js
+ * import { setProjectAnnotations } from '@storybook/preact';
+ * import projectAnnotations from './.storybook/preview';
+ *
+ * setProjectAnnotations(projectAnnotations);
+ * ```
+ *
+ * @param projectAnnotations - E.g. (import projectAnnotations from '../.storybook/preview')
+ */
+export function setProjectAnnotations(
+  projectAnnotations:
+    | NamedOrDefaultProjectAnnotations<any>
+    | NamedOrDefaultProjectAnnotations<any>[]
+): NormalizedProjectAnnotations<HtmlRenderer> {
+  setDefaultProjectAnnotations(INTERNAL_DEFAULT_PROJECT_ANNOTATIONS);
+  return originalSetProjectAnnotations(
+    projectAnnotations
+  ) as NormalizedProjectAnnotations<HtmlRenderer>;
+}

From 3229c13c993d739d3e58cb334c67bfbc16e6da0d Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Fri, 16 Aug 2024 12:46:43 +0200
Subject: [PATCH 17/38] Preact: Introduce setProjectAnnotations API

---
 code/renderers/preact/src/index.ts            |  1 +
 code/renderers/preact/src/portable-stories.ts | 41 +++++++++++++++++++
 2 files changed, 42 insertions(+)
 create mode 100644 code/renderers/preact/src/portable-stories.ts

diff --git a/code/renderers/preact/src/index.ts b/code/renderers/preact/src/index.ts
index 13fea98639ff..80c844382e83 100644
--- a/code/renderers/preact/src/index.ts
+++ b/code/renderers/preact/src/index.ts
@@ -2,6 +2,7 @@
 import './globals';
 
 export * from './public-types';
+export * from './portable-stories';
 
 // optimization: stop HMR propagation in webpack
 
diff --git a/code/renderers/preact/src/portable-stories.ts b/code/renderers/preact/src/portable-stories.ts
new file mode 100644
index 000000000000..cc9ca05a2627
--- /dev/null
+++ b/code/renderers/preact/src/portable-stories.ts
@@ -0,0 +1,41 @@
+import {
+  setProjectAnnotations as originalSetProjectAnnotations,
+  setDefaultProjectAnnotations,
+} from 'storybook/internal/preview-api';
+import type {
+  NamedOrDefaultProjectAnnotations,
+  NormalizedProjectAnnotations,
+} from 'storybook/internal/types';
+
+import * as INTERNAL_DEFAULT_PROJECT_ANNOTATIONS from './entry-preview';
+import type { PreactRenderer } from './types';
+
+/**
+ * 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`.
+ *
+ * Example:
+ *
+ * ```jsx
+ * // setup-file.js
+ * import { setProjectAnnotations } from '@storybook/preact';
+ * import projectAnnotations from './.storybook/preview';
+ *
+ * setProjectAnnotations(projectAnnotations);
+ * ```
+ *
+ * @param projectAnnotations - E.g. (import projectAnnotations from '../.storybook/preview')
+ */
+export function setProjectAnnotations(
+  projectAnnotations:
+    | NamedOrDefaultProjectAnnotations<any>
+    | NamedOrDefaultProjectAnnotations<any>[]
+): NormalizedProjectAnnotations<PreactRenderer> {
+  setDefaultProjectAnnotations(INTERNAL_DEFAULT_PROJECT_ANNOTATIONS);
+  return originalSetProjectAnnotations(
+    projectAnnotations
+  ) as NormalizedProjectAnnotations<PreactRenderer>;
+}

From a38643e9b69ce49493c58f84b5bc96feb02db0ec Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Fri, 16 Aug 2024 12:47:11 +0200
Subject: [PATCH 18/38] SvelteKit: Introduce setProjectAnnotations API

---
 code/frameworks/sveltekit/src/index.ts        |  1 +
 .../sveltekit/src/portable-stories.ts         | 52 +++++++++++++++++++
 2 files changed, 53 insertions(+)
 create mode 100644 code/frameworks/sveltekit/src/portable-stories.ts

diff --git a/code/frameworks/sveltekit/src/index.ts b/code/frameworks/sveltekit/src/index.ts
index fcb073fefcd6..a904f93ec89d 100644
--- a/code/frameworks/sveltekit/src/index.ts
+++ b/code/frameworks/sveltekit/src/index.ts
@@ -1 +1,2 @@
 export * from './types';
+export * from './portable-stories';
diff --git a/code/frameworks/sveltekit/src/portable-stories.ts b/code/frameworks/sveltekit/src/portable-stories.ts
new file mode 100644
index 000000000000..d17061f562f8
--- /dev/null
+++ b/code/frameworks/sveltekit/src/portable-stories.ts
@@ -0,0 +1,52 @@
+import {
+  composeConfigs,
+  setProjectAnnotations as originalSetProjectAnnotations,
+  setDefaultProjectAnnotations,
+} from 'storybook/internal/preview-api';
+import type {
+  NamedOrDefaultProjectAnnotations,
+  NormalizedProjectAnnotations,
+  ProjectAnnotations,
+} from 'storybook/internal/types';
+
+import type { SvelteRenderer } from '@storybook/svelte';
+
+// TODO: investigate whether we need to prebundle this or can just import from the renderer package
+import { INTERNAL_DEFAULT_PROJECT_ANNOTATIONS as svelteAnnotations } from '../../../renderers/svelte/src/portable-stories';
+import * as svelteKitAnnotations from './preview';
+
+/**
+ * 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`.
+ *
+ * Example:
+ *
+ * ```jsx
+ * // setup-file.js
+ * import { setProjectAnnotations } from '@storybook/sveltekit';
+ * import projectAnnotations from './.storybook/preview';
+ *
+ * setProjectAnnotations(projectAnnotations);
+ * ```
+ *
+ * @param projectAnnotations - E.g. (import projectAnnotations from '../.storybook/preview')
+ */
+export function setProjectAnnotations(
+  projectAnnotations:
+    | NamedOrDefaultProjectAnnotations<any>
+    | NamedOrDefaultProjectAnnotations<any>[]
+): NormalizedProjectAnnotations<SvelteRenderer> {
+  setDefaultProjectAnnotations(INTERNAL_DEFAULT_PROJECT_ANNOTATIONS);
+  return originalSetProjectAnnotations(
+    projectAnnotations
+  ) as NormalizedProjectAnnotations<SvelteRenderer>;
+}
+
+// This will not be necessary once we have auto preset loading
+const INTERNAL_DEFAULT_PROJECT_ANNOTATIONS: ProjectAnnotations<SvelteRenderer> = composeConfigs([
+  svelteAnnotations,
+  svelteKitAnnotations,
+]);

From 1738902fbfa2890c0d8fc5c3e9cdfdaaa1670756 Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Fri, 16 Aug 2024 12:47:36 +0200
Subject: [PATCH 19/38] minor change in jsdoc

---
 .../frameworks/experimental-nextjs-vite/src/portable-stories.ts | 2 +-
 code/frameworks/nextjs/src/portable-stories.ts                  | 2 +-
 code/renderers/react/src/portable-stories.tsx                   | 2 +-
 code/renderers/svelte/src/portable-stories.ts                   | 2 +-
 code/renderers/vue3/src/portable-stories.ts                     | 2 +-
 5 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/code/frameworks/experimental-nextjs-vite/src/portable-stories.ts b/code/frameworks/experimental-nextjs-vite/src/portable-stories.ts
index f03e89bce5b5..1fbbcc24dd8c 100644
--- a/code/frameworks/experimental-nextjs-vite/src/portable-stories.ts
+++ b/code/frameworks/experimental-nextjs-vite/src/portable-stories.ts
@@ -32,7 +32,7 @@ import * as nextJsAnnotations from './preview';
  * Example:
  *
  * ```jsx
- * // setup.js (for jest)
+ * // setup-file.js
  * import { setProjectAnnotations } from '@storybook/experimental-nextjs-vite';
  * import projectAnnotations from './.storybook/preview';
  *
diff --git a/code/frameworks/nextjs/src/portable-stories.ts b/code/frameworks/nextjs/src/portable-stories.ts
index 28295342b4c8..a49d6bdf8162 100644
--- a/code/frameworks/nextjs/src/portable-stories.ts
+++ b/code/frameworks/nextjs/src/portable-stories.ts
@@ -32,7 +32,7 @@ import * as nextJsAnnotations from './preview';
  * Example:
  *
  * ```jsx
- * // setup.js (for jest)
+ * // setup-file.js
  * import { setProjectAnnotations } from '@storybook/nextjs';
  * import projectAnnotations from './.storybook/preview';
  *
diff --git a/code/renderers/react/src/portable-stories.tsx b/code/renderers/react/src/portable-stories.tsx
index 6969be0d65e8..2ea196e85b4b 100644
--- a/code/renderers/react/src/portable-stories.tsx
+++ b/code/renderers/react/src/portable-stories.tsx
@@ -31,7 +31,7 @@ import type { ReactRenderer } from './types';
  * Example:
  *
  * ```jsx
- * // setup.js (for jest)
+ * // setup-file.js
  * import { setProjectAnnotations } from '@storybook/react';
  * import projectAnnotations from './.storybook/preview';
  *
diff --git a/code/renderers/svelte/src/portable-stories.ts b/code/renderers/svelte/src/portable-stories.ts
index 416025c0346e..309b6c4071d4 100644
--- a/code/renderers/svelte/src/portable-stories.ts
+++ b/code/renderers/svelte/src/portable-stories.ts
@@ -49,7 +49,7 @@ type MapToComposed<TModule> = {
  * Example:
  *
  * ```jsx
- * // setup.js (for jest)
+ * // setup-file.js
  * import { setProjectAnnotations } from '@storybook/svelte';
  * import projectAnnotations from './.storybook/preview';
  *
diff --git a/code/renderers/vue3/src/portable-stories.ts b/code/renderers/vue3/src/portable-stories.ts
index eeba697c0023..e02b36b53628 100644
--- a/code/renderers/vue3/src/portable-stories.ts
+++ b/code/renderers/vue3/src/portable-stories.ts
@@ -39,7 +39,7 @@ type MapToJSXAble<T> = {
  * Example:
  *
  * ```jsx
- * // setup.js (for jest)
+ * // setup-file.js
  * import { setProjectAnnotations } from '@storybook/vue3';
  * import projectAnnotations from './.storybook/preview';
  *

From 52903fad217707af3afe960378b5affd78ab55e3 Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Fri, 16 Aug 2024 13:04:34 +0200
Subject: [PATCH 20/38] fix tests

---
 code/addons/vitest/src/plugin/viewports.test.ts | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/code/addons/vitest/src/plugin/viewports.test.ts b/code/addons/vitest/src/plugin/viewports.test.ts
index ef65a287fe42..811c813c3bff 100644
--- a/code/addons/vitest/src/plugin/viewports.test.ts
+++ b/code/addons/vitest/src/plugin/viewports.test.ts
@@ -24,8 +24,7 @@ describe('setViewport', () => {
   it('should do nothing if __vitest_browser__ is false', async () => {
     globalThis.__vitest_browser__ = false;
 
-    const result = await setViewport();
-    expect(result).toBeNull();
+    await setViewport();
     expect(page.viewport).not.toHaveBeenCalled();
   });
 

From 7ff1345743a48f716ec768548f049d3bf0d5f3c6 Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Fri, 16 Aug 2024 15:49:10 +0200
Subject: [PATCH 21/38] extra changes

---
 code/frameworks/sveltekit/src/portable-stories.ts |  4 +---
 scripts/tasks/sandbox-parts.ts                    | 10 +++++++++-
 2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/code/frameworks/sveltekit/src/portable-stories.ts b/code/frameworks/sveltekit/src/portable-stories.ts
index d17061f562f8..3264d09db9cf 100644
--- a/code/frameworks/sveltekit/src/portable-stories.ts
+++ b/code/frameworks/sveltekit/src/portable-stories.ts
@@ -10,9 +10,7 @@ import type {
 } from 'storybook/internal/types';
 
 import type { SvelteRenderer } from '@storybook/svelte';
-
-// TODO: investigate whether we need to prebundle this or can just import from the renderer package
-import { INTERNAL_DEFAULT_PROJECT_ANNOTATIONS as svelteAnnotations } from '../../../renderers/svelte/src/portable-stories';
+import { INTERNAL_DEFAULT_PROJECT_ANNOTATIONS as svelteAnnotations } from '@storybook/svelte';
 import * as svelteKitAnnotations from './preview';
 
 /**
diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts
index ed157aad2075..a0b24c7b4e26 100644
--- a/scripts/tasks/sandbox-parts.ts
+++ b/scripts/tasks/sandbox-parts.ts
@@ -366,7 +366,15 @@ export async function setupVitest(details: TemplateDetails, options: PassedOptio
   const isVue = template.expected.renderer === '@storybook/vue3';
   const isNextjs = template.expected.framework.includes('nextjs');
   // const isAngular = template.expected.framework === '@storybook/angular';
-  const storybookPackage = isNextjs ? template.expected.framework : template.expected.renderer;
+
+  const portableStoriesFrameworks = [
+    '@storybook/nextjs',
+    '@storybook/experimental-nextjs-vite',
+    // TODO: add sveltekit and angular once we enable their sandboxes
+  ];
+  const storybookPackage = portableStoriesFrameworks.includes(template.expected.framework)
+    ? template.expected.framework
+    : template.expected.renderer;
   const viteConfigPath = template.name.includes('JavaScript') ? 'vite.config.js' : 'vite.config.ts';
 
   await writeFile(

From a853126d1de6255088b30d86ad4451d7c39fccf7 Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Fri, 16 Aug 2024 17:59:12 +0200
Subject: [PATCH 22/38] refactor

---
 .../vitest/src/plugin/viewports.test.ts       | 108 ++++++++++++------
 code/addons/vitest/src/plugin/viewports.ts    |  37 ++++--
 code/core/src/preview-errors.ts               |  20 ++++
 3 files changed, 125 insertions(+), 40 deletions(-)

diff --git a/code/addons/vitest/src/plugin/viewports.test.ts b/code/addons/vitest/src/plugin/viewports.test.ts
index 811c813c3bff..e78182964966 100644
--- a/code/addons/vitest/src/plugin/viewports.test.ts
+++ b/code/addons/vitest/src/plugin/viewports.test.ts
@@ -21,14 +21,23 @@ describe('setViewport', () => {
     globalThis.__vitest_browser__ = false;
   });
 
-  it('should do nothing if __vitest_browser__ is false', async () => {
+  it('should no op outside when not in Vitest browser mode', async () => {
     globalThis.__vitest_browser__ = false;
 
     await setViewport();
     expect(page.viewport).not.toHaveBeenCalled();
   });
 
-  it('should set the viewport to the specified dimensions from INITIAL_VIEWPORTS', async () => {
+  it('should fall back to DEFAULT_VIEWPORT_DIMENSIONS if defaultViewport does not exist', async () => {
+    const viewportsParam: any = {
+      defaultViewport: 'nonExistentViewport',
+    };
+
+    await setViewport(viewportsParam);
+    expect(page.viewport).toHaveBeenCalledWith(1200, 900);
+  });
+
+  it('should set the dimensions of viewport from INITIAL_VIEWPORTS', async () => {
     const viewportsParam: any = {
       // supported by default in addon viewports
       defaultViewport: 'ipad',
@@ -38,72 +47,105 @@ describe('setViewport', () => {
     expect(page.viewport).toHaveBeenCalledWith(768, 1024);
   });
 
-  it('should set the viewport to the specified dimensions if defaultViewport is valid', async () => {
-    const viewportsParam: ViewportsParam = {
-      defaultViewport: 'small',
+  it('should set custom defined viewport dimensions', async () => {
+     const viewportsParam: ViewportsParam = {
+      defaultViewport: 'customViewport',
       viewports: {
-        small: {
-          name: 'Small screen',
+        customViewport: {
+          name: 'Custom Viewport',
           type: 'mobile',
           styles: {
-            width: '375px',
-            height: '667px',
+            width: '800px',
+            height: '600px',
           },
         },
       },
     };
 
     await setViewport(viewportsParam);
-    expect(page.viewport).toHaveBeenCalledWith(375, 667);
+    expect(page.viewport).toHaveBeenCalledWith(800, 600);
   });
 
-  it('should set the viewport to DEFAULT_VIEWPORT_DIMENSIONS if defaultViewport has unparseable styles', async () => {
+  it('should correctly handle percentage-based dimensions', async () => {
     const viewportsParam: ViewportsParam = {
-      defaultViewport: 'oddSizes',
+      defaultViewport: 'percentageViewport',
       viewports: {
-        oddSizes: {
-          name: 'foo',
-          type: 'other',
+        percentageViewport: {
+          name: 'Percentage Viewport',
+          type: 'desktop',
           styles: {
-            width: 'calc(100vw - 20px)',
-            height: '100%',
+            width: '50%',
+            height: '50%',
           },
         },
       },
     };
 
     await setViewport(viewportsParam);
-    expect(page.viewport).toHaveBeenCalledWith(
-      DEFAULT_VIEWPORT_DIMENSIONS.width,
-      DEFAULT_VIEWPORT_DIMENSIONS.height
-    );
+    expect(page.viewport).toHaveBeenCalledWith(600, 450); // 50% of 1920 and 1080
   });
 
-  it('should merge provided viewports with initial viewports', async () => {
+  it('should correctly handle vw and vh based dimensions', async () => {
     const viewportsParam: ViewportsParam = {
-      defaultViewport: 'customViewport',
+      defaultViewport: 'viewportUnits',
       viewports: {
-        customViewport: {
-          name: 'Custom Viewport',
-          type: 'mobile',
+        viewportUnits: {
+          name: 'VW/VH Viewport',
+          type: 'desktop',
           styles: {
-            width: '800px',
-            height: '600px',
+            width: '50vw',
+            height: '50vh',
           },
         },
       },
     };
 
     await setViewport(viewportsParam);
-    expect(page.viewport).toHaveBeenCalledWith(800, 600);
+    expect(page.viewport).toHaveBeenCalledWith(600, 450); // 50% of 1920 and 1080
   });
 
-  it('should fallback to DEFAULT_VIEWPORT_DIMENSIONS if defaultViewport does not exist', async () => {
-    const viewportsParam: any = {
-      defaultViewport: 'nonExistentViewport',
+  it('should correctly handle em based dimensions', async () => {
+    const viewportsParam: ViewportsParam = {
+      defaultViewport: 'viewportUnits',
+      viewports: {
+        viewportUnits: {
+          name: 'em/rem Viewport',
+          type: 'mobile',
+          styles: {
+            width: '20em',
+            height: '40rem',
+          },
+        },
+      },
     };
 
     await setViewport(viewportsParam);
-    expect(page.viewport).toHaveBeenCalledWith(1200, 900);
+    expect(page.viewport).toHaveBeenCalledWith(320, 640); // dimensions * 16
+  });
+
+  it('should throw an error for unsupported dimension values', async () => {
+    const viewportsParam: ViewportsParam = {
+      defaultViewport: 'invalidViewport',
+      viewports: {
+        invalidViewport: {
+          name: 'Invalid Viewport',
+          type: 'desktop',
+          styles: {
+            width: 'calc(100vw - 20px)',
+            height: '10pc',
+          },
+        },
+      },
+    };
+
+    await expect(setViewport(viewportsParam)).rejects.toThrowErrorMatchingInlineSnapshot(`
+      [SB_ADDON_VITEST_0001 (UnsupportedViewportDimensionError): Encountered an unsupported value "calc(100vw - 20px)" when setting the viewport width dimension.
+
+      The Storybook plugin only supports values in the following units:
+      - px, vh, vw, em, rem and %.
+
+      You can either change the viewport for this story or use one of the supported units.]
+    `);
+    expect(page.viewport).not.toHaveBeenCalled();
   });
 });
diff --git a/code/addons/vitest/src/plugin/viewports.ts b/code/addons/vitest/src/plugin/viewports.ts
index 747a90b57bc8..084129bd8903 100644
--- a/code/addons/vitest/src/plugin/viewports.ts
+++ b/code/addons/vitest/src/plugin/viewports.ts
@@ -3,6 +3,7 @@ import { page } from '@vitest/browser/context';
 
 import { INITIAL_VIEWPORTS } from '../../../viewport/src/defaults';
 import type { ViewportMap, ViewportStyles } from '../../../viewport/src/types';
+import { UnsupportedViewportDimensionError } from 'storybook/internal/preview-errors';
 
 declare global {
   // eslint-disable-next-line no-var, @typescript-eslint/naming-convention
@@ -19,6 +20,32 @@ export const DEFAULT_VIEWPORT_DIMENSIONS = {
   height: 900,
 };
 
+const validPixelOrNumber = /^\d+(px)?$/;
+const percentagePattern = /^(\d+(\.\d+)?%)$/;
+const vwPattern = /^(\d+(\.\d+)?vw)$/;
+const vhPattern = /^(\d+(\.\d+)?vh)$/;
+const emRemPattern = /^(\d+)(em|rem)$/;
+
+const parseDimension = (value: string, dimension: 'width' | 'height') => {
+  if (validPixelOrNumber.test(value)) {
+    return Number.parseInt(value, 10);
+  } else if (percentagePattern.test(value)) {
+    const percentageValue = parseFloat(value) / 100;
+    return Math.round(DEFAULT_VIEWPORT_DIMENSIONS[dimension] * percentageValue);
+  } else if (vwPattern.test(value)) {
+    const vwValue = parseFloat(value) / 100;
+    return Math.round(DEFAULT_VIEWPORT_DIMENSIONS.width * vwValue);
+  } else if (vhPattern.test(value)) {
+    const vhValue = parseFloat(value) / 100;
+    return Math.round(DEFAULT_VIEWPORT_DIMENSIONS.height * vhValue);
+  } else if (emRemPattern.test(value)) {
+    const emRemValue = Number.parseInt(value, 10);
+    return emRemValue * 16;
+  } else {
+    throw new UnsupportedViewportDimensionError({ dimension, value });
+  }
+};
+
 export const setViewport = async (viewportsParam: ViewportsParam = {} as ViewportsParam) => {
   const defaultViewport = viewportsParam.defaultViewport;
 
@@ -37,13 +64,9 @@ export const setViewport = async (viewportsParam: ViewportsParam = {} as Viewpor
   if (defaultViewport in viewports) {
     const styles = viewports[defaultViewport].styles as ViewportStyles;
     if (styles?.width && styles?.height) {
-      const validPixelOrNumber = /^\d+(px)?$/;
-
-      // if both dimensions are not valid numbers e.g. 'calc(100vh - 10px)' or '100%', use the default dimensions instead
-      if (validPixelOrNumber.test(styles.width) && validPixelOrNumber.test(styles.height)) {
-        viewportWidth = Number.parseInt(styles.width, 10);
-        viewportHeight = Number.parseInt(styles.height, 10);
-      }
+      const { width, height } = styles;
+      viewportWidth = parseDimension(width, 'width');
+      viewportHeight = parseDimension(height, 'height');
     }
   }
 
diff --git a/code/core/src/preview-errors.ts b/code/core/src/preview-errors.ts
index 79df5e45d8dd..a97271116967 100644
--- a/code/core/src/preview-errors.ts
+++ b/code/core/src/preview-errors.ts
@@ -30,6 +30,7 @@ export enum Category {
   RENDERER_VUE3 = 'RENDERER_VUE3',
   RENDERER_WEB_COMPONENTS = 'RENDERER_WEB-COMPONENTS',
   FRAMEWORK_NEXTJS = 'FRAMEWORK_NEXTJS',
+  ADDON_VITEST = 'ADDON_VITEST',
 }
 
 export class MissingStoryAfterHmrError extends StorybookError {
@@ -317,3 +318,22 @@ export class UnknownArgTypesError extends StorybookError {
     });
   }
 }
+
+export class UnsupportedViewportDimensionError extends StorybookError {
+  constructor(public data: { dimension: string; value: string; }) {
+    super({
+      category: Category.ADDON_VITEST,
+      code: 1,
+      // TODO: Add documentation about viewports support
+      // documentation: '',
+      message: dedent`
+        Encountered an unsupported value "${data.value}" when setting the viewport ${data.dimension} dimension.
+        
+        The Storybook plugin only supports values in the following units:
+        - px, vh, vw, em, rem and %.
+
+        You can either change the viewport for this story or use one of the supported units.
+      `,
+    });
+  }
+}

From 223e2f94f83238b8680b416203b4968ba1b34b6f Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Fri, 16 Aug 2024 18:25:52 +0200
Subject: [PATCH 23/38] update error message

---
 code/addons/vitest/src/plugin/viewports.test.ts | 2 +-
 code/core/src/preview-errors.ts                 | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/code/addons/vitest/src/plugin/viewports.test.ts b/code/addons/vitest/src/plugin/viewports.test.ts
index e78182964966..aa15fee3320e 100644
--- a/code/addons/vitest/src/plugin/viewports.test.ts
+++ b/code/addons/vitest/src/plugin/viewports.test.ts
@@ -144,7 +144,7 @@ describe('setViewport', () => {
       The Storybook plugin only supports values in the following units:
       - px, vh, vw, em, rem and %.
 
-      You can either change the viewport for this story or use one of the supported units.]
+      You can either change the viewport for this story to use one of the supported units or skip the test by adding '!test' to the story's tags per https://storybook.js.org/docs/writing-stories/tags]
     `);
     expect(page.viewport).not.toHaveBeenCalled();
   });
diff --git a/code/core/src/preview-errors.ts b/code/core/src/preview-errors.ts
index a97271116967..817645d6134b 100644
--- a/code/core/src/preview-errors.ts
+++ b/code/core/src/preview-errors.ts
@@ -331,8 +331,8 @@ export class UnsupportedViewportDimensionError extends StorybookError {
         
         The Storybook plugin only supports values in the following units:
         - px, vh, vw, em, rem and %.
-
-        You can either change the viewport for this story or use one of the supported units.
+        
+        You can either change the viewport for this story to use one of the supported units or skip the test by adding '!test' to the story's tags per https://storybook.js.org/docs/writing-stories/tags
       `,
     });
   }

From d20b3c6cfa86246bf09945af9b9d9c6305a3d309 Mon Sep 17 00:00:00 2001
From: Mikhail Shipov <mshipov@yandex.ru>
Date: Fri, 16 Aug 2024 23:58:33 +0300
Subject: [PATCH 24/38] add test story

---
 .../template/stories/docs2/UtfSymbolScroll.mdx     | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
 create mode 100644 code/addons/docs/template/stories/docs2/UtfSymbolScroll.mdx

diff --git a/code/addons/docs/template/stories/docs2/UtfSymbolScroll.mdx b/code/addons/docs/template/stories/docs2/UtfSymbolScroll.mdx
new file mode 100644
index 000000000000..11c902ce3baa
--- /dev/null
+++ b/code/addons/docs/template/stories/docs2/UtfSymbolScroll.mdx
@@ -0,0 +1,14 @@
+import { Meta } from '@storybook/addon-docs';
+
+<Meta title="UtfSymbolsScroll" />
+
+## Instruction
+
+> Instruction below works only in iframe.html. Unknown code in normal mode (with manager) removes hash from url.
+
+Click on [link](#anchor-with-utf-symbols-абвг). That will jump scroll to anchor after green block below. Then reload page and 
+it should smooth-scroll to that anchor. 
+
+<div style={{ height: "1500px", background: "green", color: "white" }}>Space for scroll test</div>
+
+## Anchor with utf symbols (абвг)
\ No newline at end of file

From 0969121205522c29c5ffaf086353931aacce996f Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Sat, 17 Aug 2024 09:28:08 +0200
Subject: [PATCH 25/38] fix lint errors

---
 code/addons/vitest/src/plugin/viewports.test.ts | 2 +-
 code/addons/vitest/src/plugin/viewports.ts      | 3 ++-
 code/core/src/preview-errors.ts                 | 2 +-
 3 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/code/addons/vitest/src/plugin/viewports.test.ts b/code/addons/vitest/src/plugin/viewports.test.ts
index aa15fee3320e..7b99e252ffe9 100644
--- a/code/addons/vitest/src/plugin/viewports.test.ts
+++ b/code/addons/vitest/src/plugin/viewports.test.ts
@@ -48,7 +48,7 @@ describe('setViewport', () => {
   });
 
   it('should set custom defined viewport dimensions', async () => {
-     const viewportsParam: ViewportsParam = {
+    const viewportsParam: ViewportsParam = {
       defaultViewport: 'customViewport',
       viewports: {
         customViewport: {
diff --git a/code/addons/vitest/src/plugin/viewports.ts b/code/addons/vitest/src/plugin/viewports.ts
index 084129bd8903..c68047877006 100644
--- a/code/addons/vitest/src/plugin/viewports.ts
+++ b/code/addons/vitest/src/plugin/viewports.ts
@@ -1,9 +1,10 @@
 /* eslint-disable no-underscore-dangle */
+import { UnsupportedViewportDimensionError } from 'storybook/internal/preview-errors';
+
 import { page } from '@vitest/browser/context';
 
 import { INITIAL_VIEWPORTS } from '../../../viewport/src/defaults';
 import type { ViewportMap, ViewportStyles } from '../../../viewport/src/types';
-import { UnsupportedViewportDimensionError } from 'storybook/internal/preview-errors';
 
 declare global {
   // eslint-disable-next-line no-var, @typescript-eslint/naming-convention
diff --git a/code/core/src/preview-errors.ts b/code/core/src/preview-errors.ts
index 817645d6134b..4bdf0fb3aefb 100644
--- a/code/core/src/preview-errors.ts
+++ b/code/core/src/preview-errors.ts
@@ -320,7 +320,7 @@ export class UnknownArgTypesError extends StorybookError {
 }
 
 export class UnsupportedViewportDimensionError extends StorybookError {
-  constructor(public data: { dimension: string; value: string; }) {
+  constructor(public data: { dimension: string; value: string }) {
     super({
       category: Category.ADDON_VITEST,
       code: 1,

From b24135dd7d1520dd83fabc88f2bc358afa24f64c Mon Sep 17 00:00:00 2001
From: Michael Shilman <shilman@users.noreply.github.com>
Date: Mon, 19 Aug 2024 09:14:20 +0800
Subject: [PATCH 26/38] Addon-docs: Remove babel dependency

---
 code/addons/docs/package.json | 1 -
 code/yarn.lock                | 1 -
 2 files changed, 2 deletions(-)

diff --git a/code/addons/docs/package.json b/code/addons/docs/package.json
index 28933b229b1a..23e63535db89 100644
--- a/code/addons/docs/package.json
+++ b/code/addons/docs/package.json
@@ -98,7 +98,6 @@
     "prep": "jiti ../../../scripts/prepare/bundle.ts"
   },
   "dependencies": {
-    "@babel/core": "^7.24.4",
     "@mdx-js/react": "^3.0.0",
     "@storybook/blocks": "workspace:*",
     "@storybook/csf-plugin": "workspace:*",
diff --git a/code/yarn.lock b/code/yarn.lock
index 34cf0e23881e..5a6a74d11ad1 100644
--- a/code/yarn.lock
+++ b/code/yarn.lock
@@ -5321,7 +5321,6 @@ __metadata:
   version: 0.0.0-use.local
   resolution: "@storybook/addon-docs@workspace:addons/docs"
   dependencies:
-    "@babel/core": "npm:^7.24.4"
     "@mdx-js/mdx": "npm:^3.0.0"
     "@mdx-js/react": "npm:^3.0.0"
     "@rollup/pluginutils": "npm:^5.0.2"

From 67afdd13fcd93b882047669da2287e561ad558ed Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Mon, 19 Aug 2024 11:49:55 +0200
Subject: [PATCH 27/38] increase CI class for vitest integration

---
 .circleci/config.yml           | 2 +-
 scripts/tasks/sandbox-parts.ts | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index cd966b042091..81ea1ffde026 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -403,7 +403,7 @@ jobs:
       parallelism:
         type: integer
     executor:
-      class: large
+      class: xlarge
       name: sb_playwright
     parallelism: << parameters.parallelism >>
     steps:
diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts
index a0b24c7b4e26..3bd7dc9b3624 100644
--- a/scripts/tasks/sandbox-parts.ts
+++ b/scripts/tasks/sandbox-parts.ts
@@ -488,7 +488,8 @@ export async function setupVitest(details: TemplateDetails, options: PassedOptio
 
   packageJson.scripts = {
     ...packageJson.scripts,
-    vitest: 'vitest --pass-with-no-tests --reporter=default --reporter=hanging-process',
+    vitest:
+      'vitest --pass-with-no-tests --reporter=default --reporter=hanging-process --test-timeout=5000',
   };
 
   // This workaround is needed because Vitest seems to have issues in link mode

From 70fc7966b5591543fdde4ee6008b794b0a1f529d Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Mon, 19 Aug 2024 11:28:34 +0200
Subject: [PATCH 28/38] add vite plugin to sveltekit

---
 code/frameworks/sveltekit/package.json | 12 +++++++++---
 code/frameworks/sveltekit/src/vite.ts  |  5 +++++
 2 files changed, 14 insertions(+), 3 deletions(-)
 create mode 100644 code/frameworks/sveltekit/src/vite.ts

diff --git a/code/frameworks/sveltekit/package.json b/code/frameworks/sveltekit/package.json
index 69a1986efac3..a4eb985b7543 100644
--- a/code/frameworks/sveltekit/package.json
+++ b/code/frameworks/sveltekit/package.json
@@ -25,8 +25,8 @@
   "exports": {
     ".": {
       "types": "./dist/index.d.ts",
-      "node": "./dist/index.js",
       "import": "./dist/index.mjs",
+      "node": "./dist/index.js",
       "require": "./dist/index.js"
     },
     "./dist/preview.mjs": {
@@ -36,6 +36,11 @@
       "types": "./dist/preset.d.ts",
       "require": "./dist/preset.js"
     },
+    "./vite": {
+      "types": "./dist/vite.d.ts",
+      "require": "./dist/vite.js",
+      "import": "./dist/vite.mjs"
+    },
     "./package.json": "./package.json"
   },
   "main": "dist/index.js",
@@ -78,9 +83,10 @@
     "entries": [
       "./src/index.ts",
       "./src/preview.ts",
-      "./src/preset.ts"
+      "./src/preset.ts",
+      "./src/vite.ts"
     ],
     "platform": "node"
   },
   "gitHead": "e6a7fd8a655c69780bc20b9749c2699e44beae16"
-}
+}
\ No newline at end of file
diff --git a/code/frameworks/sveltekit/src/vite.ts b/code/frameworks/sveltekit/src/vite.ts
new file mode 100644
index 000000000000..8c59bade8e82
--- /dev/null
+++ b/code/frameworks/sveltekit/src/vite.ts
@@ -0,0 +1,5 @@
+import { mockSveltekitStores } from './plugins/mock-sveltekit-stores'
+
+export const storybookSveltekitPlugin = () => {
+  return [mockSveltekitStores()]
+}
\ No newline at end of file

From 46844555bb67c74228670b67234ebbb1e8e571c4 Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Mon, 19 Aug 2024 11:44:18 +0200
Subject: [PATCH 29/38] SvelteKit: Add portable stories vite plugin

---
 .circleci/config.yml                          |  6 +--
 .../Forms.svelte                              |  2 +-
 .../cli-storybook/src/sandbox-templates.ts    |  8 ++--
 scripts/tasks/sandbox-parts.ts                | 37 +++++++++++++------
 4 files changed, 33 insertions(+), 20 deletions(-)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 81ea1ffde026..3e5c0b499cc6 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -723,7 +723,7 @@ workflows:
           requires:
             - build-sandboxes
       - vitest-integration:
-          parallelism: 4
+          parallelism: 5
           requires:
             - create-sandboxes
       - bench:
@@ -789,7 +789,7 @@ workflows:
           requires:
             - build-sandboxes
       - vitest-integration:
-          parallelism: 4
+          parallelism: 5
           requires:
             - create-sandboxes
       - test-portable-stories:
@@ -856,7 +856,7 @@ workflows:
           requires:
             - build-sandboxes
       - vitest-integration:
-          parallelism: 8
+          parallelism: 11
           requires:
             - create-sandboxes
       - test-portable-stories:
diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-skeleton-ts/Forms.svelte b/code/frameworks/sveltekit/template/stories_svelte-kit-skeleton-ts/Forms.svelte
index 371a17656bea..8513ae2a7064 100644
--- a/code/frameworks/sveltekit/template/stories_svelte-kit-skeleton-ts/Forms.svelte
+++ b/code/frameworks/sveltekit/template/stories_svelte-kit-skeleton-ts/Forms.svelte
@@ -2,6 +2,6 @@
 	import { enhance } from '$app/forms';
 </script>
 
-<form use:enhance>
+<form use:enhance method="post">
 	<button>enhance</button>
 </form>
\ No newline at end of file
diff --git a/code/lib/cli-storybook/src/sandbox-templates.ts b/code/lib/cli-storybook/src/sandbox-templates.ts
index bad1d34ba51b..ffdbccdd5e4d 100644
--- a/code/lib/cli-storybook/src/sandbox-templates.ts
+++ b/code/lib/cli-storybook/src/sandbox-templates.ts
@@ -449,8 +449,7 @@ const baseTemplates = {
       renderer: '@storybook/svelte',
       builder: '@storybook/builder-vite',
     },
-    // TODO: remove vitest-integration filter once project annotations exist for sveltekit
-    skipTasks: ['e2e-tests-dev', 'bench', 'vitest-integration'],
+    skipTasks: ['e2e-tests-dev', 'bench'],
   },
   'svelte-kit/skeleton-ts': {
     name: 'SvelteKit Latest (Vite | TypeScript)',
@@ -461,8 +460,7 @@ const baseTemplates = {
       renderer: '@storybook/svelte',
       builder: '@storybook/builder-vite',
     },
-    // TODO: remove vitest-integration filter once project annotations exist for sveltekit
-    skipTasks: ['e2e-tests-dev', 'bench', 'vitest-integration'],
+    skipTasks: ['e2e-tests-dev', 'bench'],
   },
   'svelte-kit/prerelease-ts': {
     name: 'SvelteKit Prerelease (Vite | TypeScript)',
@@ -473,7 +471,7 @@ const baseTemplates = {
       renderer: '@storybook/svelte',
       builder: '@storybook/builder-vite',
     },
-    skipTasks: ['e2e-tests-dev', 'bench', 'vitest-integration'],
+    skipTasks: ['e2e-tests-dev', 'bench'],
   },
   'lit-vite/default-js': {
     name: 'Lit Latest (Vite | JavaScript)',
diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts
index 3bd7dc9b3624..1efffdb68593 100644
--- a/scripts/tasks/sandbox-parts.ts
+++ b/scripts/tasks/sandbox-parts.ts
@@ -360,17 +360,40 @@ async function linkPackageStories(
   );
 }
 
+const getVitestPluginInfo = (details: TemplateDetails) => {
+  let frameworkPluginImport = '';
+  let frameworkPluginCall = '';
+
+  const framework = details.template.expected.framework;
+  const isNextjs = framework.includes('nextjs');
+  const isSveltekit = framework.includes('sveltekit');
+
+  if (isNextjs) {
+    frameworkPluginImport = "import vitePluginNext from 'vite-plugin-storybook-nextjs'";
+    frameworkPluginCall = 'vitePluginNext()';
+  }
+
+  if (isSveltekit) {
+    frameworkPluginImport = "import { storybookSveltekitPlugin } from '@storybook/sveltekit/vite'";
+    frameworkPluginCall = 'storybookSveltekitPlugin()';
+  }
+
+  return { frameworkPluginImport, frameworkPluginCall };
+};
+
 export async function setupVitest(details: TemplateDetails, options: PassedOptionValues) {
   const { sandboxDir, template } = details;
 
   const isVue = template.expected.renderer === '@storybook/vue3';
   const isNextjs = template.expected.framework.includes('nextjs');
+  const { frameworkPluginCall, frameworkPluginImport } = getVitestPluginInfo(details);
   // const isAngular = template.expected.framework === '@storybook/angular';
 
   const portableStoriesFrameworks = [
     '@storybook/nextjs',
     '@storybook/experimental-nextjs-vite',
-    // TODO: add sveltekit and angular once we enable their sandboxes
+    '@storybook/sveltekit',
+    // TODO: add angular once we enable their sandboxes
   ];
   const storybookPackage = portableStoriesFrameworks.includes(template.expected.framework)
     ? template.expected.framework
@@ -409,7 +432,7 @@ export async function setupVitest(details: TemplateDetails, options: PassedOptio
     dedent`
       import { defineWorkspace, defaultExclude } from "vitest/config";
       import { storybookTest } from "@storybook/experimental-addon-vitest/plugin";
-      ${isNextjs ? "import vitePluginNext from 'vite-plugin-storybook-nextjs'" : ''}
+      ${frameworkPluginImport}
 
       export default defineWorkspace([
         {
@@ -421,7 +444,7 @@ export async function setupVitest(details: TemplateDetails, options: PassedOptio
                 include: ["vitest"],
               },
             }),
-           ${isNextjs ? 'vitePluginNext(),' : ''}
+           ${frameworkPluginCall}
           ],
           ${
             isNextjs
@@ -452,14 +475,6 @@ export async function setupVitest(details: TemplateDetails, options: PassedOptio
               ...defaultExclude,
               // TODO: investigate TypeError: Cannot read properties of null (reading 'useContext')
               "**/*argtypes*",
-              // TODO (SVELTEKIT): Failures related to missing framework annotations
-              "**/frameworks/sveltekit_svelte-kit-skeleton-ts/navigation.stories*",
-              "**/frameworks/sveltekit_svelte-kit-skeleton-ts/hrefs.stories*",
-              // TODO (SVELTEKIT): Investigate Error: use:enhance can only be used on <form> fields with method="POST"
-              "**/frameworks/sveltekit_svelte-kit-skeleton-ts/forms.stories*",
-              // TODO (SVELTE|SVELTEKIT): Typescript preprocessor issue
-              "**/frameworks/svelte-vite_svelte-vite-default-ts/ts-docs.stories.*",
-              "**/frameworks/sveltekit_svelte-kit-skeleton-ts/ts-docs.stories.*",
             ],
             /**
              * TODO: Either fix or acknowledge limitation of:

From d8ee6ed018ad5953155399e1e9be3fba4e97dbec Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Mon, 19 Aug 2024 11:51:49 +0200
Subject: [PATCH 30/38] fix formatting

---
 code/frameworks/sveltekit/package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/code/frameworks/sveltekit/package.json b/code/frameworks/sveltekit/package.json
index a4eb985b7543..8b000ac64d7e 100644
--- a/code/frameworks/sveltekit/package.json
+++ b/code/frameworks/sveltekit/package.json
@@ -89,4 +89,4 @@
     "platform": "node"
   },
   "gitHead": "e6a7fd8a655c69780bc20b9749c2699e44beae16"
-}
\ No newline at end of file
+}

From 16b4f11acec6be053bb711bac8e69def6ee3949d Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Mon, 19 Aug 2024 12:09:01 +0200
Subject: [PATCH 31/38] fix lint

---
 code/frameworks/sveltekit/src/portable-stories.ts | 1 +
 code/frameworks/sveltekit/src/vite.ts             | 6 +++---
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/code/frameworks/sveltekit/src/portable-stories.ts b/code/frameworks/sveltekit/src/portable-stories.ts
index 3264d09db9cf..9edaaf8ac55b 100644
--- a/code/frameworks/sveltekit/src/portable-stories.ts
+++ b/code/frameworks/sveltekit/src/portable-stories.ts
@@ -11,6 +11,7 @@ import type {
 
 import type { SvelteRenderer } from '@storybook/svelte';
 import { INTERNAL_DEFAULT_PROJECT_ANNOTATIONS as svelteAnnotations } from '@storybook/svelte';
+
 import * as svelteKitAnnotations from './preview';
 
 /**
diff --git a/code/frameworks/sveltekit/src/vite.ts b/code/frameworks/sveltekit/src/vite.ts
index 8c59bade8e82..b3fd4f3e6dbe 100644
--- a/code/frameworks/sveltekit/src/vite.ts
+++ b/code/frameworks/sveltekit/src/vite.ts
@@ -1,5 +1,5 @@
-import { mockSveltekitStores } from './plugins/mock-sveltekit-stores'
+import { mockSveltekitStores } from './plugins/mock-sveltekit-stores';
 
 export const storybookSveltekitPlugin = () => {
-  return [mockSveltekitStores()]
-}
\ No newline at end of file
+  return [mockSveltekitStores()];
+};

From 8399ee121b0f80a754e59af4f2c3daf80ec1c8f3 Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Mon, 19 Aug 2024 12:09:32 +0200
Subject: [PATCH 32/38] fix lint

---
 code/frameworks/sveltekit/src/portable-stories.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/code/frameworks/sveltekit/src/portable-stories.ts b/code/frameworks/sveltekit/src/portable-stories.ts
index 3264d09db9cf..9edaaf8ac55b 100644
--- a/code/frameworks/sveltekit/src/portable-stories.ts
+++ b/code/frameworks/sveltekit/src/portable-stories.ts
@@ -11,6 +11,7 @@ import type {
 
 import type { SvelteRenderer } from '@storybook/svelte';
 import { INTERNAL_DEFAULT_PROJECT_ANNOTATIONS as svelteAnnotations } from '@storybook/svelte';
+
 import * as svelteKitAnnotations from './preview';
 
 /**

From e554a7f6b36e0ef0b7cabb9dfb1928992b11c1bc Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Tue, 20 Aug 2024 14:09:47 +0200
Subject: [PATCH 33/38] Addon Vitest: Improve transformation logic to avoid
 duplicate tests

This work changes the transformation done in the plugin to:
- account for no tests in a file
- transform only the stories that should be included (uses tags at compile time)
- execute only tests if they are imported in the same
---
 code/addons/vitest/src/plugin/index.ts        |  31 +-
 code/addons/vitest/src/plugin/test-utils.ts   |  32 +-
 .../vitest/src/plugin/viewports.test.ts       |   5 +-
 .../vitest-plugin/transformer.test.ts         | 311 ++++++++++++++----
 .../csf-tools/vitest-plugin/transformer.ts    | 260 ++++++++++-----
 5 files changed, 468 insertions(+), 171 deletions(-)

diff --git a/code/addons/vitest/src/plugin/index.ts b/code/addons/vitest/src/plugin/index.ts
index c1fbabb8b626..322e1d5bb74b 100644
--- a/code/addons/vitest/src/plugin/index.ts
+++ b/code/addons/vitest/src/plugin/index.ts
@@ -3,8 +3,12 @@ import { join, resolve } from 'node:path';
 
 import type { Plugin } from 'vitest/config';
 
-import { loadAllPresets, validateConfigurationFiles } from 'storybook/internal/common';
-import { vitestTransform } from 'storybook/internal/csf-tools';
+import {
+  getInterpretedFile,
+  loadAllPresets,
+  validateConfigurationFiles,
+} from 'storybook/internal/common';
+import { readConfig, vitestTransform } from 'storybook/internal/csf-tools';
 import { MainFileMissingError } from 'storybook/internal/server-errors';
 import type { StoriesEntry } from 'storybook/internal/types';
 
@@ -16,6 +20,16 @@ const defaultOptions: UserOptions = {
   storybookUrl: 'http://localhost:6006',
 };
 
+const extractTagsFromPreview = async (configDir: string) => {
+  const previewConfigPath = getInterpretedFile(join(resolve(configDir), 'preview'));
+
+  if (!previewConfigPath) {
+    return [];
+  }
+  const previewConfig = await readConfig(previewConfigPath);
+  return previewConfig.getFieldValue(['tags']) ?? [];
+};
+
 export const storybookTest = (options?: UserOptions): Plugin => {
   const finalOptions = {
     ...defaultOptions,
@@ -45,27 +59,33 @@ export const storybookTest = (options?: UserOptions): Plugin => {
     finalOptions.configDir = resolve(process.cwd(), finalOptions.configDir);
   }
 
+  let previewLevelTags: string[];
+
   return {
     name: 'vite-plugin-storybook-test',
     enforce: 'pre',
     async buildStart() {
+      // evaluate main.js and preview.js so we can extract
+      // stories for autotitle support and tags for tags filtering support
+      const configDir = finalOptions.configDir;
       try {
-        await validateConfigurationFiles(finalOptions.configDir);
+        await validateConfigurationFiles(configDir);
       } catch (err) {
         throw new MainFileMissingError({
-          location: finalOptions.configDir,
+          location: configDir,
           source: 'vitest',
         });
       }
 
       const presets = await loadAllPresets({
-        configDir: finalOptions.configDir,
+        configDir,
         corePresets: [],
         overridePresets: [],
         packageJson: {},
       });
 
       stories = await presets.apply('stories', []);
+      previewLevelTags = await extractTagsFromPreview(configDir);
     },
     async config(config) {
       // If we end up needing to know if we are running in browser mode later
@@ -123,6 +143,7 @@ export const storybookTest = (options?: UserOptions): Plugin => {
           configDir: finalOptions.configDir,
           tagsFilter: finalOptions.tags,
           stories,
+          previewLevelTags,
         });
       }
     },
diff --git a/code/addons/vitest/src/plugin/test-utils.ts b/code/addons/vitest/src/plugin/test-utils.ts
index a86a178cc429..632ace561619 100644
--- a/code/addons/vitest/src/plugin/test-utils.ts
+++ b/code/addons/vitest/src/plugin/test-utils.ts
@@ -3,33 +3,29 @@
 /* eslint-disable no-underscore-dangle */
 import { type RunnerTask, type TaskContext, type TaskMeta, type TestContext } from 'vitest';
 
-import type { ComposedStoryFn } from 'storybook/internal/types';
+import { composeStory } from 'storybook/internal/preview-api';
+import type { ComponentAnnotations, ComposedStoryFn } from 'storybook/internal/types';
 
-import type { UserOptions } from './types';
 import { setViewport } from './viewports';
 
-type TagsFilter = Required<UserOptions['tags']>;
-
-export const isValidTest = (storyTags: string[], tagsFilter: TagsFilter) => {
-  const isIncluded =
-    tagsFilter?.include.length === 0 || tagsFilter?.include.some((tag) => storyTags.includes(tag));
-  const isNotExcluded = tagsFilter?.exclude.every((tag) => !storyTags.includes(tag));
-
-  return isIncluded && isNotExcluded;
-};
-
-export const testStory = (Story: ComposedStoryFn, tagsFilter: TagsFilter) => {
+export const testStory = (
+  exportName: string,
+  story: ComposedStoryFn,
+  meta: ComponentAnnotations,
+  skipTags: string[]
+) => {
+  const composedStory = composeStory(story, meta, undefined, undefined, exportName);
   return async (context: TestContext & TaskContext & { story: ComposedStoryFn }) => {
-    if (Story === undefined || tagsFilter?.skip.some((tag) => Story.tags.includes(tag))) {
+    if (composedStory === undefined || skipTags?.some((tag) => composedStory.tags.includes(tag))) {
       context.skip();
     }
 
-    context.story = Story;
+    context.story = composedStory;
 
     const _task = context.task as RunnerTask & { meta: TaskMeta & { storyId: string } };
-    _task.meta.storyId = Story.id;
+    _task.meta.storyId = composedStory.id;
 
-    await setViewport(Story.parameters.viewport);
-    await Story.run();
+    await setViewport(composedStory.parameters.viewport);
+    await composedStory.run();
   };
 };
diff --git a/code/addons/vitest/src/plugin/viewports.test.ts b/code/addons/vitest/src/plugin/viewports.test.ts
index 7b99e252ffe9..cd83f5edc518 100644
--- a/code/addons/vitest/src/plugin/viewports.test.ts
+++ b/code/addons/vitest/src/plugin/viewports.test.ts
@@ -34,7 +34,10 @@ describe('setViewport', () => {
     };
 
     await setViewport(viewportsParam);
-    expect(page.viewport).toHaveBeenCalledWith(1200, 900);
+    expect(page.viewport).toHaveBeenCalledWith(
+      DEFAULT_VIEWPORT_DIMENSIONS.width,
+      DEFAULT_VIEWPORT_DIMENSIONS.height
+    );
   });
 
   it('should set the dimensions of viewport from INITIAL_VIEWPORTS', async () => {
diff --git a/code/core/src/csf-tools/vitest-plugin/transformer.test.ts b/code/core/src/csf-tools/vitest-plugin/transformer.test.ts
index 76ec0458ab9a..a92af8c4cc2f 100644
--- a/code/core/src/csf-tools/vitest-plugin/transformer.test.ts
+++ b/code/core/src/csf-tools/vitest-plugin/transformer.test.ts
@@ -24,13 +24,21 @@ const transform = async ({
   fileName = 'src/components/Button.stories.js',
   tagsFilter = {
     include: ['test'],
-    exclude: [],
-    skip: [],
+    exclude: [] as string[],
+    skip: [] as string[],
   },
   configDir = '.storybook',
   stories = [],
+  previewLevelTags = [],
 }) => {
-  const transformed = await originalTransform({ code, fileName, configDir, stories, tagsFilter });
+  const transformed = await originalTransform({
+    code,
+    fileName,
+    configDir,
+    stories,
+    tagsFilter,
+    previewLevelTags,
+  });
   if (typeof transformed === 'string') {
     return { code: transformed, map: null };
   }
@@ -53,10 +61,10 @@ describe('transformer', () => {
   describe('default exports (meta)', () => {
     it('should add title to inline default export if not present', async () => {
       const code = `
-        import { _test } from 'bla';
         export default {
           component: Button,
         };
+        export const Story = {};
       `;
 
       const result = await transform({ code });
@@ -64,15 +72,18 @@ describe('transformer', () => {
       expect(getStoryTitle).toHaveBeenCalled();
 
       expect(result.code).toMatchInlineSnapshot(`
-        import { test as _test2 } from "vitest";
-        import { composeStory as _composeStory } from "storybook/internal/preview-api";
-        import { testStory as _testStory, isValidTest as _isValidTest } from "@storybook/experimental-addon-vitest/internal/test-utils";
-        import { _test } from 'bla';
+        import { test as _test, expect as _expect } from "vitest";
+        import { testStory as _testStory } from "@storybook/experimental-addon-vitest/internal/test-utils";
         const _meta = {
           component: Button,
           title: "automatic/calculated/title"
         };
         export default _meta;
+        export const Story = {};
+        const _isRunningFromThisFile = import.meta.url.includes(_expect.getState().testPath ?? globalThis.__vitest_worker__.filepath);
+        if (_isRunningFromThisFile) {
+          _test("Story", _testStory("Story", Story, _meta, []));
+        }
       `);
     });
 
@@ -82,6 +93,7 @@ describe('transformer', () => {
           title: 'Button',
           component: Button,
         };
+        export const Story = {};
       `;
 
       const result = await transform({ code });
@@ -89,14 +101,18 @@ describe('transformer', () => {
       expect(getStoryTitle).toHaveBeenCalled();
 
       expect(result.code).toMatchInlineSnapshot(`
-        import { test as _test } from "vitest";
-        import { composeStory as _composeStory } from "storybook/internal/preview-api";
-        import { testStory as _testStory, isValidTest as _isValidTest } from "@storybook/experimental-addon-vitest/internal/test-utils";
+        import { test as _test, expect as _expect } from "vitest";
+        import { testStory as _testStory } from "@storybook/experimental-addon-vitest/internal/test-utils";
         const _meta = {
           title: "automatic/calculated/title",
           component: Button
         };
         export default _meta;
+        export const Story = {};
+        const _isRunningFromThisFile = import.meta.url.includes(_expect.getState().testPath ?? globalThis.__vitest_worker__.filepath);
+        if (_isRunningFromThisFile) {
+          _test("Story", _testStory("Story", Story, _meta, []));
+        }
       `);
     });
 
@@ -105,8 +121,9 @@ describe('transformer', () => {
         const meta = {
           component: Button,
         };
-  
         export default meta;
+
+        export const Story = {};
       `;
 
       const result = await transform({ code });
@@ -114,14 +131,18 @@ describe('transformer', () => {
       expect(getStoryTitle).toHaveBeenCalled();
 
       expect(result.code).toMatchInlineSnapshot(`
-        import { test as _test } from "vitest";
-        import { composeStory as _composeStory } from "storybook/internal/preview-api";
-        import { testStory as _testStory, isValidTest as _isValidTest } from "@storybook/experimental-addon-vitest/internal/test-utils";
+        import { test as _test, expect as _expect } from "vitest";
+        import { testStory as _testStory } from "@storybook/experimental-addon-vitest/internal/test-utils";
         const meta = {
           component: Button,
           title: "automatic/calculated/title"
         };
         export default meta;
+        export const Story = {};
+        const _isRunningFromThisFile = import.meta.url.includes(_expect.getState().testPath ?? globalThis.__vitest_worker__.filepath);
+        if (_isRunningFromThisFile) {
+          _test("Story", _testStory("Story", Story, meta, []));
+        }
       `);
     });
 
@@ -130,9 +151,10 @@ describe('transformer', () => {
         const meta = {
           title: 'Button',
           component: Button,
-        };
-  
+        };  
         export default meta;
+
+        export const Story = {};
       `;
 
       const result = await transform({ code });
@@ -140,14 +162,18 @@ describe('transformer', () => {
       expect(getStoryTitle).toHaveBeenCalled();
 
       expect(result.code).toMatchInlineSnapshot(`
-        import { test as _test } from "vitest";
-        import { composeStory as _composeStory } from "storybook/internal/preview-api";
-        import { testStory as _testStory, isValidTest as _isValidTest } from "@storybook/experimental-addon-vitest/internal/test-utils";
+        import { test as _test, expect as _expect } from "vitest";
+        import { testStory as _testStory } from "@storybook/experimental-addon-vitest/internal/test-utils";
         const meta = {
           title: "automatic/calculated/title",
           component: Button
         };
         export default meta;
+        export const Story = {};
+        const _isRunningFromThisFile = import.meta.url.includes(_expect.getState().testPath ?? globalThis.__vitest_worker__.filepath);
+        if (_isRunningFromThisFile) {
+          _test("Story", _testStory("Story", Story, meta, []));
+        }
       `);
     });
   });
@@ -155,22 +181,21 @@ describe('transformer', () => {
   describe('named exports (stories)', () => {
     it('should add test statement to inline exported stories', async () => {
       const code = `
-      export default {
-        component: Button,
-      }
-      export const Primary = {
-        args: {
-          label: 'Primary Button',
-        },
-      };
-    `;
+        export default {
+          component: Button,
+        }
+        export const Primary = {
+          args: {
+            label: 'Primary Button',
+          },
+        };
+      `;
 
       const result = await transform({ code });
 
       expect(result.code).toMatchInlineSnapshot(`
-        import { test as _test } from "vitest";
-        import { composeStory as _composeStory } from "storybook/internal/preview-api";
-        import { testStory as _testStory, isValidTest as _isValidTest } from "@storybook/experimental-addon-vitest/internal/test-utils";
+        import { test as _test, expect as _expect } from "vitest";
+        import { testStory as _testStory } from "@storybook/experimental-addon-vitest/internal/test-utils";
         const _meta = {
           component: Button,
           title: "automatic/calculated/title"
@@ -181,31 +206,65 @@ describe('transformer', () => {
             label: 'Primary Button'
           }
         };
-        const _composedPrimary = _composeStory(Primary, _meta, undefined, undefined, "Primary");
-        if (_isValidTest(_composedPrimary.tags, {"include":["test"],"exclude":[],"skip":[]})) {
-          _test("Primary", _testStory(_composedPrimary, {"include":["test"],"exclude":[],"skip":[]}));
+        const _isRunningFromThisFile = import.meta.url.includes(_expect.getState().testPath ?? globalThis.__vitest_worker__.filepath);
+        if (_isRunningFromThisFile) {
+          _test("Primary", _testStory("Primary", Primary, _meta, []));
         }
       `);
     });
 
     it('should add test statement to const declared exported stories', async () => {
       const code = `
-      export default {};
-      const Primary = {
-        args: {
-          label: 'Primary Button',
-        },
-      };
+        export default {};
+        const Primary = {
+          args: {
+            label: 'Primary Button',
+          },
+        };
 
-      export { Primary };
-    `;
+        export { Primary };
+      `;
 
       const result = await transform({ code });
 
       expect(result.code).toMatchInlineSnapshot(`
-        import { test as _test } from "vitest";
-        import { composeStory as _composeStory } from "storybook/internal/preview-api";
-        import { testStory as _testStory, isValidTest as _isValidTest } from "@storybook/experimental-addon-vitest/internal/test-utils";
+        import { test as _test, expect as _expect } from "vitest";
+        import { testStory as _testStory } from "@storybook/experimental-addon-vitest/internal/test-utils";
+        const _meta = {
+          title: "automatic/calculated/title"
+        };
+        export default _meta;
+        const Primary = {
+          args: {
+            label: 'Primary Button'
+          }
+        };
+        export { Primary };
+        const _isRunningFromThisFile = import.meta.url.includes(_expect.getState().testPath ?? globalThis.__vitest_worker__.filepath);
+        if (_isRunningFromThisFile) {
+          _test("Primary", _testStory("Primary", Primary, _meta, []));
+        }
+      `);
+    });
+
+    it('should add tests for multiple stories', async () => {
+      const code = `
+        export default {};
+        const Primary = {
+          args: {
+            label: 'Primary Button',
+          },
+        };
+
+        export const Secondary = {}
+
+        export { Primary };
+      `;
+
+      const result = await transform({ code });
+      expect(result.code).toMatchInlineSnapshot(`
+        import { test as _test, expect as _expect } from "vitest";
+        import { testStory as _testStory } from "@storybook/experimental-addon-vitest/internal/test-utils";
         const _meta = {
           title: "automatic/calculated/title"
         };
@@ -215,37 +274,160 @@ describe('transformer', () => {
             label: 'Primary Button'
           }
         };
+        export const Secondary = {};
         export { Primary };
-        const _composedPrimary = _composeStory(Primary, _meta, undefined, undefined, "Primary");
-        if (_isValidTest(_composedPrimary.tags, {"include":["test"],"exclude":[],"skip":[]})) {
-          _test("Primary", _testStory(_composedPrimary, {"include":["test"],"exclude":[],"skip":[]}));
+        const _isRunningFromThisFile = import.meta.url.includes(_expect.getState().testPath ?? globalThis.__vitest_worker__.filepath);
+        if (_isRunningFromThisFile) {
+          _test("Secondary", _testStory("Secondary", Secondary, _meta, []));
+          _test("Primary", _testStory("Primary", Primary, _meta, []));
         }
       `);
     });
 
     it('should exclude exports via excludeStories', async () => {
       const code = `
-      export default {
-        title: 'Button',
-        component: Button,
-        excludeStories: ['nonStory'],
-      }
-      export const nonStory = 123
-    `;
+        export default {
+          title: 'Button',
+          component: Button,
+          excludeStories: ['nonStory'],
+        }
+        export const Story = {};
+        export const nonStory = 123
+      `;
 
       const result = await transform({ code });
 
       expect(result.code).toMatchInlineSnapshot(`
-        import { test as _test } from "vitest";
-        import { composeStory as _composeStory } from "storybook/internal/preview-api";
-        import { testStory as _testStory, isValidTest as _isValidTest } from "@storybook/experimental-addon-vitest/internal/test-utils";
+        import { test as _test, expect as _expect } from "vitest";
+        import { testStory as _testStory } from "@storybook/experimental-addon-vitest/internal/test-utils";
         const _meta = {
           title: "automatic/calculated/title",
           component: Button,
           excludeStories: ['nonStory']
         };
         export default _meta;
+        export const Story = {};
         export const nonStory = 123;
+        const _isRunningFromThisFile = import.meta.url.includes(_expect.getState().testPath ?? globalThis.__vitest_worker__.filepath);
+        if (_isRunningFromThisFile) {
+          _test("Story", _testStory("Story", Story, _meta, []));
+        }
+      `);
+    });
+
+    it('should return a describe with skip if there are no valid stories', async () => {
+      const code = `
+        export default {
+          title: 'Button',
+          component: Button,
+          tags: ['!test']
+        }
+        export const Story = {}
+      `;
+      const result = await transform({ code });
+
+      expect(result.code).toMatchInlineSnapshot(`
+        import { test as _test, describe as _describe } from "vitest";
+        const _meta = {
+          title: "automatic/calculated/title",
+          component: Button,
+          tags: ['!test']
+        };
+        export default _meta;
+        export const Story = {};
+        _describe.skip("No valid tests found");
+      `);
+    });
+  });
+
+  describe('tags filtering mechanism', () => {
+    it('should only include stories from tags.include', async () => {
+      const code = `
+        export default {};
+        export const Included = { tags: ['include-me'] };
+
+        export const NotIncluded = {}
+      `;
+
+      const result = await transform({
+        code,
+        tagsFilter: { include: ['include-me'], exclude: [], skip: [] },
+      });
+
+      expect(result.code).toMatchInlineSnapshot(`
+        import { test as _test, expect as _expect } from "vitest";
+        import { testStory as _testStory } from "@storybook/experimental-addon-vitest/internal/test-utils";
+        const _meta = {
+          title: "automatic/calculated/title"
+        };
+        export default _meta;
+        export const Included = {
+          tags: ['include-me']
+        };
+        export const NotIncluded = {};
+        const _isRunningFromThisFile = import.meta.url.includes(_expect.getState().testPath ?? globalThis.__vitest_worker__.filepath);
+        if (_isRunningFromThisFile) {
+          _test("Included", _testStory("Included", Included, _meta, []));
+        }
+      `);
+    });
+
+    it('should exclude stories from tags.exclude', async () => {
+      const code = `
+        export default {};
+        export const Included = {};
+
+        export const NotIncluded = { tags: ['exclude-me'] }
+      `;
+
+      const result = await transform({
+        code,
+        tagsFilter: { include: ['test'], exclude: ['exclude-me'], skip: [] },
+      });
+
+      expect(result.code).toMatchInlineSnapshot(`
+        import { test as _test, expect as _expect } from "vitest";
+        import { testStory as _testStory } from "@storybook/experimental-addon-vitest/internal/test-utils";
+        const _meta = {
+          title: "automatic/calculated/title"
+        };
+        export default _meta;
+        export const Included = {};
+        export const NotIncluded = {
+          tags: ['exclude-me']
+        };
+        const _isRunningFromThisFile = import.meta.url.includes(_expect.getState().testPath ?? globalThis.__vitest_worker__.filepath);
+        if (_isRunningFromThisFile) {
+          _test("Included", _testStory("Included", Included, _meta, []));
+        }
+      `);
+    });
+
+    it('should pass skip tags to testStory call using tags.skip', async () => {
+      const code = `
+        export default {};
+        export const Skipped = { tags: ['skip-me'] };
+      `;
+
+      const result = await transform({
+        code,
+        tagsFilter: { include: ['test'], exclude: [], skip: ['skip-me'] },
+      });
+
+      expect(result.code).toMatchInlineSnapshot(`
+        import { test as _test, expect as _expect } from "vitest";
+        import { testStory as _testStory } from "@storybook/experimental-addon-vitest/internal/test-utils";
+        const _meta = {
+          title: "automatic/calculated/title"
+        };
+        export default _meta;
+        export const Skipped = {
+          tags: ['skip-me']
+        };
+        const _isRunningFromThisFile = import.meta.url.includes(_expect.getState().testPath ?? globalThis.__vitest_worker__.filepath);
+        if (_isRunningFromThisFile) {
+          _test("Skipped", _testStory("Skipped", Skipped, _meta, ["skip-me"]));
+        }
       `);
     });
   });
@@ -266,18 +448,17 @@ describe('transformer', () => {
       });
 
       expect(transformedCode).toMatchInlineSnapshot(`
-        import { test as _test } from "vitest";
-        import { composeStory as _composeStory } from "storybook/internal/preview-api";
-        import { testStory as _testStory, isValidTest as _isValidTest } from "@storybook/experimental-addon-vitest/internal/test-utils";
+        import { test as _test, expect as _expect } from "vitest";
+        import { testStory as _testStory } from "@storybook/experimental-addon-vitest/internal/test-utils";
         const meta = {
           title: "automatic/calculated/title",
           component: Button
         };
         export default meta;
         export const Primary = {};
-        const _composedPrimary = _composeStory(Primary, meta, undefined, undefined, "Primary");
-        if (_isValidTest(_composedPrimary.tags, {"include":["test"],"exclude":[],"skip":[]})) {
-          _test("Primary", _testStory(_composedPrimary, {"include":["test"],"exclude":[],"skip":[]}));
+        const _isRunningFromThisFile = import.meta.url.includes(_expect.getState().testPath ?? globalThis.__vitest_worker__.filepath);
+        if (_isRunningFromThisFile) {
+          _test("Primary", _testStory("Primary", Primary, meta, []));
         }
       `);
 
diff --git a/code/core/src/csf-tools/vitest-plugin/transformer.ts b/code/core/src/csf-tools/vitest-plugin/transformer.ts
index aafe709a2399..c3753720b17f 100644
--- a/code/core/src/csf-tools/vitest-plugin/transformer.ts
+++ b/code/core/src/csf-tools/vitest-plugin/transformer.ts
@@ -2,7 +2,8 @@
 
 /* eslint-disable no-underscore-dangle */
 import { getStoryTitle } from '@storybook/core/common';
-import type { StoriesEntry } from '@storybook/core/types';
+import type { StoriesEntry, Tag } from '@storybook/core/types';
+import { combineTags } from '@storybook/csf';
 
 import * as t from '@babel/types';
 import { dedent } from 'ts-dedent';
@@ -11,22 +12,34 @@ import { formatCsf, loadCsf } from '../CsfFile';
 
 const logger = console;
 
+type TagsFilter = {
+  include: string[];
+  exclude: string[];
+  skip: string[];
+};
+
+const isValidTest = (storyTags: string[], tagsFilter: TagsFilter) => {
+  const isIncluded =
+    tagsFilter?.include.length === 0 || tagsFilter?.include.some((tag) => storyTags.includes(tag));
+  const isNotExcluded = tagsFilter?.exclude.every((tag) => !storyTags.includes(tag));
+
+  return isIncluded && isNotExcluded;
+};
+
 export async function vitestTransform({
   code,
   fileName,
   configDir,
   stories,
   tagsFilter,
+  previewLevelTags = [],
 }: {
   code: string;
   fileName: string;
   configDir: string;
-  tagsFilter: {
-    include: string[];
-    exclude: string[];
-    skip: string[];
-  };
+  tagsFilter: TagsFilter;
   stories: StoriesEntry[];
+  previewLevelTags: Tag[];
 }) {
   const isStoryFile = /\.stor(y|ies)\./.test(fileName);
   if (!isStoryFile) {
@@ -81,90 +94,173 @@ export async function vitestTransform({
     );
   }
 
+  // Filter out stories based on the passed tags filter
+  let validStories: (typeof parsed)['_storyStatements'] = {};
+  Object.keys(parsed._stories).map((key) => {
+    const finalTags = combineTags(
+      'test',
+      'dev',
+      ...previewLevelTags,
+      ...(parsed.meta?.tags || []),
+      ...(parsed._stories[key].tags || [])
+    );
+
+    if (isValidTest(finalTags, tagsFilter)) {
+      validStories[key] = parsed._storyStatements[key];
+    }
+  });
+
   const vitestTestId = parsed._file.path.scope.generateUidIdentifier('test');
-  const composeStoryId = parsed._file.path.scope.generateUidIdentifier('composeStory');
-  const testStoryId = parsed._file.path.scope.generateUidIdentifier('testStory');
-  const isValidTestId = parsed._file.path.scope.generateUidIdentifier('isValidTest');
-
-  const tagsFilterId = t.identifier(JSON.stringify(tagsFilter));
-
-  const getTestStatementForStory = ({ exportName, node }: { exportName: string; node: t.Node }) => {
-    const composedStoryId = parsed._file.path.scope.generateUidIdentifier(`composed${exportName}`);
-
-    const composeStoryCall = t.variableDeclaration('const', [
-      t.variableDeclarator(
-        composedStoryId,
-        t.callExpression(composeStoryId, [
-          t.identifier(exportName),
-          t.identifier(metaExportName),
-          t.identifier('undefined'),
-          t.identifier('undefined'),
-          t.stringLiteral(exportName),
-        ])
-      ),
-    ]);
-
-    // Preserve sourcemaps location
-    composeStoryCall.loc = node.loc;
-
-    const isValidTestCall = t.ifStatement(
-      t.callExpression(isValidTestId, [
-        t.memberExpression(composedStoryId, t.identifier('tags')),
-        tagsFilterId,
-      ]),
-      t.blockStatement([
-        t.expressionStatement(
-          t.callExpression(vitestTestId, [
-            t.stringLiteral(exportName),
-            t.callExpression(testStoryId, [composedStoryId, tagsFilterId]),
-          ])
-        ),
+  const vitestDescribeId = parsed._file.path.scope.generateUidIdentifier('describe');
+
+  // if no valid stories are found, we just add describe.skip() to the file to avoid empty test files
+  if (Object.keys(validStories).length === 0) {
+    const describeSkipBlock = t.expressionStatement(
+      t.callExpression(t.memberExpression(vitestDescribeId, t.identifier('skip')), [
+        t.stringLiteral('No valid tests found'),
       ])
     );
-    // Preserve sourcemaps location
-    isValidTestCall.loc = node.loc;
-
-    return [composeStoryCall, isValidTestCall];
-  };
-
-  Object.entries(parsed._storyStatements).forEach(([exportName, node]) => {
-    if (node === null) {
-      logger.warn(
-        dedent`
-          [Storybook]: Could not transform "${exportName}" story into test at "${fileName}".
-          Please make sure to define stories in the same file and not re-export stories coming from other files".
-        `
+
+    ast.program.body.push(describeSkipBlock);
+    const imports = [
+      t.importDeclaration(
+        [
+          t.importSpecifier(vitestTestId, t.identifier('test')),
+          t.importSpecifier(vitestDescribeId, t.identifier('describe')),
+        ],
+        t.stringLiteral('vitest')
+      ),
+    ];
+
+    ast.program.body.unshift(...imports);
+  } else {
+    const vitestExpectId = parsed._file.path.scope.generateUidIdentifier('expect');
+    const composeStoryId = parsed._file.path.scope.generateUidIdentifier('composeStory');
+    const testStoryId = parsed._file.path.scope.generateUidIdentifier('testStory');
+    const skipTagsId = t.identifier(JSON.stringify(tagsFilter.skip));
+
+    /**
+     * In Storybook users might be importing stories from other story files. As a side effect, tests
+     * can get re-triggered. To avoid this, we add a guard to only run tests if the current file is
+     * the one running the test.
+     *
+     * Const isRunningFromThisFile = import.meta.url.includes(expect.getState().testPath ??
+     * globalThis.**vitest_worker**.filepath) if(isRunningFromThisFile) { ... }
+     */
+    function getTestGuardDeclaration(vitestExpectId: t.Identifier) {
+      const isRunningFromThisFileId =
+        parsed._file.path.scope.generateUidIdentifier('isRunningFromThisFile');
+
+      // expect.getState().testPath
+      const testPathProperty = t.memberExpression(
+        t.callExpression(t.memberExpression(vitestExpectId, t.identifier('getState')), []),
+        t.identifier('testPath')
+      );
+
+      // There is a bug in Vitest where expect.getState().testPath is undefined when called outside of a test function so we add this fallback in the meantime
+      // https://github.com/vitest-dev/vitest/issues/6367
+      // globalThis.__vitest_worker__.filepath
+      const filePathProperty = t.memberExpression(
+        t.memberExpression(t.identifier('globalThis'), t.identifier('__vitest_worker__')),
+        t.identifier('filepath')
+      );
+
+      // Combine testPath and filepath using the ?? operator
+      const nullishCoalescingExpression = t.logicalExpression(
+        '??',
+        testPathProperty,
+        filePathProperty
       );
-      return;
+
+      // Create the final expression: import.meta.url.includes(...)
+      const includesCall = t.callExpression(
+        t.memberExpression(
+          t.memberExpression(
+            t.memberExpression(t.identifier('import'), t.identifier('meta')),
+            t.identifier('url')
+          ),
+          t.identifier('includes')
+        ),
+        [nullishCoalescingExpression]
+      );
+
+      const isRunningFromThisFileDeclaration = t.variableDeclaration('const', [
+        t.variableDeclarator(isRunningFromThisFileId, includesCall),
+      ]);
+      return { isRunningFromThisFileDeclaration, isRunningFromThisFileId };
     }
 
-    ast.program.body.push(
-      ...getTestStatementForStory({
-        exportName,
-        node,
+    const { isRunningFromThisFileDeclaration, isRunningFromThisFileId } =
+      getTestGuardDeclaration(vitestExpectId);
+
+    ast.program.body.push(isRunningFromThisFileDeclaration);
+
+    const getTestStatementForStory = ({
+      exportName,
+      node,
+    }: {
+      exportName: string;
+      node: t.Node;
+    }) => {
+      // Create the _test expression directly using the exportName identifier
+      const testStoryCall = t.expressionStatement(
+        t.callExpression(vitestTestId, [
+          t.stringLiteral(exportName),
+          t.callExpression(testStoryId, [
+            t.stringLiteral(exportName),
+            t.identifier(exportName),
+            t.identifier(metaExportName),
+            skipTagsId,
+          ]),
+        ])
+      );
+
+      // Preserve sourcemaps location
+      testStoryCall.loc = node.loc;
+
+      // Return just the testStoryCall as composeStoryCall is not needed
+      return testStoryCall;
+    };
+
+    const storyTestStatements = Object.entries(validStories)
+      .map(([exportName, node]) => {
+        if (node === null) {
+          logger.warn(
+            dedent`
+            [Storybook]: Could not transform "${exportName}" story into test at "${fileName}".
+            Please make sure to define stories in the same file and not re-export stories coming from other files".
+          `
+          );
+          return;
+        }
+
+        return getTestStatementForStory({
+          exportName,
+          node,
+        });
       })
-    );
-  });
+      .filter((st) => !!st);
+
+    const testBlock = t.ifStatement(isRunningFromThisFileId, t.blockStatement(storyTestStatements));
 
-  const imports = [
-    t.importDeclaration(
-      [t.importSpecifier(vitestTestId, t.identifier('test'))],
-      t.stringLiteral('vitest')
-    ),
-    t.importDeclaration(
-      [t.importSpecifier(composeStoryId, t.identifier('composeStory'))],
-      t.stringLiteral('storybook/internal/preview-api')
-    ),
-    t.importDeclaration(
-      [
-        t.importSpecifier(testStoryId, t.identifier('testStory')),
-        t.importSpecifier(isValidTestId, t.identifier('isValidTest')),
-      ],
-      t.stringLiteral('@storybook/experimental-addon-vitest/internal/test-utils')
-    ),
-  ];
-
-  ast.program.body.unshift(...imports);
+    ast.program.body.push(testBlock);
+
+    const imports = [
+      t.importDeclaration(
+        [
+          t.importSpecifier(vitestTestId, t.identifier('test')),
+          t.importSpecifier(vitestExpectId, t.identifier('expect')),
+        ],
+        t.stringLiteral('vitest')
+      ),
+      t.importDeclaration(
+        [t.importSpecifier(testStoryId, t.identifier('testStory'))],
+        t.stringLiteral('@storybook/experimental-addon-vitest/internal/test-utils')
+      ),
+    ];
+
+    ast.program.body.unshift(...imports);
+  }
 
   return formatCsf(parsed, { sourceMaps: true, sourceFileName: fileName }, code);
 }

From 518d76b92b6472d7a6ceeb8ac456b2ff6fade9e9 Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Tue, 20 Aug 2024 15:45:57 +0200
Subject: [PATCH 34/38] fix types

---
 code/core/src/csf-tools/vitest-plugin/transformer.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/code/core/src/csf-tools/vitest-plugin/transformer.ts b/code/core/src/csf-tools/vitest-plugin/transformer.ts
index c3753720b17f..53bd84e166de 100644
--- a/code/core/src/csf-tools/vitest-plugin/transformer.ts
+++ b/code/core/src/csf-tools/vitest-plugin/transformer.ts
@@ -239,7 +239,7 @@ export async function vitestTransform({
           node,
         });
       })
-      .filter((st) => !!st);
+      .filter((st) => !!st) as t.ExpressionStatement[];
 
     const testBlock = t.ifStatement(isRunningFromThisFileId, t.blockStatement(storyTestStatements));
 

From 2b4004de37ec3fa74d2713314be3bc75bfae53a7 Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Tue, 20 Aug 2024 16:14:20 +0200
Subject: [PATCH 35/38] fix lint

---
 code/core/src/csf-tools/vitest-plugin/transformer.ts | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/code/core/src/csf-tools/vitest-plugin/transformer.ts b/code/core/src/csf-tools/vitest-plugin/transformer.ts
index 53bd84e166de..d6cf63c8e68d 100644
--- a/code/core/src/csf-tools/vitest-plugin/transformer.ts
+++ b/code/core/src/csf-tools/vitest-plugin/transformer.ts
@@ -95,7 +95,7 @@ export async function vitestTransform({
   }
 
   // Filter out stories based on the passed tags filter
-  let validStories: (typeof parsed)['_storyStatements'] = {};
+  const validStories: (typeof parsed)['_storyStatements'] = {};
   Object.keys(parsed._stories).map((key) => {
     const finalTags = combineTags(
       'test',
@@ -135,7 +135,6 @@ export async function vitestTransform({
     ast.program.body.unshift(...imports);
   } else {
     const vitestExpectId = parsed._file.path.scope.generateUidIdentifier('expect');
-    const composeStoryId = parsed._file.path.scope.generateUidIdentifier('composeStory');
     const testStoryId = parsed._file.path.scope.generateUidIdentifier('testStory');
     const skipTagsId = t.identifier(JSON.stringify(tagsFilter.skip));
 
@@ -147,7 +146,7 @@ export async function vitestTransform({
      * Const isRunningFromThisFile = import.meta.url.includes(expect.getState().testPath ??
      * globalThis.**vitest_worker**.filepath) if(isRunningFromThisFile) { ... }
      */
-    function getTestGuardDeclaration(vitestExpectId: t.Identifier) {
+    function getTestGuardDeclaration() {
       const isRunningFromThisFileId =
         parsed._file.path.scope.generateUidIdentifier('isRunningFromThisFile');
 

From 82b5534c84f20ae9d4bec3726e5cb4e9f2fd7d7d Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Tue, 20 Aug 2024 16:37:27 +0200
Subject: [PATCH 36/38] fix lint

---
 code/core/src/csf-tools/vitest-plugin/transformer.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/code/core/src/csf-tools/vitest-plugin/transformer.ts b/code/core/src/csf-tools/vitest-plugin/transformer.ts
index d6cf63c8e68d..c2da59285f24 100644
--- a/code/core/src/csf-tools/vitest-plugin/transformer.ts
+++ b/code/core/src/csf-tools/vitest-plugin/transformer.ts
@@ -190,7 +190,7 @@ export async function vitestTransform({
     }
 
     const { isRunningFromThisFileDeclaration, isRunningFromThisFileId } =
-      getTestGuardDeclaration(vitestExpectId);
+      getTestGuardDeclaration();
 
     ast.program.body.push(isRunningFromThisFileDeclaration);
 

From fad8af0a6a7fa4faa169694b60af794e1a04f7f1 Mon Sep 17 00:00:00 2001
From: Yann Braga <yannbf@gmail.com>
Date: Tue, 20 Aug 2024 17:49:46 +0200
Subject: [PATCH 37/38] fix lint

---
 code/core/src/csf-tools/vitest-plugin/transformer.ts | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/code/core/src/csf-tools/vitest-plugin/transformer.ts b/code/core/src/csf-tools/vitest-plugin/transformer.ts
index c2da59285f24..98ce9635f4eb 100644
--- a/code/core/src/csf-tools/vitest-plugin/transformer.ts
+++ b/code/core/src/csf-tools/vitest-plugin/transformer.ts
@@ -189,8 +189,7 @@ export async function vitestTransform({
       return { isRunningFromThisFileDeclaration, isRunningFromThisFileId };
     }
 
-    const { isRunningFromThisFileDeclaration, isRunningFromThisFileId } =
-      getTestGuardDeclaration();
+    const { isRunningFromThisFileDeclaration, isRunningFromThisFileId } = getTestGuardDeclaration();
 
     ast.program.body.push(isRunningFromThisFileDeclaration);
 

From 7e83d7e4d71eb901ac3ef704ce6eb6ae0f771d16 Mon Sep 17 00:00:00 2001
From: storybook-bot <32066757+storybook-bot@users.noreply.github.com>
Date: Tue, 20 Aug 2024 16:15:36 +0000
Subject: [PATCH 38/38] Write changelog for 8.3.0-alpha.8 [skip ci]

---
 CHANGELOG.prerelease.md | 10 ++++++++++
 code/package.json       |  3 ++-
 docs/versions/next.json |  2 +-
 3 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md
index 212933922023..dc1270061fa3 100644
--- a/CHANGELOG.prerelease.md
+++ b/CHANGELOG.prerelease.md
@@ -1,3 +1,13 @@
+## 8.3.0-alpha.8
+
+- Addon Vitest: Improve transformation logic to avoid duplicate tests - [#28929](https://github.com/storybookjs/storybook/pull/28929), thanks @yannbf!
+- Addon Vitest: Set default viewport if applicable - [#28905](https://github.com/storybookjs/storybook/pull/28905), thanks @yannbf!
+- Addon-docs: Remove babel dependency - [#28915](https://github.com/storybookjs/storybook/pull/28915), thanks @shilman!
+- Blocks: Fix scroll to non-ascii anchors - [#28826](https://github.com/storybookjs/storybook/pull/28826), thanks @SkReD!
+- Core: Introduce setProjectAnnotations API to more renderers and frameworks - [#28907](https://github.com/storybookjs/storybook/pull/28907), thanks @yannbf!
+- Dependencies: Upgrade `commander` - [#28857](https://github.com/storybookjs/storybook/pull/28857), thanks @43081j!
+- SvelteKit: Introduce portable stories support - [#28918](https://github.com/storybookjs/storybook/pull/28918), thanks @yannbf!
+
 ## 8.3.0-alpha.7
 
 - Addon Vitest: Set screenshotFailures to false by default - [#28908](https://github.com/storybookjs/storybook/pull/28908), thanks @yannbf!
diff --git a/code/package.json b/code/package.json
index 6ffc9cdc3ea7..d17d76eb0998 100644
--- a/code/package.json
+++ b/code/package.json
@@ -292,5 +292,6 @@
         "Dependency Upgrades"
       ]
     ]
-  }
+  },
+  "deferredNextVersion": "8.3.0-alpha.8"
 }
diff --git a/docs/versions/next.json b/docs/versions/next.json
index a47c722e043a..713ecd1b6677 100644
--- a/docs/versions/next.json
+++ b/docs/versions/next.json
@@ -1 +1 @@
-{"version":"8.3.0-alpha.7","info":{"plain":"- Addon Vitest: Set screenshotFailures to false by default - [#28908](https://github.com/storybookjs/storybook/pull/28908), thanks @yannbf!\n- Core: Add Rsbuild frameworks to known frameworks - [#28694](https://github.com/storybookjs/storybook/pull/28694), thanks @fi3ework!\n- Test: Fix support for TS < 4.7 - [#28887](https://github.com/storybookjs/storybook/pull/28887), thanks @ndelangen!\n- Vitest Addon: Fix error message logic in set up file - [#28906](https://github.com/storybookjs/storybook/pull/28906), thanks @yannbf!"}}
+{"version":"8.3.0-alpha.8","info":{"plain":"- Addon Vitest: Improve transformation logic to avoid duplicate tests - [#28929](https://github.com/storybookjs/storybook/pull/28929), thanks @yannbf!\n- Addon Vitest: Set default viewport if applicable - [#28905](https://github.com/storybookjs/storybook/pull/28905), thanks @yannbf!\n- Addon-docs: Remove babel dependency - [#28915](https://github.com/storybookjs/storybook/pull/28915), thanks @shilman!\n- Blocks: Fix scroll to non-ascii anchors - [#28826](https://github.com/storybookjs/storybook/pull/28826), thanks @SkReD!\n- Core: Introduce setProjectAnnotations API to more renderers and frameworks - [#28907](https://github.com/storybookjs/storybook/pull/28907), thanks @yannbf!\n- Dependencies: Upgrade `commander` - [#28857](https://github.com/storybookjs/storybook/pull/28857), thanks @43081j!\n- SvelteKit: Introduce portable stories support - [#28918](https://github.com/storybookjs/storybook/pull/28918), thanks @yannbf!"}}