From af7fb1b8402dd05f73c7161b3909e4f09bbccafb Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 13 Jun 2025 11:47:20 +0200 Subject: [PATCH 01/12] Support Vitest 3.2 --- code/addons/vitest/src/postinstall.ts | 75 +++++----- .../vitest/src/updateVitestFile.test.ts | 129 ++++++++++++++++++ code/addons/vitest/src/updateVitestFile.ts | 25 ++++ .../templates/vitest.config.3.2.template.ts | 30 ++++ docs/_snippets/vitest-plugin-vitest-config.md | 125 +++++++++-------- .../integrations/vitest-addon.mdx | 2 +- 6 files changed, 289 insertions(+), 97 deletions(-) create mode 100644 code/addons/vitest/templates/vitest.config.3.2.template.ts diff --git a/code/addons/vitest/src/postinstall.ts b/code/addons/vitest/src/postinstall.ts index 9e93be4fa550..22939f718a98 100644 --- a/code/addons/vitest/src/postinstall.ts +++ b/code/addons/vitest/src/postinstall.ts @@ -61,6 +61,9 @@ export default async function postInstall(options: PostinstallOptions) { const dependencies = ['vitest', '@vitest/browser', 'playwright'].filter((p) => !allDeps[p]); const vitestVersionSpecifier = await packageManager.getInstalledVersion('vitest'); const coercedVitestVersion = vitestVersionSpecifier ? coerce(vitestVersionSpecifier) : null; + const isVitest3_2OrNewer = vitestVersionSpecifier + ? satisfies(vitestVersionSpecifier, '>=3.2.0') + : true; // if Vitest is installed, we use the same version to keep consistency across Vitest packages const vitestVersionToInstall = vitestVersionSpecifier ?? 'latest'; @@ -402,16 +405,29 @@ export default async function postInstall(options: PostinstallOptions) { let target, updated; const configFile = await fs.readFile(rootConfig, 'utf8'); const hasWorkspaceConfig = configFile.includes('workspace:'); + const hasProjectsConfig = configFile.includes('projects:'); + const configFileHasTypeReference = configFile.match( + /\/\/\/\s*/ + ); + + const templateName = hasWorkspaceConfig + ? 'vitest.config.template.ts' + : hasProjectsConfig || isVitest3_2OrNewer + ? 'vitest.config.3.2.template.ts' + : null; - // For Vitest 3+ with an existing workspace option in the config file, we extend the workspace array, - // otherwise we fall back to creating a workspace file. - if (hasWorkspaceConfig) { - const configTemplate = await loadTemplate('vitest.config.template.ts', { + if (templateName) { + const configTemplate = await loadTemplate(templateName, { CONFIG_DIR: options.configDir, BROWSER_CONFIG: browserConfig, SETUP_FILE: relative(dirname(rootConfig), vitestSetupFile), }); - const source = babelParse(configTemplate); + + const source = babelParse( + configFileHasTypeReference + ? configTemplate + : '/// \n' + configTemplate + ); target = babelParse(configFile); updated = updateConfigFile(source, target); } @@ -424,49 +440,28 @@ export default async function postInstall(options: PostinstallOptions) { const formattedContent = await formatFileContent(rootConfig, generate(target).code); await writeFile(rootConfig, formattedContent); } else { - // Fall back to creating a workspace file if we can't update the config file. - printWarning( - '⚠️ Cannot update config file', + printError( + '🚨 Oh no!', dedent` - Could not update your existing ${vitestConfigFile ? 'Vitest' : 'Vite'} config file: - ${colors.gray(rootConfig)} + We were unable to update your existing ${vitestConfigFile ? 'Vitest' : 'Vite'} config file - Your existing config file cannot be safely updated, so instead a new Vitest - workspace file will be created, extending from your config file. - - Please refer to the Vitest documentation to learn about the workspace file: - ${picocolors.cyan(`https://vitest.dev/guide/workspace.html`)} - ` + Please refer to the documentation to complete the setup manually: + ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/integrations/vitest-addon#manual-setup`)} + ` ); - - const extension = extname(rootConfig).includes('ts') ? '.ts' : '.js'; - const newWorkspaceFile = resolve(dirname(rootConfig), `vitest.workspace${extension}`); - const workspaceTemplate = await loadTemplate('vitest.workspace.template.ts', { - ROOT_CONFIG: relative(dirname(newWorkspaceFile), rootConfig), - EXTENDS_WORKSPACE: viteConfigFile - ? relative(dirname(newWorkspaceFile), viteConfigFile) - : '', - CONFIG_DIR: options.configDir, - BROWSER_CONFIG: browserConfig, - SETUP_FILE: relative(dirname(newWorkspaceFile), vitestSetupFile), - }).then((t) => t.replace(/\s+extends: '',/, '')); - - logger.line(1); - logger.plain(`${step} Creating a Vitest workspace file:`); - logger.plain(colors.gray(` ${newWorkspaceFile}`)); - - const formattedContent = await formatFileContent(newWorkspaceFile, workspaceTemplate); - await writeFile(newWorkspaceFile, formattedContent); } } // If there's no existing Vitest/Vite config, we create a new Vitest config file. else { const newConfigFile = resolve(`vitest.config.${fileExtension}`); - const configTemplate = await loadTemplate('vitest.config.template.ts', { - CONFIG_DIR: options.configDir, - BROWSER_CONFIG: browserConfig, - SETUP_FILE: relative(dirname(newConfigFile), vitestSetupFile), - }); + const configTemplate = await loadTemplate( + isVitest3_2OrNewer ? 'vitest.config.3.2.template.ts' : 'vitest.config.template.ts', + { + CONFIG_DIR: options.configDir, + BROWSER_CONFIG: browserConfig, + SETUP_FILE: relative(dirname(newConfigFile), vitestSetupFile), + } + ); logger.line(1); logger.plain(`${step} Creating a Vitest config file:`); diff --git a/code/addons/vitest/src/updateVitestFile.test.ts b/code/addons/vitest/src/updateVitestFile.test.ts index 0b4f36f85198..27188a8e8ef4 100644 --- a/code/addons/vitest/src/updateVitestFile.test.ts +++ b/code/addons/vitest/src/updateVitestFile.test.ts @@ -158,6 +158,135 @@ describe('updateConfigFile', () => { expect(updated).toBe(false); }); + it('adds projects property to test config', async () => { + const source = babel.babelParse( + await loadTemplate('vitest.config.3.2.template.ts', { + CONFIG_DIR: '.storybook', + BROWSER_CONFIG: "{ provider: 'playwright' }", + SETUP_FILE: '../.storybook/vitest.setup.ts', + }) + ); + const target = babel.babelParse(` + /// + import { defineConfig } from 'vite' + import react from '@vitejs/plugin-react' + + // https://vite.dev/config/ + export default defineConfig({ + plugins: [react()], + test: { + globals: true, + }, + }) + `); + + const updated = updateConfigFile(source, target); + expect(updated).toBe(true); + + const { code } = babel.generate(target); + expect(code).toMatchInlineSnapshot(` + "/// + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + + // https://vite.dev/config/ + import path from 'node:path'; + import { fileURLToPath } from 'node:url'; + import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; + const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url)); + + // More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon + export default defineConfig({ + plugins: [react()], + test: { + globals: true, + projects: [{ + extends: true, + plugins: [ + // The plugin will run tests for the stories defined in your Storybook config + // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest + storybookTest({ + configDir: path.join(dirname, '.storybook') + })], + test: { + name: 'storybook', + browser: { + provider: 'playwright' + }, + setupFiles: ['../.storybook/vitest.setup.ts'] + } + }] + } + });" + `); + }); + + it('edits projects property of test config', async () => { + const source = babel.babelParse( + await loadTemplate('vitest.config.3.2.template.ts', { + CONFIG_DIR: '.storybook', + BROWSER_CONFIG: "{ provider: 'playwright' }", + SETUP_FILE: '../.storybook/vitest.setup.ts', + }) + ); + const target = babel.babelParse(` + /// + import { defineConfig } from 'vite' + import react from '@vitejs/plugin-react' + + // https://vite.dev/config/ + export default defineConfig({ + plugins: [react()], + test: { + globals: true, + projects: ['packages/*', {some: 'config'}] + } + }) + `); + + const updated = updateConfigFile(source, target); + expect(updated).toBe(true); + + const { code } = babel.generate(target); + expect(code).toMatchInlineSnapshot(` + "/// + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + + // https://vite.dev/config/ + import path from 'node:path'; + import { fileURLToPath } from 'node:url'; + import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; + const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url)); + + // More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon + export default defineConfig({ + plugins: [react()], + test: { + globals: true, + projects: ['packages/*', { + some: 'config' + }, { + extends: true, + plugins: [ + // The plugin will run tests for the stories defined in your Storybook config + // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest + storybookTest({ + configDir: path.join(dirname, '.storybook') + })], + test: { + name: 'storybook', + browser: { + provider: 'playwright' + }, + setupFiles: ['../.storybook/vitest.setup.ts'] + } + }] + } + });" + `); + }); + it('adds workspace property to test config', async () => { const source = babel.babelParse( await loadTemplate('vitest.config.template.ts', { diff --git a/code/addons/vitest/src/updateVitestFile.ts b/code/addons/vitest/src/updateVitestFile.ts index cc6f06a26763..44c6ff8ee90a 100644 --- a/code/addons/vitest/src/updateVitestFile.ts +++ b/code/addons/vitest/src/updateVitestFile.ts @@ -50,6 +50,31 @@ const mergeProperties = ( } }; +/** + * Merges a source Vitest configuration AST into a target configuration AST. + * + * This function intelligently combines configuration elements from a source file (typically a + * template) into an existing target configuration file, avoiding duplicates and preserving the + * structure of both files. + * + * The function performs the following operations: + * + * 1. **Import Merging**: Adds new import statements from source that don't exist in target (determined + * by local specifier name). Imports are inserted after existing imports. + * 2. **Variable Declaration Merging**: Copies variable declarations from source to target if they + * don't already exist (determined by variable name). Variables are inserted after imports. + * 3. **Configuration Object Merging**: Merges the configuration object properties from source into + * target's default export. Supports both direct object exports and function-wrapped exports + * (e.g., `defineConfig({})`). The merging is recursive: + * + * - Nested objects are merged deeply + * - Arrays are concatenated (shallow merge) + * - Primitive values are overwritten + * + * @param source - The source Babel AST (template configuration to merge from) + * @param target - The target Babel AST (existing configuration to merge into) + * @returns {boolean} - True if the target was modified, false otherwise + */ export const updateConfigFile = (source: BabelFile['ast'], target: BabelFile['ast']) => { let updated = false; for (const sourceNode of source.program.body) { diff --git a/code/addons/vitest/templates/vitest.config.3.2.template.ts b/code/addons/vitest/templates/vitest.config.3.2.template.ts new file mode 100644 index 000000000000..d261dc2d202a --- /dev/null +++ b/code/addons/vitest/templates/vitest.config.3.2.template.ts @@ -0,0 +1,30 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { defineConfig } from 'vitest/config'; + +import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; + +const dirname = + typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url)); + +// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon +export default defineConfig({ + test: { + projects: [ + { + extends: true, + plugins: [ + // The plugin will run tests for the stories defined in your Storybook config + // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest + storybookTest({ configDir: path.join(dirname, 'CONFIG_DIR') }), + ], + test: { + name: 'storybook', + browser: BROWSER_CONFIG, + setupFiles: ['SETUP_FILE'], + }, + }, + ], + }, +}); diff --git a/docs/_snippets/vitest-plugin-vitest-config.md b/docs/_snippets/vitest-plugin-vitest-config.md index 023fd9d8cefd..cfd91cbb3268 100644 --- a/docs/_snippets/vitest-plugin-vitest-config.md +++ b/docs/_snippets/vitest-plugin-vitest-config.md @@ -12,25 +12,30 @@ import viteConfig from './vite.config'; export default mergeConfig( viteConfig, defineConfig({ - plugins: [ - storybookTest({ - // The location of your Storybook config, main.js|ts - configDir: path.join(dirname, '.storybook'), - // This should match your package.json script to run Storybook - // The --ci flag will skip prompts and not open a browser - storybookScript: 'yarn storybook --ci', - }), - ], test: { - // Enable browser mode - browser: { - enabled: true, - // Make sure to install Playwright - provider: 'playwright', - headless: true, - instances: [{ browser: 'chromium' }], - }, - setupFiles: ['./.storybook/vitest.setup.ts'], + // Use `workspace` field in Vitest < 3.2 + projects: [{ + plugins: [ + storybookTest({ + // The location of your Storybook config, main.js|ts + configDir: path.join(dirname, '.storybook'), + // This should match your package.json script to run Storybook + // The --ci flag will skip prompts and not open a browser + storybookScript: 'yarn storybook --ci', + }) + ], + test: { + // Enable browser mode + browser: { + enabled: true, + // Make sure to install Playwright + provider: 'playwright', + headless: true, + instances: [{ browser: 'chromium' }], + }, + setupFiles: ['./.storybook/vitest.setup.ts'], + }, + }], }, }), ); @@ -39,7 +44,6 @@ export default mergeConfig( ```ts filename="vitest.config.ts" renderer="vue" import { defineConfig, mergeConfig } from 'vitest/config'; import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; -import { storybookVuePlugin } from '@storybook/vue3-vite/vite-plugin'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -51,26 +55,30 @@ const dirname = export default mergeConfig( viteConfig, defineConfig({ - plugins: [ - storybookTest({ - // The location of your Storybook config, main.js|ts - configDir: path.join(dirname, '.storybook'), - // This should match your package.json script to run Storybook - // The --ci flag will skip prompts and not open a browser - storybookScript: 'yarn storybook --ci', - }), - storybookVuePlugin(), - ], test: { - // Enable browser mode - browser: { - enabled: true, - // Make sure to install Playwright - provider: 'playwright', - headless: true, - instances: [{ browser: 'chromium' }], - }, - setupFiles: ['./.storybook/vitest.setup.ts'], + // Use `workspace` field in Vitest < 3.2 + projects: [{ + plugins: [ + storybookTest({ + // The location of your Storybook config, main.js|ts + configDir: path.join(dirname, '.storybook'), + // This should match your package.json script to run Storybook + // The --ci flag will skip prompts and not open a browser + storybookScript: 'yarn storybook --ci', + }) + ], + test: { + // Enable browser mode + browser: { + enabled: true, + // Make sure to install Playwright + provider: 'playwright', + headless: true, + instances: [{ browser: 'chromium' }], + }, + setupFiles: ['./.storybook/vitest.setup.ts'], + }, + }], }, }), ); @@ -90,25 +98,30 @@ import viteConfig from './vite.config'; export default mergeConfig( viteConfig, defineConfig({ - plugins: [ - storybookTest({ - // The location of your Storybook config, main.js|ts - configDir: path.join(dirname, '.storybook'), - // This should match your package.json script to run Storybook - // The --ci flag will skip prompts and not open a browser - storybookScript: 'yarn storybook --ci', - }), - ], test: { - // Enable browser mode - browser: { - enabled: true, - // Make sure to install Playwright - provider: 'playwright', - headless: true, - instances: [{ browser: 'chromium' }], - }, - setupFiles: ['./.storybook/vitest.setup.ts'], + // Use `workspace` field in Vitest < 3.2 + projects: [{ + plugins: [ + storybookTest({ + // The location of your Storybook config, main.js|ts + configDir: path.join(dirname, '.storybook'), + // This should match your package.json script to run Storybook + // The --ci flag will skip prompts and not open a browser + storybookScript: 'yarn storybook --ci', + }) + ], + test: { + // Enable browser mode + browser: { + enabled: true, + // Make sure to install Playwright + provider: 'playwright', + headless: true, + instances: [{ browser: 'chromium' }], + }, + setupFiles: ['./.storybook/vitest.setup.ts'], + }, + }], }, }), ); diff --git a/docs/writing-tests/integrations/vitest-addon.mdx b/docs/writing-tests/integrations/vitest-addon.mdx index 57a399d4971e..5d89f12cd5dc 100644 --- a/docs/writing-tests/integrations/vitest-addon.mdx +++ b/docs/writing-tests/integrations/vitest-addon.mdx @@ -97,7 +97,7 @@ When the addon is set up automatically, it will create or adjust your Vitest con
- Example Vitest workspace file + Example Vitest workspace file (Vitest < 3.2) If you're using a [Vitest workspace](https://vitest.dev/guide/workspace), you can define a new workspace project: From fe724177725a496774076b2ac7b9945ee936cf08 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 13 Jun 2025 12:46:49 +0200 Subject: [PATCH 02/12] Update Vitest 3.2 --- code/addons/vitest/package.json | 6 +- code/yarn.lock | 569 +++++++++++++++++++++++++++++++- 2 files changed, 566 insertions(+), 9 deletions(-) diff --git a/code/addons/vitest/package.json b/code/addons/vitest/package.json index d6a528aab851..94e2c9ba6378 100644 --- a/code/addons/vitest/package.json +++ b/code/addons/vitest/package.json @@ -100,8 +100,8 @@ "@types/micromatch": "^4.0.0", "@types/node": "^22.0.0", "@types/semver": "^7", - "@vitest/browser": "^3.1.1", - "@vitest/runner": "^3.1.1", + "@vitest/browser": "^3.2.0", + "@vitest/runner": "^3.2.0", "boxen": "^8.0.1", "es-toolkit": "^1.36.0", "execa": "^8.0.1", @@ -119,7 +119,7 @@ "tree-kill": "^1.2.2", "ts-dedent": "^2.2.0", "typescript": "^5.8.3", - "vitest": "^3.1.1" + "vitest": "^3.2.0" }, "peerDependencies": { "@vitest/browser": "^3.0.0", diff --git a/code/yarn.lock b/code/yarn.lock index 05396bfbb3d6..ff98e2bdaa48 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5584,6 +5584,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm-eabi@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.43.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@rollup/rollup-android-arm64@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-android-arm64@npm:4.34.8" @@ -5598,6 +5605,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm64@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-android-arm64@npm:4.43.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-arm64@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-darwin-arm64@npm:4.34.8" @@ -5612,6 +5626,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-arm64@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.43.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-x64@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-darwin-x64@npm:4.34.8" @@ -5626,6 +5647,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-x64@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.43.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-freebsd-arm64@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-freebsd-arm64@npm:4.34.8" @@ -5640,6 +5668,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-freebsd-arm64@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.43.0" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-freebsd-x64@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-freebsd-x64@npm:4.34.8" @@ -5654,6 +5689,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-freebsd-x64@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-freebsd-x64@npm:4.43.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-linux-arm-gnueabihf@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.34.8" @@ -5668,6 +5710,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm-gnueabihf@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.43.0" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm-musleabihf@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.34.8" @@ -5682,6 +5731,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm-musleabihf@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.43.0" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-gnu@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.34.8" @@ -5696,6 +5752,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm64-gnu@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.43.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-musl@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-linux-arm64-musl@npm:4.34.8" @@ -5710,6 +5773,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm64-musl@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.43.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-loongarch64-gnu@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.34.8" @@ -5724,6 +5794,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-loongarch64-gnu@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.43.0" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-powerpc64le-gnu@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.34.8" @@ -5738,6 +5815,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.43.0" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-riscv64-gnu@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.34.8" @@ -5752,6 +5836,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-riscv64-gnu@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.43.0" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-riscv64-musl@npm:4.40.1": version: 4.40.1 resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.40.1" @@ -5759,6 +5850,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-riscv64-musl@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.43.0" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-s390x-gnu@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.34.8" @@ -5773,6 +5871,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-s390x-gnu@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.43.0" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-gnu@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-linux-x64-gnu@npm:4.34.8" @@ -5787,6 +5892,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-gnu@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.43.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-musl@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-linux-x64-musl@npm:4.34.8" @@ -5801,6 +5913,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-musl@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.43.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-win32-arm64-msvc@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.34.8" @@ -5815,6 +5934,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-arm64-msvc@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.43.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-win32-ia32-msvc@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.34.8" @@ -5829,6 +5955,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-ia32-msvc@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.43.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@rollup/rollup-win32-x64-msvc@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-win32-x64-msvc@npm:4.34.8" @@ -5843,6 +5976,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-x64-msvc@npm:4.43.0": + version: 4.43.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.43.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rtsao/scc@npm:^1.1.0": version: 1.1.0 resolution: "@rtsao/scc@npm:1.1.0" @@ -6044,8 +6184,8 @@ __metadata: "@types/micromatch": "npm:^4.0.0" "@types/node": "npm:^22.0.0" "@types/semver": "npm:^7" - "@vitest/browser": "npm:^3.1.1" - "@vitest/runner": "npm:^3.1.1" + "@vitest/browser": "npm:^3.2.0" + "@vitest/runner": "npm:^3.2.0" boxen: "npm:^8.0.1" es-toolkit: "npm:^1.36.0" execa: "npm:^8.0.1" @@ -6064,7 +6204,7 @@ __metadata: tree-kill: "npm:^1.2.2" ts-dedent: "npm:^2.2.0" typescript: "npm:^5.8.3" - vitest: "npm:^3.1.1" + vitest: "npm:^3.2.0" peerDependencies: "@vitest/browser": ^3.0.0 "@vitest/runner": ^3.0.0 @@ -7361,6 +7501,15 @@ __metadata: languageName: node linkType: hard +"@types/chai@npm:^5.2.2": + version: 5.2.2 + resolution: "@types/chai@npm:5.2.2" + dependencies: + "@types/deep-eql": "npm:*" + checksum: 10c0/49282bf0e8246800ebb36f17256f97bd3a8c4fb31f92ad3c0eaa7623518d7e87f1eaad4ad206960fcaf7175854bdff4cb167e4fe96811e0081b4ada83dd533ec + languageName: node + linkType: hard + "@types/color-convert@npm:^2.0.0": version: 2.0.4 resolution: "@types/color-convert@npm:2.0.4" @@ -7414,6 +7563,13 @@ __metadata: languageName: node linkType: hard +"@types/deep-eql@npm:*": + version: 4.0.2 + resolution: "@types/deep-eql@npm:4.0.2" + checksum: 10c0/bf3f811843117900d7084b9d0c852da9a044d12eb40e6de73b552598a6843c21291a8a381b0532644574beecd5e3491c5ff3a0365ab86b15d59862c025384844 + languageName: node + linkType: hard + "@types/detect-port@npm:^1.3.0": version: 1.3.5 resolution: "@types/detect-port@npm:1.3.5" @@ -8405,6 +8561,33 @@ __metadata: languageName: node linkType: hard +"@vitest/browser@npm:^3.2.0": + version: 3.2.3 + resolution: "@vitest/browser@npm:3.2.3" + dependencies: + "@testing-library/dom": "npm:^10.4.0" + "@testing-library/user-event": "npm:^14.6.1" + "@vitest/mocker": "npm:3.2.3" + "@vitest/utils": "npm:3.2.3" + magic-string: "npm:^0.30.17" + sirv: "npm:^3.0.1" + tinyrainbow: "npm:^2.0.0" + ws: "npm:^8.18.2" + peerDependencies: + playwright: "*" + vitest: 3.2.3 + webdriverio: ^7.0.0 || ^8.0.0 || ^9.0.0 + peerDependenciesMeta: + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + checksum: 10c0/effc8affe57ab4bfe12cc71dc5506c85b034b018c1fc71fde8591be77755962f979e06aae23b7c80460d93b36f3106ab52611d839d2d45be08450e81fd80c8ee + languageName: node + linkType: hard + "@vitest/coverage-istanbul@npm:^3.1.1": version: 3.1.2 resolution: "@vitest/coverage-istanbul@npm:3.1.2" @@ -8475,6 +8658,19 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:3.2.3": + version: 3.2.3 + resolution: "@vitest/expect@npm:3.2.3" + dependencies: + "@types/chai": "npm:^5.2.2" + "@vitest/spy": "npm:3.2.3" + "@vitest/utils": "npm:3.2.3" + chai: "npm:^5.2.0" + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/5eb6278be8f5294779472d1276e150a1b573274441a68c2681c447179abd22af451813fdfbe87e04f5909ca7a0926700f9b79022f227c9816e5d0fa8e0229e15 + languageName: node + linkType: hard + "@vitest/expect@patch:@vitest/expect@npm%3A3.0.9#~/.yarn/patches/@vitest-expect-npm-3.0.9-e2a2210fb4.patch": version: 3.0.9 resolution: "@vitest/expect@patch:@vitest/expect@npm%3A3.0.9#~/.yarn/patches/@vitest-expect-npm-3.0.9-e2a2210fb4.patch::version=3.0.9&hash=f52714" @@ -8506,6 +8702,25 @@ __metadata: languageName: node linkType: hard +"@vitest/mocker@npm:3.2.3": + version: 3.2.3 + resolution: "@vitest/mocker@npm:3.2.3" + dependencies: + "@vitest/spy": "npm:3.2.3" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.17" + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/b670f229c3b1de5561de3cbbecb18f964d4888355d7f1cb8bbff4350b2cfbe477bef834cc2f66af7727ca7dc567540018885eb652f46e0be1cda4015491dc0a9 + languageName: node + linkType: hard + "@vitest/pretty-format@npm:3.0.9": version: 3.0.9 resolution: "@vitest/pretty-format@npm:3.0.9" @@ -8524,7 +8739,16 @@ __metadata: languageName: node linkType: hard -"@vitest/runner@npm:3.1.2, @vitest/runner@npm:^3.1.1": +"@vitest/pretty-format@npm:3.2.3, @vitest/pretty-format@npm:^3.2.3": + version: 3.2.3 + resolution: "@vitest/pretty-format@npm:3.2.3" + dependencies: + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/e8fa7b97822c58404bef07d19fa9a49d5b7edb6797dd355584ad7246585bbbe9c55dd1fb05d0c3939b9c15fba05c3e134e2b96ea0cb64ca79a2b9dab60087a6a + languageName: node + linkType: hard + +"@vitest/runner@npm:3.1.2": version: 3.1.2 resolution: "@vitest/runner@npm:3.1.2" dependencies: @@ -8534,6 +8758,17 @@ __metadata: languageName: node linkType: hard +"@vitest/runner@npm:3.2.3, @vitest/runner@npm:^3.2.0": + version: 3.2.3 + resolution: "@vitest/runner@npm:3.2.3" + dependencies: + "@vitest/utils": "npm:3.2.3" + pathe: "npm:^2.0.3" + strip-literal: "npm:^3.0.0" + checksum: 10c0/c20cb6e2ac4fdfb3d4f5136714ea65f9063562d3afaa1574dc82f53d061444bc01583f9915346768ca75f5ea0658f02fb594752e21abbca5ab50290f58732147 + languageName: node + linkType: hard + "@vitest/snapshot@npm:3.1.2": version: 3.1.2 resolution: "@vitest/snapshot@npm:3.1.2" @@ -8545,6 +8780,17 @@ __metadata: languageName: node linkType: hard +"@vitest/snapshot@npm:3.2.3": + version: 3.2.3 + resolution: "@vitest/snapshot@npm:3.2.3" + dependencies: + "@vitest/pretty-format": "npm:3.2.3" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + checksum: 10c0/f6dd0248afb3f3cbcbbb9fd39c2c8273c4ec92176f65e6ba9d36a0c33552d3658013e3a02944e14c7637f51d6702a5c07963b59707ca459bd1ac31f39c81160c + languageName: node + linkType: hard + "@vitest/spy@npm:3.0.9": version: 3.0.9 resolution: "@vitest/spy@npm:3.0.9" @@ -8563,6 +8809,15 @@ __metadata: languageName: node linkType: hard +"@vitest/spy@npm:3.2.3": + version: 3.2.3 + resolution: "@vitest/spy@npm:3.2.3" + dependencies: + tinyspy: "npm:^4.0.3" + checksum: 10c0/ce77d5934ac4741513993aad9d8ff44ff03ff5cf5a177e010c7ffcd8d3060087e56df1938c1100d49de712daf952cd2c72dd83e1684d043e698bd2afe0025f5e + languageName: node + linkType: hard + "@vitest/utils@npm:3.0.9": version: 3.0.9 resolution: "@vitest/utils@npm:3.0.9" @@ -8585,6 +8840,17 @@ __metadata: languageName: node linkType: hard +"@vitest/utils@npm:3.2.3": + version: 3.2.3 + resolution: "@vitest/utils@npm:3.2.3" + dependencies: + "@vitest/pretty-format": "npm:3.2.3" + loupe: "npm:^3.1.3" + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/c7a785a73bc0d7c0202ced0d9912639b9deb6f05dd6c25700a13d97e13320ccec57660f11ad1f9225419ac485339fdf7af28c8d77456bcb9558e6c7d73ad538a + languageName: node + linkType: hard + "@volar/language-core@npm:2.4.13, @volar/language-core@npm:~2.4.11": version: 2.4.13 resolution: "@volar/language-core@npm:2.4.13" @@ -12103,6 +12369,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:^4.4.1": + version: 4.4.1 + resolution: "debug@npm:4.4.1" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d2b44bc1afd912b49bb7ebb0d50a860dc93a4dd7d946e8de94abc957bb63726b7dd5aa48c18c2386c379ec024c46692e15ed3ed97d481729f929201e671fcd55 + languageName: node + linkType: hard + "decamelize@npm:^1.2.0": version: 1.2.0 resolution: "decamelize@npm:1.2.0" @@ -13266,7 +13544,7 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^1.2.1, es-module-lexer@npm:^1.5.0, es-module-lexer@npm:^1.5.4, es-module-lexer@npm:^1.6.0": +"es-module-lexer@npm:^1.2.1, es-module-lexer@npm:^1.5.0, es-module-lexer@npm:^1.5.4, es-module-lexer@npm:^1.6.0, es-module-lexer@npm:^1.7.0": version: 1.7.0 resolution: "es-module-lexer@npm:1.7.0" checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b @@ -14602,6 +14880,18 @@ __metadata: languageName: node linkType: hard +"fdir@npm:^6.4.5": + version: 6.4.6 + resolution: "fdir@npm:6.4.6" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/45b559cff889934ebb8bc498351e5acba40750ada7e7d6bde197768d2fa67c149be8ae7f8ff34d03f4e1eb20f2764116e56440aaa2f6689e9a4aa7ef06acafe9 + languageName: node + linkType: hard + "fetch-retry@npm:^6.0.0": version: 6.0.0 resolution: "fetch-retry@npm:6.0.0" @@ -17481,6 +17771,13 @@ __metadata: languageName: node linkType: hard +"js-tokens@npm:^9.0.1": + version: 9.0.1 + resolution: "js-tokens@npm:9.0.1" + checksum: 10c0/68dcab8f233dde211a6b5fd98079783cbcd04b53617c1250e3553ee16ab3e6134f5e65478e41d82f6d351a052a63d71024553933808570f04dbf828d7921e80e + languageName: node + linkType: hard + "js-yaml@npm:^3.10.0, js-yaml@npm:^3.13.1": version: 3.14.1 resolution: "js-yaml@npm:3.14.1" @@ -19663,7 +19960,7 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.6, nanoid@npm:^3.3.8": +"nanoid@npm:^3.3.11, nanoid@npm:^3.3.6, nanoid@npm:^3.3.8": version: 3.3.11 resolution: "nanoid@npm:3.3.11" bin: @@ -21516,6 +21813,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.5.4": + version: 8.5.5 + resolution: "postcss@npm:8.5.5" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/6415873fab84de05c2d8fd18f72ea6654bca437bb4b9f02ca819c438501e4b3a450023e575e17587c6eaa5bedddaaa4dad3af210f5cf166e30cec09cac58baf8 + languageName: node + linkType: hard + "preact@npm:^10.5.13": version: 10.26.5 resolution: "preact@npm:10.26.5" @@ -23404,6 +23712,81 @@ __metadata: languageName: node linkType: hard +"rollup@npm:^4.40.0": + version: 4.43.0 + resolution: "rollup@npm:4.43.0" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.43.0" + "@rollup/rollup-android-arm64": "npm:4.43.0" + "@rollup/rollup-darwin-arm64": "npm:4.43.0" + "@rollup/rollup-darwin-x64": "npm:4.43.0" + "@rollup/rollup-freebsd-arm64": "npm:4.43.0" + "@rollup/rollup-freebsd-x64": "npm:4.43.0" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.43.0" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.43.0" + "@rollup/rollup-linux-arm64-gnu": "npm:4.43.0" + "@rollup/rollup-linux-arm64-musl": "npm:4.43.0" + "@rollup/rollup-linux-loongarch64-gnu": "npm:4.43.0" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.43.0" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.43.0" + "@rollup/rollup-linux-riscv64-musl": "npm:4.43.0" + "@rollup/rollup-linux-s390x-gnu": "npm:4.43.0" + "@rollup/rollup-linux-x64-gnu": "npm:4.43.0" + "@rollup/rollup-linux-x64-musl": "npm:4.43.0" + "@rollup/rollup-win32-arm64-msvc": "npm:4.43.0" + "@rollup/rollup-win32-ia32-msvc": "npm:4.43.0" + "@rollup/rollup-win32-x64-msvc": "npm:4.43.0" + "@types/estree": "npm:1.0.7" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loongarch64-gnu": + optional: true + "@rollup/rollup-linux-powerpc64le-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/a14a16ee5433f9eddfe803ed1a3f4528e3e96f746e55bf88c5482f9a60a4ad61f507b59f46d5d9c8dc98bb7983483e0c94b760ae37c02157eba9da5665c1641b + languageName: node + linkType: hard + "rsvp@npm:^3.0.14, rsvp@npm:^3.0.18": version: 3.6.2 resolution: "rsvp@npm:3.6.2" @@ -24888,6 +25271,15 @@ __metadata: languageName: node linkType: hard +"strip-literal@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-literal@npm:3.0.0" + dependencies: + js-tokens: "npm:^9.0.1" + checksum: 10c0/d81657f84aba42d4bbaf2a677f7e7f34c1f3de5a6726db8bc1797f9c0b303ba54d4660383a74bde43df401cf37cce1dff2c842c55b077a4ceee11f9e31fba828 + languageName: node + linkType: hard + "stubs@npm:^3.0.0": version: 3.0.0 resolution: "stubs@npm:3.0.0" @@ -25346,6 +25738,16 @@ __metadata: languageName: node linkType: hard +"tinyglobby@npm:^0.2.14": + version: 0.2.14 + resolution: "tinyglobby@npm:0.2.14" + dependencies: + fdir: "npm:^6.4.4" + picomatch: "npm:^4.0.2" + checksum: 10c0/f789ed6c924287a9b7d3612056ed0cda67306cd2c80c249fd280cf1504742b12583a2089b61f4abbd24605f390809017240e250241f09938054c9b363e51c0a6 + languageName: node + linkType: hard + "tinypool@npm:^1.0.2": version: 1.0.2 resolution: "tinypool@npm:1.0.2" @@ -25353,6 +25755,13 @@ __metadata: languageName: node linkType: hard +"tinypool@npm:^1.1.0": + version: 1.1.0 + resolution: "tinypool@npm:1.1.0" + checksum: 10c0/deb6bde5e3d85d4ba043806c66f43fb5b649716312a47b52761a83668ffc71cd0ea4e24254c1b02a3702e5c27e02605f0189a1460f6284a5930a08bd0c06435c + languageName: node + linkType: hard + "tinyrainbow@npm:^2.0.0": version: 2.0.0 resolution: "tinyrainbow@npm:2.0.0" @@ -25367,6 +25776,13 @@ __metadata: languageName: node linkType: hard +"tinyspy@npm:^4.0.3": + version: 4.0.3 + resolution: "tinyspy@npm:4.0.3" + checksum: 10c0/0a92a18b5350945cc8a1da3a22c9ad9f4e2945df80aaa0c43e1b3a3cfb64d8501e607ebf0305e048e3c3d3e0e7f8eb10cea27dc17c21effb73e66c4a3be36373 + languageName: node + linkType: hard + "tmp@npm:0.0.28": version: 0.0.28 resolution: "tmp@npm:0.0.28" @@ -26448,6 +26864,21 @@ __metadata: languageName: node linkType: hard +"vite-node@npm:3.2.3": + version: 3.2.3 + resolution: "vite-node@npm:3.2.3" + dependencies: + cac: "npm:^6.7.14" + debug: "npm:^4.4.1" + es-module-lexer: "npm:^1.7.0" + pathe: "npm:^2.0.3" + vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0-0" + bin: + vite-node: vite-node.mjs + checksum: 10c0/b952b0d9e45662506ea7303ac87d08e02f1e3355777cf7d426f211292c4f87e8837aef589e552bb11404d1bc0a9bd18871ce6ba874b5f0bb171f8e010de20a11 + languageName: node + linkType: hard + "vite-plugin-babel@npm:^1.3.0": version: 1.3.0 resolution: "vite-plugin-babel@npm:1.3.0" @@ -26589,6 +27020,61 @@ __metadata: languageName: node linkType: hard +"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0": + version: 7.0.0-beta.1 + resolution: "vite@npm:7.0.0-beta.1" + dependencies: + esbuild: "npm:^0.25.0" + fdir: "npm:^6.4.5" + fsevents: "npm:~2.3.3" + picomatch: "npm:^4.0.2" + postcss: "npm:^8.5.4" + rollup: "npm:^4.40.0" + tinyglobby: "npm:^0.2.14" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/0ab20103182244b310fb2bb4a9f3fb21110a61328052e73accfebf3503270bddd1d19417c2d17bd93f1108125da5ffbc52a1f05c8bf3f564907919ffc64d3d78 + languageName: node + linkType: hard + "vite@npm:^5.0.0 || ^6.0.0, vite@npm:^6.2.5": version: 6.3.3 resolution: "vite@npm:6.3.3" @@ -26726,6 +27212,62 @@ __metadata: languageName: node linkType: hard +"vitest@npm:^3.2.0": + version: 3.2.3 + resolution: "vitest@npm:3.2.3" + dependencies: + "@types/chai": "npm:^5.2.2" + "@vitest/expect": "npm:3.2.3" + "@vitest/mocker": "npm:3.2.3" + "@vitest/pretty-format": "npm:^3.2.3" + "@vitest/runner": "npm:3.2.3" + "@vitest/snapshot": "npm:3.2.3" + "@vitest/spy": "npm:3.2.3" + "@vitest/utils": "npm:3.2.3" + chai: "npm:^5.2.0" + debug: "npm:^4.4.1" + expect-type: "npm:^1.2.1" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.2" + std-env: "npm:^3.9.0" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^0.3.2" + tinyglobby: "npm:^0.2.14" + tinypool: "npm:^1.1.0" + tinyrainbow: "npm:^2.0.0" + vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0-0" + vite-node: "npm:3.2.3" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/debug": ^4.1.12 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@vitest/browser": 3.2.3 + "@vitest/ui": 3.2.3 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/debug": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 10c0/1d853016622f32020e91cc72348d0dc642bde2ddcbd648655a9d33d420375c7cbd6f1a6f5c4398a5d4f59b8c2b120e62eba49fb37f8042e5d4c688b7e60148ef + languageName: node + linkType: hard + "vlq@npm:^0.2.1": version: 0.2.3 resolution: "vlq@npm:0.2.3" @@ -27550,6 +28092,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.18.2": + version: 8.18.2 + resolution: "ws@npm:8.18.2" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/4b50f67931b8c6943c893f59c524f0e4905bbd183016cfb0f2b8653aa7f28dad4e456b9d99d285bbb67cca4fedd9ce90dfdfaa82b898a11414ebd66ee99141e4 + languageName: node + linkType: hard + "xcase@npm:^2.0.1": version: 2.0.1 resolution: "xcase@npm:2.0.1" From b9a0ceb9b39134922aabbbce5bc8df4c0c750a15 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 13 Jun 2025 16:03:49 +0200 Subject: [PATCH 03/12] Refactor post-install script for improved error handling and logging --- code/addons/vitest/src/postinstall.ts | 131 +++++++++++++++----------- 1 file changed, 74 insertions(+), 57 deletions(-) diff --git a/code/addons/vitest/src/postinstall.ts b/code/addons/vitest/src/postinstall.ts index 8f5132257458..525ba6960514 100644 --- a/code/addons/vitest/src/postinstall.ts +++ b/code/addons/vitest/src/postinstall.ts @@ -14,16 +14,14 @@ import { serverResolve, transformImportFiles, validateFrameworkName, - versions, } from 'storybook/internal/common'; import { readConfig, writeConfig } from 'storybook/internal/csf-tools'; -import { colors, logger } from 'storybook/internal/node-logger'; +import { logger } from 'storybook/internal/node-logger'; // eslint-disable-next-line depend/ban-dependencies import { execa } from 'execa'; import { findUp } from 'find-up'; -import { dirname, extname, join, relative, resolve } from 'pathe'; -import picocolors from 'picocolors'; +import { dirname, join, relative, resolve } from 'pathe'; import prompts from 'prompts'; import { coerce, satisfies } from 'semver'; import { dedent } from 'ts-dedent'; @@ -39,6 +37,13 @@ const EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.cts', '.mts', '.cjs', '.mjs' const addonA11yName = '@storybook/addon-a11y'; +let hasErrors = false; + +const logErrors = (...args: Parameters) => { + hasErrors = true; + printError(...args); +}; + const findFile = async (basename: string, extensions = EXTENSIONS) => findUp( extensions.map((ext) => basename + ext), @@ -49,7 +54,7 @@ export default async function postInstall(options: PostinstallOptions) { printSuccess( '👋 Howdy!', dedent` - I'm the installation helper for ${colors.pink(ADDON_NAME)} + I'm the installation helper for ${ADDON_NAME} Hold on for a moment while I look at your project and get it set up... ` @@ -151,13 +156,13 @@ export default async function postInstall(options: PostinstallOptions) { if (!isRendererSupported) { reasons.push(dedent` - • The addon cannot yet be used with ${picocolors.bold(colors.pink(info.frameworkPackageName))} + • The addon cannot yet be used with ${info.frameworkPackageName} `); } if (coercedVitestVersion && !satisfies(coercedVitestVersion, '>=3.0.0')) { reasons.push(dedent` - • The addon requires Vitest 3.0.0 or higher. You are currently using ${picocolors.bold(vitestVersionSpecifier)}. + • The addon requires Vitest 3.0.0 or higher. You are currently using ${vitestVersionSpecifier}. Please update all of your Vitest dependencies and try again. `); } @@ -167,7 +172,7 @@ export default async function postInstall(options: PostinstallOptions) { if (coercedMswVersion && !satisfies(coercedMswVersion, '>=2.0.0')) { reasons.push(dedent` - • The addon uses Vitest behind the scenes, which supports only version 2 and above of MSW. However, we have detected version ${picocolors.bold(coercedMswVersion.version)} in this project. + • The addon uses Vitest behind the scenes, which supports only version 2 and above of MSW. However, we have detected version ${coercedMswVersion.version} in this project. Please update the 'msw' package and try again. `); } @@ -176,7 +181,7 @@ export default async function postInstall(options: PostinstallOptions) { const nextVersion = await packageManager.getInstalledVersion('next'); if (!nextVersion) { reasons.push(dedent` - • You are using ${picocolors.bold(colors.pink('@storybook/nextjs'))} without having ${picocolors.bold(colors.pink('next'))} installed. + • You are using @storybook/nextjs without having "next" installed. Please install "next" or use a different Storybook framework integration and try again. `); } @@ -189,7 +194,7 @@ export default async function postInstall(options: PostinstallOptions) { reasons.push('--------------------------------'); reasons.push( dedent` - You can fix these issues and rerun the command to reinstall. If you wish to roll back the installation, remove ${picocolors.bold(colors.pink(ADDON_NAME))} from the "addons" array + You can fix these issues and rerun the command to reinstall. If you wish to roll back the installation, remove ${ADDON_NAME} from the "addons" array in your main Storybook config file and remove the dependency from your package.json file. ` ); @@ -198,14 +203,14 @@ export default async function postInstall(options: PostinstallOptions) { reasons.push( dedent` Please check the documentation for more information about its requirements and installation: - ${picocolors.cyan(`https://storybook.js.org/docs/next/${DOCUMENTATION_LINK}`)} + https://storybook.js.org/docs/next/${DOCUMENTATION_LINK} ` ); } else { reasons.push( dedent` Fear not, however, you can follow the manual installation process instead at: - ${picocolors.cyan(`https://storybook.js.org/docs/next/${DOCUMENTATION_LINK}#manual-setup`)} + https://storybook.js.org/docs/next/${DOCUMENTATION_LINK}#manual-setup ` ); } @@ -219,7 +224,7 @@ export default async function postInstall(options: PostinstallOptions) { const result = await prerequisiteCheck(); if (result) { - printError('⛔️ Sorry!', result); + logErrors('⛔️ Sorry!', result); logger.line(1); return; } @@ -230,9 +235,9 @@ export default async function postInstall(options: PostinstallOptions) { dedent` It looks like you're using Next.js. - Adding ${picocolors.bold(colors.pink(`@storybook/nextjs-vite/vite-plugin`))} so you can use it with Vitest. + Adding "@storybook/nextjs-vite/vite-plugin" so you can use it with Vitest. - More info about the plugin at ${picocolors.cyan(`https://github.com/storybookjs/vite-plugin-storybook-nextjs`)} + More info about the plugin at https://github.com/storybookjs/vite-plugin-storybook-nextjs ` ); try { @@ -251,8 +256,8 @@ export default async function postInstall(options: PostinstallOptions) { dedent` You don't seem to have a coverage reporter installed. Vitest needs either V8 or Istanbul to generate coverage reports. - Adding ${picocolors.bold(colors.pink(`@vitest/coverage-v8`))} to enable coverage reporting. - Read more about Vitest coverage providers at ${picocolors.cyan(`https://vitest.dev/guide/coverage.html#coverage-providers`)} + Adding "@vitest/coverage-v8" to enable coverage reporting. + Read more about Vitest coverage providers at https://vitest.dev/guide/coverage.html#coverage-providers ` ); dependencies.push(`@vitest/coverage-v8`); // Version specifier is added below @@ -273,7 +278,7 @@ export default async function postInstall(options: PostinstallOptions) { ); logger.line(1); logger.plain(`${step} Installing dependencies:`); - logger.plain(colors.gray(' ' + versionedDependencies.join(', '))); + logger.plain(' ' + versionedDependencies.join(', ')); } await packageManager.installDependencies(); @@ -282,10 +287,10 @@ export default async function postInstall(options: PostinstallOptions) { if (options.skipInstall) { logger.plain('Skipping Playwright installation, please run this command manually:'); - logger.plain(colors.gray(' npx playwright install chromium --with-deps')); + logger.plain(' npx playwright install chromium --with-deps'); } else { logger.plain(`${step} Configuring Playwright with Chromium (this might take some time):`); - logger.plain(colors.gray(' npx playwright install chromium --with-deps')); + logger.plain(' npx playwright install chromium --with-deps'); await packageManager.executeCommand({ command: 'npx', args: ['playwright', 'install', 'chromium', '--with-deps'], @@ -297,14 +302,14 @@ export default async function postInstall(options: PostinstallOptions) { const vitestSetupFile = resolve(options.configDir, `vitest.setup.${fileExtension}`); if (existsSync(vitestSetupFile)) { - printError( + logErrors( '🚨 Oh no!', dedent` Found an existing Vitest setup file: - ${colors.gray(vitestSetupFile)} + ${vitestSetupFile} Please refer to the documentation to complete the setup manually: - ${picocolors.cyan(`https://storybook.js.org/docs/next/${DOCUMENTATION_LINK}#manual-setup`)} + https://storybook.js.org/docs/next/${DOCUMENTATION_LINK}#manual-setup ` ); logger.line(1); @@ -313,7 +318,7 @@ export default async function postInstall(options: PostinstallOptions) { logger.line(1); logger.plain(`${step} Creating a Vitest setup file for Storybook:`); - logger.plain(colors.gray(` ${vitestSetupFile}`)); + logger.plain(` ${vitestSetupFile}`); const previewExists = EXTENSIONS.map((ext) => resolve(options.configDir, `preview${ext}`)).some( existsSync @@ -380,22 +385,22 @@ export default async function postInstall(options: PostinstallOptions) { if (updated) { logger.line(1); logger.plain(`${step} Updating your Vitest workspace file:`); - logger.plain(colors.gray(` ${vitestWorkspaceFile}`)); + logger.plain(` ${vitestWorkspaceFile}`); const formattedContent = await formatFileContent(vitestWorkspaceFile, generate(target).code); await writeFile(vitestWorkspaceFile, formattedContent); } else { - printError( + logErrors( '🚨 Oh no!', dedent` Could not update existing Vitest workspace file: - ${colors.gray(vitestWorkspaceFile)} + ${vitestWorkspaceFile} I was able to configure most of the addon but could not safely extend your existing workspace file automatically, you must do it yourself. Please refer to the documentation to complete the setup manually: - ${picocolors.cyan(`https://storybook.js.org/docs/next/${DOCUMENTATION_LINK}#manual-setup`)} + https://storybook.js.org/docs/next/${DOCUMENTATION_LINK}#manual-setup ` ); logger.line(1); @@ -406,17 +411,15 @@ export default async function postInstall(options: PostinstallOptions) { else if (rootConfig) { let target, updated; const configFile = await fs.readFile(rootConfig, 'utf8'); - const hasWorkspaceConfig = configFile.includes('workspace:'); const hasProjectsConfig = configFile.includes('projects:'); const configFileHasTypeReference = configFile.match( /\/\/\/\s*/ ); - const templateName = hasWorkspaceConfig - ? 'vitest.config.template.ts' - : hasProjectsConfig || isVitest3_2OrNewer + const templateName = + hasProjectsConfig || isVitest3_2OrNewer ? 'vitest.config.3.2.template.ts' - : null; + : 'vitest.config.template.ts'; if (templateName) { const configTemplate = await loadTemplate(templateName, { @@ -425,11 +428,7 @@ export default async function postInstall(options: PostinstallOptions) { SETUP_FILE: relative(dirname(rootConfig), vitestSetupFile), }); - const source = babelParse( - configFileHasTypeReference - ? configTemplate - : '/// \n' + configTemplate - ); + const source = babelParse(configTemplate); target = babelParse(configFile); updated = updateConfigFile(source, target); } @@ -437,18 +436,23 @@ export default async function postInstall(options: PostinstallOptions) { if (target && updated) { logger.line(1); logger.plain(`${step} Updating your ${vitestConfigFile ? 'Vitest' : 'Vite'} config file:`); - logger.plain(colors.gray(` ${rootConfig}`)); + logger.plain(` ${rootConfig}`); const formattedContent = await formatFileContent(rootConfig, generate(target).code); - await writeFile(rootConfig, formattedContent); + await writeFile( + rootConfig, + configFileHasTypeReference + ? formattedContent + : '/// \n' + formattedContent + ); } else { - printError( + logErrors( '🚨 Oh no!', dedent` - We were unable to update your existing ${vitestConfigFile ? 'Vitest' : 'Vite'} config file + We were unable to update your existing ${vitestConfigFile ? 'Vitest' : 'Vite'} config file. Please refer to the documentation to complete the setup manually: - ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/integrations/vitest-addon#manual-setup`)} + https://storybook.js.org/docs/writing-tests/integrations/vitest-addon#manual-setup ` ); } @@ -467,7 +471,7 @@ export default async function postInstall(options: PostinstallOptions) { logger.line(1); logger.plain(`${step} Creating a Vitest config file:`); - logger.plain(colors.gray(` ${newConfigFile}`)); + logger.plain(` ${newConfigFile}`); const formattedContent = await formatFileContent(newConfigFile, configTemplate); await writeFile(newConfigFile, formattedContent); @@ -499,7 +503,7 @@ export default async function postInstall(options: PostinstallOptions) { stdio: 'inherit', }); } catch (e: unknown) { - printError( + logErrors( '🚨 Oh no!', dedent` We have detected that you have ${addonA11yName} installed but could not automatically set it up for @storybook/addon-vitest: @@ -507,7 +511,7 @@ export default async function postInstall(options: PostinstallOptions) { ${e instanceof Error ? e.message : String(e)} Please refer to the documentation to complete the setup manually: - ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration`)} + https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration ` ); } @@ -515,19 +519,32 @@ export default async function postInstall(options: PostinstallOptions) { const runCommand = rootConfig ? `npx vitest --project=storybook` : `npx vitest`; - printSuccess( - '🎉 All done!', - dedent` - @storybook/addon-vitest is now configured and you're ready to run your tests! + if (!hasErrors) { + printSuccess( + '🎉 All done!', + dedent` + @storybook/addon-vitest is now configured and you're ready to run your tests! + + Here are a couple of tips to get you started: + • You can run tests with "${runCommand}" + • When using the Vitest extension in your editor, all of your stories will be shown as tests! + + Check the documentation for more information about its features and options at: + https://storybook.js.org/docs/next/${DOCUMENTATION_LINK} + ` + ); + } else { + printWarning( + '⚠️ Done, but with errors!', + dedent` + @storybook/addon-vitest was installed successfully, but there were some errors during the setup process. - Here are a couple of tips to get you started: - • You can run tests with ${colors.gray(runCommand)} - • When using the Vitest extension in your editor, all of your stories will be shown as tests! + Please refer to the documentation to complete the setup manually and check the errors above: + https://storybook.js.org/docs/next/${DOCUMENTATION_LINK}#manual-setup + ` + ); + } - Check the documentation for more information about its features and options at: - ${picocolors.cyan(`https://storybook.js.org/docs/next/${DOCUMENTATION_LINK}`)} - ` - ); logger.line(1); } From 22d796f57e111aed900e750363a39ed80440e7c5 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 16 Jun 2025 08:21:42 +0200 Subject: [PATCH 04/12] Update optimizeDeps.ts to include additional Storybook addons and fix dependency order --- code/builders/builder-vite/src/optimizeDeps.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/code/builders/builder-vite/src/optimizeDeps.ts b/code/builders/builder-vite/src/optimizeDeps.ts index 9d4d782321aa..585b09996ce7 100644 --- a/code/builders/builder-vite/src/optimizeDeps.ts +++ b/code/builders/builder-vite/src/optimizeDeps.ts @@ -17,15 +17,15 @@ const INCLUDE_CANDIDATES = [ '@jridgewell/sourcemap-codec', '@storybook/addon-a11y/preview', '@storybook/addon-designs/blocks', + '@storybook/addon-docs/blocks', '@storybook/addon-docs/preview', '@storybook/addon-links/preview', '@storybook/addon-themes', '@storybook/addon-themes/preview', - '@storybook/addon-docs/blocks', - '@storybook/nextjs-vite/dist/preview.mjs', '@storybook/html', '@storybook/html/dist/entry-preview-docs.mjs', '@storybook/html/dist/entry-preview.mjs', + '@storybook/nextjs-vite/dist/preview.mjs', '@storybook/preact', '@storybook/preact/dist/entry-preview-docs.mjs', '@storybook/preact/dist/entry-preview.mjs', @@ -43,7 +43,6 @@ const INCLUDE_CANDIDATES = [ '@storybook/web-components', '@storybook/web-components/dist/entry-preview-docs.mjs', '@storybook/web-components/dist/entry-preview.mjs', - 'storybook/viewport', 'acorn-jsx', 'acorn-walk', 'acorn', @@ -118,8 +117,8 @@ const INCLUDE_CANDIDATES = [ 'react-is', 'react-textarea-autosize', 'react', - 'react/jsx-runtime', 'react/jsx-dev-runtime', + 'react/jsx-runtime', 'refractor/core', 'refractor/lang/bash.js', 'refractor/lang/css.js', @@ -133,13 +132,18 @@ const INCLUDE_CANDIDATES = [ 'refractor/lang/typescript.js', 'refractor/lang/yaml.js', 'regenerator-runtime/runtime.js', - 'semver', // TODO: Remove once https://github.com/npm/node-semver/issues/712 is fixed 'sb-original/default-loader', 'sb-original/image-context', + 'semver', // TODO: Remove once https://github.com/npm/node-semver/issues/712 is fixed 'slash', 'store2', - 'storybook/internal/preview/runtime', + 'storybook/actions', + 'storybook/actions/decorator', + 'storybook/internal/core-events', 'storybook/internal/csf', + 'storybook/internal/preview/runtime', + 'storybook/preview-api', + 'storybook/viewport', 'synchronous-promise', 'telejson', 'ts-dedent', From da0e6f435bca90680e909c14f77a6dd7a02abdeb Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 16 Jun 2025 08:22:34 +0200 Subject: [PATCH 05/12] Simplify vitest sandbox generation --- scripts/tasks/sandbox-parts.ts | 173 +++++---------------------------- 1 file changed, 27 insertions(+), 146 deletions(-) diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 094ed3dcf477..b6b78766b987 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -13,6 +13,7 @@ import { writeFile, writeJson, } from 'fs-extra'; +import { readFile } from 'fs/promises'; import JSON5 from 'json5'; import { createRequire } from 'module'; import { join, relative, resolve, sep } from 'path'; @@ -386,7 +387,6 @@ export async function setupVitest(details: TemplateDetails, options: PassedOptio await writeJson(packageJsonPath, packageJson, { spaces: 2 }); const isVue = template.expected.renderer === '@storybook/vue3'; - const isNextjs = template.expected.framework.includes('nextjs'); // const isAngular = template.expected.framework === '@storybook/angular'; const portableStoriesFrameworks = [ @@ -439,152 +439,33 @@ export async function setupVitest(details: TemplateDetails, options: PassedOptio const vitestConfigFile = await findFirstPath(['vitest.config.ts', 'vitest.config.js'], opts); const workspaceFile = await findFirstPath(['vitest.workspace.ts', 'vitest.workspace.js'], opts); - if (workspaceFile) { - await writeFile( - join(sandboxDir, workspaceFile), - dedent` - import path from 'node:path'; - import { fileURLToPath } from 'node:url'; - import { defineWorkspace, defaultExclude } from "vitest/config"; - import { storybookTest } from "@storybook/addon-vitest/vitest-plugin"; - - ${viteConfigFile ? `import viteConfig from './${viteConfigFile}';` : ''} - - const dirname = - typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url)); - - export default defineWorkspace([ - { - ${!isNextjs ? `extends: "${viteConfigFile}",` : ''} - plugins: [ - storybookTest({ - configDir: path.join(dirname, '.storybook'), - storybookScript: "yarn storybook --ci", - tags: { - include: ["vitest"], - }, - }), - ], - ${ - isNextjs - ? `optimizeDeps: { - include: [ - "next/image", - "next/legacy/image", - "next/dist/compiled/react", - "sb-original/default-loader", - "sb-original/image-context", - ], - },` - : '' - } - resolve: { - preserveSymlinks: true, - }, - test: { - name: "storybook", - pool: "threads", - exclude: [ - ...defaultExclude, - // TODO: investigate TypeError: Cannot read properties of null (reading 'useContext') - "**/*argtypes*", - ], - /** - * TODO: Either fix or acknowledge limitation of: - * - storybook/preview-api hooks: - * -- UseState - */ - // @ts-expect-error this type does not exist but the property does! - testNamePattern: /^(?!.*(UseState)).*$/, - browser: { - enabled: true, - provider: "playwright", - headless: true, - instances: [{ - browser: 'chromium' - }] - }, - setupFiles: ["./.storybook/vitest.setup.ts"], - environment: "happy-dom", - }, - }, - ]); - ` - ); - } else { - const defaultConfigFile = template.name.includes('JavaScript') - ? 'vitest.config.js' - : 'vitest.config.ts'; - await writeFile( - join(sandboxDir, vitestConfigFile || viteConfigFile || defaultConfigFile), - dedent` - import path from 'node:path'; - import { fileURLToPath } from 'node:url'; - import { defineConfig, defaultExclude } from "vitest/config"; - import { storybookTest } from "@storybook/addon-vitest/vitest-plugin"; - - ${vitestConfigFile && viteConfigFile ? `import viteConfig from './${viteConfigFile}';` : ''} - - const dirname = - typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url)); - - export default defineConfig({ - ${!isNextjs ? `extends: "${viteConfigFile}",` : ''} - plugins: [ - storybookTest({ - configDir: path.join(dirname, '.storybook'), - storybookScript: "yarn storybook --ci", - tags: { - include: ["vitest"], - }, - }), - ], - ${ - isNextjs - ? `optimizeDeps: { - include: [ - "next/image", - "next/legacy/image", - "next/dist/compiled/react", - "sb-original/default-loader", - "sb-original/image-context", - ], - },` - : '' - } - resolve: { - preserveSymlinks: true, - }, - test: { - name: "storybook", - pool: "threads", - exclude: [ - ...defaultExclude, - // TODO: investigate TypeError: Cannot read properties of null (reading 'useContext') - "**/*argtypes*", - ], - /** - * TODO: Either fix or acknowledge limitation of: - * - storybook/preview-api hooks: - * -- UseState - */ - // @ts-expect-error this type does not exist but the property does! - testNamePattern: /^(?!.*(UseState)).*$/, - browser: { - enabled: true, - provider: "playwright", - headless: true, - instances: [{ - browser: 'chromium' - }] - }, - setupFiles: ["./.storybook/vitest.setup.ts"], - environment: "happy-dom", - }, - }); - ` - ); + const configFile = workspaceFile || vitestConfigFile || viteConfigFile; + if (!configFile) { + throw new Error(`No Vitest or Vite config file found in sandbox: ${sandboxDir}`); } + + let fileContent = await readFile(join(sandboxDir, configFile), 'utf-8'); + // search for storybookTest({...}) and place `tags: 'vitest'` into it but tags option doesn't exist yet in the config. Also consider multi line + const storybookTestRegex = /storybookTest\((\{[\s\S]*?\})\)/g; + fileContent = fileContent.replace(storybookTestRegex, (match, args) => { + // Add tags as the last property before the closing } + const lastBraceIndex = args.lastIndexOf('}'); + if (lastBraceIndex !== -1) { + // Insert before the last } + const before = args.slice(0, lastBraceIndex).trimEnd(); + const needsComma = before.endsWith('{') ? '' : ','; + const after = args.slice(lastBraceIndex); + return `storybookTest(${before}${needsComma}\n tags: {\n include: ['vitest']\n }\n${after})`; + } + // If tags exists and is not empty, or any other case, return as is + return match; + }); + + await writeFile(join(sandboxDir, configFile), fileContent); + // Only run story tests which are tagged with 'vitest' + const previewConfig = await readConfig({ cwd: sandboxDir, fileName: 'preview' }); + previewConfig.setFieldValue(['tags'], ['vitest']); + await writeConfig(previewConfig); } export async function addExtraDependencies({ From 5730da97769dc2c37c55c07f0bdbb5657f388e7b Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 16 Jun 2025 10:14:56 +0200 Subject: [PATCH 06/12] Add INCLUDE_CANDIDATES constant for optimized dependency handling in Vitest and load optimizeDeps preset --- code/addons/vitest/src/vitest-plugin/index.ts | 5 + code/builders/builder-vite/src/constants.ts | 146 +++++++++++++++++ .../builders/builder-vite/src/optimizeDeps.ts | 148 +----------------- 3 files changed, 152 insertions(+), 147 deletions(-) create mode 100644 code/builders/builder-vite/src/constants.ts diff --git a/code/addons/vitest/src/vitest-plugin/index.ts b/code/addons/vitest/src/vitest-plugin/index.ts index e5696bdcaf4c..138acce92c2b 100644 --- a/code/addons/vitest/src/vitest-plugin/index.ts +++ b/code/addons/vitest/src/vitest-plugin/index.ts @@ -29,6 +29,7 @@ import sirv from 'sirv'; import { dedent } from 'ts-dedent'; // ! Relative import to prebundle it without needing to depend on the Vite builder +import { INCLUDE_CANDIDATES } from '../../../../builders/builder-vite/src/constants'; import { withoutVitePlugins } from '../../../../builders/builder-vite/src/utils/without-vite-plugins'; import type { InternalOptions, UserOptions } from './types'; @@ -151,6 +152,7 @@ export const storybookTest = async (options?: UserOptions): Promise => staticDirs, previewLevelTags, core, + extraOptimizeDeps, ] = await Promise.all([ getStoryGlobsAndFiles(presets, directories), presets.apply('framework', undefined), @@ -159,6 +161,7 @@ export const storybookTest = async (options?: UserOptions): Promise => presets.apply('staticDirs', []), extractTagsFromPreview(finalOptions.configDir), presets.apply('core'), + presets.apply('optimizeViteDeps', []), ]); const pluginsToIgnore = [ @@ -329,6 +332,8 @@ export const storybookTest = async (options?: UserOptions): Promise => '@storybook/addon-vitest/internal/setup-file', '@storybook/addon-vitest/internal/global-setup', '@storybook/addon-vitest/internal/test-utils', + ...extraOptimizeDeps, + ...INCLUDE_CANDIDATES, ...(frameworkName?.includes('react') || frameworkName?.includes('nextjs') ? ['react-dom/test-utils'] : []), diff --git a/code/builders/builder-vite/src/constants.ts b/code/builders/builder-vite/src/constants.ts new file mode 100644 index 000000000000..fc4d5145e4ba --- /dev/null +++ b/code/builders/builder-vite/src/constants.ts @@ -0,0 +1,146 @@ +// It ensures that vite converts cjs deps into esm without vite having to find them during startup and then having to log a message about them and restart +// TODO: Many of the deps might be prebundled now though, so probably worth trying to remove and see what happens +export const INCLUDE_CANDIDATES = [ + '@ampproject/remapping', + '@base2/pretty-print-object', + '@emotion/core', + '@emotion/is-prop-valid', + '@emotion/styled', + '@jridgewell/sourcemap-codec', + '@storybook/addon-a11y/preview', + '@storybook/addon-designs/blocks', + '@storybook/addon-docs/blocks', + '@storybook/addon-docs/preview', + '@storybook/addon-links/preview', + '@storybook/addon-themes', + '@storybook/addon-themes/preview', + '@storybook/html', + '@storybook/html/dist/entry-preview-docs.mjs', + '@storybook/html/dist/entry-preview.mjs', + '@storybook/nextjs-vite/dist/preview.mjs', + '@storybook/preact', + '@storybook/preact/dist/entry-preview-docs.mjs', + '@storybook/preact/dist/entry-preview.mjs', + '@storybook/react > acorn-jsx', + '@storybook/react', + '@storybook/react/dist/entry-preview-docs.mjs', + '@storybook/react/dist/entry-preview-rsc.mjs', + '@storybook/react/dist/entry-preview.mjs', + '@storybook/svelte', + '@storybook/svelte/dist/entry-preview-docs.mjs', + '@storybook/svelte/dist/entry-preview.mjs', + '@storybook/vue3', + '@storybook/vue3/dist/entry-preview-docs.mjs', + '@storybook/vue3/dist/entry-preview.mjs', + '@storybook/web-components', + '@storybook/web-components/dist/entry-preview-docs.mjs', + '@storybook/web-components/dist/entry-preview.mjs', + 'acorn-jsx', + 'acorn-walk', + 'acorn', + 'airbnb-js-shims', + 'ansi-to-html', + 'aria-query', + 'axe-core', + 'axobject-query', + 'chromatic/isChromatic', + 'color-convert', + 'deep-object-diff', + 'doctrine', + 'emotion-theming', + 'escodegen', + 'estraverse', + 'fast-deep-equal', + 'html-tags', + 'isobject', + 'loader-utils', + 'lodash/camelCase.js', + 'lodash/camelCase', + 'lodash/cloneDeep.js', + 'lodash/cloneDeep', + 'lodash/countBy.js', + 'lodash/countBy', + 'lodash/debounce.js', + 'lodash/debounce', + 'lodash/isEqual.js', + 'lodash/isEqual', + 'lodash/isFunction.js', + 'lodash/isFunction', + 'lodash/isPlainObject.js', + 'lodash/isPlainObject', + 'lodash/isString.js', + 'lodash/isString', + 'lodash/kebabCase.js', + 'lodash/kebabCase', + 'lodash/mapKeys.js', + 'lodash/mapKeys', + 'lodash/mapValues.js', + 'lodash/mapValues', + 'lodash/merge.js', + 'lodash/merge', + 'lodash/mergeWith.js', + 'lodash/mergeWith', + 'lodash/pick.js', + 'lodash/pick', + 'lodash/pickBy.js', + 'lodash/pickBy', + 'lodash/startCase.js', + 'lodash/startCase', + 'lodash/throttle.js', + 'lodash/throttle', + 'lodash/uniq.js', + 'lodash/uniq', + 'lodash/upperFirst.js', + 'lodash/upperFirst', + 'memoizerific', + 'mockdate', + 'msw-storybook-addon', + 'overlayscrollbars', + 'polished', + 'prettier/parser-babel', + 'prettier/parser-flow', + 'prettier/parser-typescript', + 'prop-types', + 'qs', + 'react-dom', + 'react-dom/client', + 'react-dom/test-utils', + 'react-fast-compare', + 'react-is', + 'react-textarea-autosize', + 'react', + 'react/jsx-dev-runtime', + 'react/jsx-runtime', + 'refractor/core', + 'refractor/lang/bash.js', + 'refractor/lang/css.js', + 'refractor/lang/graphql.js', + 'refractor/lang/js-extras.js', + 'refractor/lang/json.js', + 'refractor/lang/jsx.js', + 'refractor/lang/markdown.js', + 'refractor/lang/markup.js', + 'refractor/lang/tsx.js', + 'refractor/lang/typescript.js', + 'refractor/lang/yaml.js', + 'regenerator-runtime/runtime.js', + 'sb-original/default-loader', + 'sb-original/image-context', + 'semver', // TODO: Remove once https://github.com/npm/node-semver/issues/712 is fixed + 'slash', + 'store2', + 'storybook/actions', + 'storybook/actions/decorator', + 'storybook/internal/core-events', + 'storybook/internal/csf', + 'storybook/internal/preview/runtime', + 'storybook/preview-api', + 'storybook/viewport', + 'synchronous-promise', + 'telejson', + 'ts-dedent', + 'unfetch', + 'util-deprecate', + 'vue', + 'warning', +]; diff --git a/code/builders/builder-vite/src/optimizeDeps.ts b/code/builders/builder-vite/src/optimizeDeps.ts index 585b09996ce7..9b7729538732 100644 --- a/code/builders/builder-vite/src/optimizeDeps.ts +++ b/code/builders/builder-vite/src/optimizeDeps.ts @@ -4,155 +4,9 @@ import type { Options } from 'storybook/internal/types'; import type { UserConfig, InlineConfig as ViteInlineConfig } from 'vite'; +import { INCLUDE_CANDIDATES } from './constants'; import { listStories } from './list-stories'; -// It ensures that vite converts cjs deps into esm without vite having to find them during startup and then having to log a message about them and restart -// TODO: Many of the deps might be prebundled now though, so probably worth trying to remove and see what happens -const INCLUDE_CANDIDATES = [ - '@ampproject/remapping', - '@base2/pretty-print-object', - '@emotion/core', - '@emotion/is-prop-valid', - '@emotion/styled', - '@jridgewell/sourcemap-codec', - '@storybook/addon-a11y/preview', - '@storybook/addon-designs/blocks', - '@storybook/addon-docs/blocks', - '@storybook/addon-docs/preview', - '@storybook/addon-links/preview', - '@storybook/addon-themes', - '@storybook/addon-themes/preview', - '@storybook/html', - '@storybook/html/dist/entry-preview-docs.mjs', - '@storybook/html/dist/entry-preview.mjs', - '@storybook/nextjs-vite/dist/preview.mjs', - '@storybook/preact', - '@storybook/preact/dist/entry-preview-docs.mjs', - '@storybook/preact/dist/entry-preview.mjs', - '@storybook/react > acorn-jsx', - '@storybook/react', - '@storybook/react/dist/entry-preview-docs.mjs', - '@storybook/react/dist/entry-preview-rsc.mjs', - '@storybook/react/dist/entry-preview.mjs', - '@storybook/svelte', - '@storybook/svelte/dist/entry-preview-docs.mjs', - '@storybook/svelte/dist/entry-preview.mjs', - '@storybook/vue3', - '@storybook/vue3/dist/entry-preview-docs.mjs', - '@storybook/vue3/dist/entry-preview.mjs', - '@storybook/web-components', - '@storybook/web-components/dist/entry-preview-docs.mjs', - '@storybook/web-components/dist/entry-preview.mjs', - 'acorn-jsx', - 'acorn-walk', - 'acorn', - 'airbnb-js-shims', - 'ansi-to-html', - 'aria-query', - 'axe-core', - 'axobject-query', - 'chromatic/isChromatic', - 'color-convert', - 'deep-object-diff', - 'doctrine', - 'emotion-theming', - 'escodegen', - 'estraverse', - 'fast-deep-equal', - 'html-tags', - 'isobject', - 'loader-utils', - 'lodash/camelCase.js', - 'lodash/camelCase', - 'lodash/cloneDeep.js', - 'lodash/cloneDeep', - 'lodash/countBy.js', - 'lodash/countBy', - 'lodash/debounce.js', - 'lodash/debounce', - 'lodash/isEqual.js', - 'lodash/isEqual', - 'lodash/isFunction.js', - 'lodash/isFunction', - 'lodash/isPlainObject.js', - 'lodash/isPlainObject', - 'lodash/isString.js', - 'lodash/isString', - 'lodash/kebabCase.js', - 'lodash/kebabCase', - 'lodash/mapKeys.js', - 'lodash/mapKeys', - 'lodash/mapValues.js', - 'lodash/mapValues', - 'lodash/merge.js', - 'lodash/merge', - 'lodash/mergeWith.js', - 'lodash/mergeWith', - 'lodash/pick.js', - 'lodash/pick', - 'lodash/pickBy.js', - 'lodash/pickBy', - 'lodash/startCase.js', - 'lodash/startCase', - 'lodash/throttle.js', - 'lodash/throttle', - 'lodash/uniq.js', - 'lodash/uniq', - 'lodash/upperFirst.js', - 'lodash/upperFirst', - 'memoizerific', - 'mockdate', - 'msw-storybook-addon', - 'overlayscrollbars', - 'polished', - 'prettier/parser-babel', - 'prettier/parser-flow', - 'prettier/parser-typescript', - 'prop-types', - 'qs', - 'react-dom', - 'react-dom/client', - 'react-dom/test-utils', - 'react-fast-compare', - 'react-is', - 'react-textarea-autosize', - 'react', - 'react/jsx-dev-runtime', - 'react/jsx-runtime', - 'refractor/core', - 'refractor/lang/bash.js', - 'refractor/lang/css.js', - 'refractor/lang/graphql.js', - 'refractor/lang/js-extras.js', - 'refractor/lang/json.js', - 'refractor/lang/jsx.js', - 'refractor/lang/markdown.js', - 'refractor/lang/markup.js', - 'refractor/lang/tsx.js', - 'refractor/lang/typescript.js', - 'refractor/lang/yaml.js', - 'regenerator-runtime/runtime.js', - 'sb-original/default-loader', - 'sb-original/image-context', - 'semver', // TODO: Remove once https://github.com/npm/node-semver/issues/712 is fixed - 'slash', - 'store2', - 'storybook/actions', - 'storybook/actions/decorator', - 'storybook/internal/core-events', - 'storybook/internal/csf', - 'storybook/internal/preview/runtime', - 'storybook/preview-api', - 'storybook/viewport', - 'synchronous-promise', - 'telejson', - 'ts-dedent', - 'unfetch', - 'util-deprecate', - 'vue', - 'warning', -]; - /** * Helper function which allows us to `filter` with an async predicate. Uses Promise.all for * performance. From f6d611f7b8bdb94eba00426b628e1612ddc97835 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 16 Jun 2025 10:36:01 +0200 Subject: [PATCH 07/12] Refactor dependency handling in Vitest to use filterResolvableIncludeCandidates for optimized resolution --- code/addons/vitest/src/vitest-plugin/index.ts | 4 ++-- code/builders/builder-vite/src/constants.ts | 20 ++++++++++++++++++- .../builders/builder-vite/src/optimizeDeps.ts | 4 ++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/code/addons/vitest/src/vitest-plugin/index.ts b/code/addons/vitest/src/vitest-plugin/index.ts index 138acce92c2b..6a1bd63b1ee3 100644 --- a/code/addons/vitest/src/vitest-plugin/index.ts +++ b/code/addons/vitest/src/vitest-plugin/index.ts @@ -29,7 +29,7 @@ import sirv from 'sirv'; import { dedent } from 'ts-dedent'; // ! Relative import to prebundle it without needing to depend on the Vite builder -import { INCLUDE_CANDIDATES } from '../../../../builders/builder-vite/src/constants'; +import { filterResolvableIncludeCandidates } from '../../../../builders/builder-vite/src/constants'; import { withoutVitePlugins } from '../../../../builders/builder-vite/src/utils/without-vite-plugins'; import type { InternalOptions, UserOptions } from './types'; @@ -333,7 +333,7 @@ export const storybookTest = async (options?: UserOptions): Promise => '@storybook/addon-vitest/internal/global-setup', '@storybook/addon-vitest/internal/test-utils', ...extraOptimizeDeps, - ...INCLUDE_CANDIDATES, + ...filterResolvableIncludeCandidates(), ...(frameworkName?.includes('react') || frameworkName?.includes('nextjs') ? ['react-dom/test-utils'] : []), diff --git a/code/builders/builder-vite/src/constants.ts b/code/builders/builder-vite/src/constants.ts index fc4d5145e4ba..2d9d7488eead 100644 --- a/code/builders/builder-vite/src/constants.ts +++ b/code/builders/builder-vite/src/constants.ts @@ -1,6 +1,6 @@ // It ensures that vite converts cjs deps into esm without vite having to find them during startup and then having to log a message about them and restart // TODO: Many of the deps might be prebundled now though, so probably worth trying to remove and see what happens -export const INCLUDE_CANDIDATES = [ +const INCLUDE_CANDIDATES = [ '@ampproject/remapping', '@base2/pretty-print-object', '@emotion/core', @@ -133,8 +133,10 @@ export const INCLUDE_CANDIDATES = [ 'storybook/actions/decorator', 'storybook/internal/core-events', 'storybook/internal/csf', + 'storybook/internal/preview-api', 'storybook/internal/preview/runtime', 'storybook/preview-api', + 'storybook/theming', 'storybook/viewport', 'synchronous-promise', 'telejson', @@ -144,3 +146,19 @@ export const INCLUDE_CANDIDATES = [ 'vue', 'warning', ]; + +/** + * Returns only those INCLUDE_CANDIDATES that can be resolved by Vite's resolver. + * + * @param config Vite config to use for resolution + */ +export function filterResolvableIncludeCandidates(): string[] { + return INCLUDE_CANDIDATES.filter((id) => { + try { + require.resolve(id); + return true; + } catch { + return false; + } + }); +} diff --git a/code/builders/builder-vite/src/optimizeDeps.ts b/code/builders/builder-vite/src/optimizeDeps.ts index 9b7729538732..7a6c74571d4e 100644 --- a/code/builders/builder-vite/src/optimizeDeps.ts +++ b/code/builders/builder-vite/src/optimizeDeps.ts @@ -4,7 +4,7 @@ import type { Options } from 'storybook/internal/types'; import type { UserConfig, InlineConfig as ViteInlineConfig } from 'vite'; -import { INCLUDE_CANDIDATES } from './constants'; +import { filterResolvableIncludeCandidates } from './constants'; import { listStories } from './list-stories'; /** @@ -28,7 +28,7 @@ export async function getOptimizeDeps(config: ViteInlineConfig, options: Options // See https://github.com/vitejs/vite/blob/67d164392e8e9081dc3f0338c4b4b8eea6c5f7da/packages/vite/src/node/optimizer/index.ts#L182-L199 const resolve = resolvedConfig.createResolver({ asSrc: false }); const include = await asyncFilter( - Array.from(new Set([...INCLUDE_CANDIDATES, ...extraOptimizeDeps])), + Array.from(new Set([...filterResolvableIncludeCandidates(), ...extraOptimizeDeps])), async (id) => Boolean(await resolve(id)) ); From af59deb443442c2bc615deef9bef2e280bd1c008 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 16 Jun 2025 11:05:55 +0200 Subject: [PATCH 08/12] Refactor optimizeDeps handling in Vitest to include INCLUDE_CANDIDATES and streamline dependency resolution --- code/addons/docs/src/preset.ts | 7 +----- code/addons/vitest/src/vitest-plugin/index.ts | 23 +++++++++++-------- code/builders/builder-vite/src/constants.ts | 18 +++++++++++---- .../builders/builder-vite/src/optimizeDeps.ts | 6 +++-- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/code/addons/docs/src/preset.ts b/code/addons/docs/src/preset.ts index c51e0f789e4a..614292098d43 100644 --- a/code/addons/docs/src/preset.ts +++ b/code/addons/docs/src/preset.ts @@ -212,11 +212,6 @@ export const resolvedReact = async (existing: any) => ({ mdx: existing?.mdx ?? dirname(require.resolve('@mdx-js/react')), }); -const optimizeViteDeps = [ - '@mdx-js/react', - '@storybook/addon-docs > acorn-jsx', - '@storybook/addon-docs', - 'markdown-to-jsx', -]; +const optimizeViteDeps = ['@mdx-js/react', '@storybook/addon-docs', 'markdown-to-jsx']; export { webpackX as webpack, docsX as docs, optimizeViteDeps }; diff --git a/code/addons/vitest/src/vitest-plugin/index.ts b/code/addons/vitest/src/vitest-plugin/index.ts index 6a1bd63b1ee3..6808340d1c2e 100644 --- a/code/addons/vitest/src/vitest-plugin/index.ts +++ b/code/addons/vitest/src/vitest-plugin/index.ts @@ -29,7 +29,10 @@ import sirv from 'sirv'; import { dedent } from 'ts-dedent'; // ! Relative import to prebundle it without needing to depend on the Vite builder -import { filterResolvableIncludeCandidates } from '../../../../builders/builder-vite/src/constants'; +import { + INCLUDE_CANDIDATES, + filterResolvableIncludeCandidates, +} from '../../../../builders/builder-vite/src/constants'; import { withoutVitePlugins } from '../../../../builders/builder-vite/src/utils/without-vite-plugins'; import type { InternalOptions, UserOptions } from './types'; @@ -329,14 +332,16 @@ export const storybookTest = async (options?: UserOptions): Promise => optimizeDeps: { include: [ - '@storybook/addon-vitest/internal/setup-file', - '@storybook/addon-vitest/internal/global-setup', - '@storybook/addon-vitest/internal/test-utils', - ...extraOptimizeDeps, - ...filterResolvableIncludeCandidates(), - ...(frameworkName?.includes('react') || frameworkName?.includes('nextjs') - ? ['react-dom/test-utils'] - : []), + ...filterResolvableIncludeCandidates([ + ...extraOptimizeDeps, + ...INCLUDE_CANDIDATES, + '@storybook/addon-vitest/internal/setup-file', + '@storybook/addon-vitest/internal/global-setup', + '@storybook/addon-vitest/internal/test-utils', + ...(frameworkName?.includes('react') || frameworkName?.includes('nextjs') + ? ['react-dom/test-utils'] + : []), + ]), ], }, diff --git a/code/builders/builder-vite/src/constants.ts b/code/builders/builder-vite/src/constants.ts index 2d9d7488eead..5f4a7601ee11 100644 --- a/code/builders/builder-vite/src/constants.ts +++ b/code/builders/builder-vite/src/constants.ts @@ -1,6 +1,6 @@ // It ensures that vite converts cjs deps into esm without vite having to find them during startup and then having to log a message about them and restart // TODO: Many of the deps might be prebundled now though, so probably worth trying to remove and see what happens -const INCLUDE_CANDIDATES = [ +export const INCLUDE_CANDIDATES = [ '@ampproject/remapping', '@base2/pretty-print-object', '@emotion/core', @@ -152,13 +152,21 @@ const INCLUDE_CANDIDATES = [ * * @param config Vite config to use for resolution */ -export function filterResolvableIncludeCandidates(): string[] { - return INCLUDE_CANDIDATES.filter((id) => { +export function filterResolvableIncludeCandidates(candidates: string[]): string[] { + return candidates.filter((id) => { + const split = id.split('>'); + const primaryId = split[0].trim(); + try { - require.resolve(id); + require.resolve(primaryId); return true; } catch { - return false; + try { + import.meta.resolve(primaryId); + return true; + } catch { + return false; + } } }); } diff --git a/code/builders/builder-vite/src/optimizeDeps.ts b/code/builders/builder-vite/src/optimizeDeps.ts index 7a6c74571d4e..d4bcd047fb72 100644 --- a/code/builders/builder-vite/src/optimizeDeps.ts +++ b/code/builders/builder-vite/src/optimizeDeps.ts @@ -4,7 +4,7 @@ import type { Options } from 'storybook/internal/types'; import type { UserConfig, InlineConfig as ViteInlineConfig } from 'vite'; -import { filterResolvableIncludeCandidates } from './constants'; +import { INCLUDE_CANDIDATES, filterResolvableIncludeCandidates } from './constants'; import { listStories } from './list-stories'; /** @@ -28,7 +28,9 @@ export async function getOptimizeDeps(config: ViteInlineConfig, options: Options // See https://github.com/vitejs/vite/blob/67d164392e8e9081dc3f0338c4b4b8eea6c5f7da/packages/vite/src/node/optimizer/index.ts#L182-L199 const resolve = resolvedConfig.createResolver({ asSrc: false }); const include = await asyncFilter( - Array.from(new Set([...filterResolvableIncludeCandidates(), ...extraOptimizeDeps])), + Array.from( + new Set([...filterResolvableIncludeCandidates([...INCLUDE_CANDIDATES, ...extraOptimizeDeps])]) + ), async (id) => Boolean(await resolve(id)) ); From d176c8ef1cdf3cacb282379efea035340bbc2929 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 16 Jun 2025 11:14:54 +0200 Subject: [PATCH 09/12] Insert resolve configuration in Vitest setup to preserve symlinks, enhancing compatibility with plugins --- scripts/tasks/sandbox-parts.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index b6b78766b987..ae8fb2031516 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -445,6 +445,12 @@ export async function setupVitest(details: TemplateDetails, options: PassedOptio } let fileContent = await readFile(join(sandboxDir, configFile), 'utf-8'); + // Insert resolve: { preserveSymlinks: true } as a sibling to plugins in the top-level config object + // Handles both defineConfig({ ... }) and defineWorkspace([ ... , { ... }]) + fileContent = fileContent.replace(/(plugins\s*:\s*\[[^\]]*\],?)/, (match) => { + // Insert resolve after plugins + return `${match}\n resolve: {\n preserveSymlinks: true\n },`; + }); // search for storybookTest({...}) and place `tags: 'vitest'` into it but tags option doesn't exist yet in the config. Also consider multi line const storybookTestRegex = /storybookTest\((\{[\s\S]*?\})\)/g; fileContent = fileContent.replace(storybookTestRegex, (match, args) => { From b35fbf87571f27a03929ec8ad7cbb442c29de6ba Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 16 Jun 2025 11:17:07 +0200 Subject: [PATCH 10/12] Update code/builders/builder-vite/src/constants.ts --- code/builders/builder-vite/src/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/builders/builder-vite/src/constants.ts b/code/builders/builder-vite/src/constants.ts index 5f4a7601ee11..d492acb085f7 100644 --- a/code/builders/builder-vite/src/constants.ts +++ b/code/builders/builder-vite/src/constants.ts @@ -148,7 +148,7 @@ export const INCLUDE_CANDIDATES = [ ]; /** - * Returns only those INCLUDE_CANDIDATES that can be resolved by Vite's resolver. + * Returns only those candidates that can be resolved * * @param config Vite config to use for resolution */ From 2d18cacc0536afc12126622ae42d91dab4ae4ca0 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 16 Jun 2025 12:34:34 +0200 Subject: [PATCH 11/12] Remove filterResolvableIncludeCandidates --- code/addons/vitest/src/vitest-plugin/index.ts | 23 +++++++----------- code/builders/builder-vite/src/constants.ts | 24 ------------------- .../builders/builder-vite/src/optimizeDeps.ts | 6 ++--- 3 files changed, 11 insertions(+), 42 deletions(-) diff --git a/code/addons/vitest/src/vitest-plugin/index.ts b/code/addons/vitest/src/vitest-plugin/index.ts index 6808340d1c2e..fe6e0942e812 100644 --- a/code/addons/vitest/src/vitest-plugin/index.ts +++ b/code/addons/vitest/src/vitest-plugin/index.ts @@ -29,10 +29,7 @@ import sirv from 'sirv'; import { dedent } from 'ts-dedent'; // ! Relative import to prebundle it without needing to depend on the Vite builder -import { - INCLUDE_CANDIDATES, - filterResolvableIncludeCandidates, -} from '../../../../builders/builder-vite/src/constants'; +import { INCLUDE_CANDIDATES } from '../../../../builders/builder-vite/src/constants'; import { withoutVitePlugins } from '../../../../builders/builder-vite/src/utils/without-vite-plugins'; import type { InternalOptions, UserOptions } from './types'; @@ -332,16 +329,14 @@ export const storybookTest = async (options?: UserOptions): Promise => optimizeDeps: { include: [ - ...filterResolvableIncludeCandidates([ - ...extraOptimizeDeps, - ...INCLUDE_CANDIDATES, - '@storybook/addon-vitest/internal/setup-file', - '@storybook/addon-vitest/internal/global-setup', - '@storybook/addon-vitest/internal/test-utils', - ...(frameworkName?.includes('react') || frameworkName?.includes('nextjs') - ? ['react-dom/test-utils'] - : []), - ]), + ...extraOptimizeDeps, + ...INCLUDE_CANDIDATES, + '@storybook/addon-vitest/internal/setup-file', + '@storybook/addon-vitest/internal/global-setup', + '@storybook/addon-vitest/internal/test-utils', + ...(frameworkName?.includes('react') || frameworkName?.includes('nextjs') + ? ['react-dom/test-utils'] + : []), ], }, diff --git a/code/builders/builder-vite/src/constants.ts b/code/builders/builder-vite/src/constants.ts index d492acb085f7..8f998f3c6dc8 100644 --- a/code/builders/builder-vite/src/constants.ts +++ b/code/builders/builder-vite/src/constants.ts @@ -146,27 +146,3 @@ export const INCLUDE_CANDIDATES = [ 'vue', 'warning', ]; - -/** - * Returns only those candidates that can be resolved - * - * @param config Vite config to use for resolution - */ -export function filterResolvableIncludeCandidates(candidates: string[]): string[] { - return candidates.filter((id) => { - const split = id.split('>'); - const primaryId = split[0].trim(); - - try { - require.resolve(primaryId); - return true; - } catch { - try { - import.meta.resolve(primaryId); - return true; - } catch { - return false; - } - } - }); -} diff --git a/code/builders/builder-vite/src/optimizeDeps.ts b/code/builders/builder-vite/src/optimizeDeps.ts index d4bcd047fb72..9b7729538732 100644 --- a/code/builders/builder-vite/src/optimizeDeps.ts +++ b/code/builders/builder-vite/src/optimizeDeps.ts @@ -4,7 +4,7 @@ import type { Options } from 'storybook/internal/types'; import type { UserConfig, InlineConfig as ViteInlineConfig } from 'vite'; -import { INCLUDE_CANDIDATES, filterResolvableIncludeCandidates } from './constants'; +import { INCLUDE_CANDIDATES } from './constants'; import { listStories } from './list-stories'; /** @@ -28,9 +28,7 @@ export async function getOptimizeDeps(config: ViteInlineConfig, options: Options // See https://github.com/vitejs/vite/blob/67d164392e8e9081dc3f0338c4b4b8eea6c5f7da/packages/vite/src/node/optimizer/index.ts#L182-L199 const resolve = resolvedConfig.createResolver({ asSrc: false }); const include = await asyncFilter( - Array.from( - new Set([...filterResolvableIncludeCandidates([...INCLUDE_CANDIDATES, ...extraOptimizeDeps])]) - ), + Array.from(new Set([...INCLUDE_CANDIDATES, ...extraOptimizeDeps])), async (id) => Boolean(await resolve(id)) ); From 9ee545100773d90b506980d44db328049d10c190 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 16 Jun 2025 12:38:18 +0200 Subject: [PATCH 12/12] Refactor INTERNAL_DEFAULT_PROJECT_ANNOTATIONS --- code/renderers/react/src/portable-stories.tsx | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/code/renderers/react/src/portable-stories.tsx b/code/renderers/react/src/portable-stories.tsx index 152236b4b732..93a663853047 100644 --- a/code/renderers/react/src/portable-stories.tsx +++ b/code/renderers/react/src/portable-stories.tsx @@ -12,6 +12,7 @@ import type { } from 'storybook/internal/types'; import { + composeConfigs, composeStories as originalComposeStories, composeStory as originalComposeStory, setProjectAnnotations as originalSetProjectAnnotations, @@ -19,6 +20,7 @@ import { } from 'storybook/preview-api'; import * as reactProjectAnnotations from './entry-preview'; +import * as reactArgTypesAnnotations from './entry-preview-argtypes'; import type { Meta } from './public-types'; import type { ReactRenderer } from './types'; @@ -53,20 +55,24 @@ export function setProjectAnnotations( } // This will not be necessary once we have auto preset loading -export const INTERNAL_DEFAULT_PROJECT_ANNOTATIONS: ProjectAnnotations = { - ...reactProjectAnnotations, - /** @deprecated */ - renderToCanvas: async (renderContext, canvasElement) => { - if (renderContext.storyContext.testingLibraryRender == null) { - return reactProjectAnnotations.renderToCanvas(renderContext, canvasElement); - } - const { - storyContext: { context, unboundStoryFn: Story, testingLibraryRender: render }, - } = renderContext; - const { unmount } = render(, { container: context.canvasElement }); - return unmount; - }, -}; +export const INTERNAL_DEFAULT_PROJECT_ANNOTATIONS: ProjectAnnotations = + composeConfigs([ + reactProjectAnnotations, + reactArgTypesAnnotations, + { + /** @deprecated */ + renderToCanvas: async (renderContext, canvasElement) => { + if (renderContext.storyContext.testingLibraryRender == null) { + return reactProjectAnnotations.renderToCanvas(renderContext, canvasElement); + } + const { + storyContext: { context, unboundStoryFn: Story, testingLibraryRender: render }, + } = renderContext; + const { unmount } = render(, { container: context.canvasElement }); + return unmount; + }, + } as ProjectAnnotations, + ]); /** * Function that will receive a story along with meta (e.g. a default export from a .stories file)