Skip to content

Commit fd74caa

Browse files
yannbfvalentinpalkovic
authored andcommitted
Merge pull request #31557 from storybookjs/valentin/monorepo-enhancements
CLI: Improve support for upgrading Storybook in monorepos (cherry picked from commit 9c2f2fd)
1 parent a23a418 commit fd74caa

File tree

194 files changed

+8261
-4671
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

194 files changed

+8261
-4671
lines changed

.cursor/mcp.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"mcpServers": {
3+
"wallaby": {
4+
"command": "node",
5+
"args": [
6+
"~/.wallaby/mcp/"
7+
]
8+
}
9+
}
10+
}

.cursorrules

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
## Test Configuration
2+
3+
This Storybook repository uses Vitest as the test runner. Here are the key commands and configuration:
4+
5+
### Test Scripts
6+
7+
- `yarn test` - Run all tests (from root directory, delegates to `cd code; yarn test`)
8+
- `yarn test <test-name>` - Run focused tests matching the pattern
9+
- `yarn test:watch` - Run tests in watch mode
10+
- `yarn test:watch <test-name>` - Run focused tests in watch mode
11+
12+
### Test Directory Structure
13+
14+
- Tests are located in the `code/` directory
15+
- Vitest configuration is in `code/vitest.workspace.ts`
16+
- Test files typically follow the pattern `*.test.ts`, `*.test.tsx`, `*.spec.ts`, or `*.spec.tsx`
17+
18+
### Running Tests in Cursor
19+
20+
1. Use Cmd+Shift+P (or Ctrl+Shift+P) and search for "Tasks: Run Task"
21+
2. Select from the available test tasks:
22+
- "Run All Tests" - Runs all tests
23+
- "Run Test (Watch Mode)" - Runs tests in watch mode
24+
- "Run Focused Test" - Prompts for test name/pattern to run specific tests
25+
- "Run Focused Test (Watch Mode)" - Runs specific tests in watch mode
26+
27+
### Vitest Configuration
28+
29+
- Workspace configuration: `./code/vitest.workspace.ts`
30+
- Command line: `yarn --cwd code test`
31+
- Root directory for tests: `./code/`
32+
33+
### Test Execution Context
34+
35+
- Tests run from the `code/` directory
36+
- Use `NODE_OPTIONS=--max_old_space_size=4096` for memory optimization
37+
- Supports both watch mode and single-run execution
38+
39+
### Focused Test Patterns
40+
41+
When running focused tests, you can use:
42+
43+
- File names: `Button.test.ts`
44+
- Test descriptions: `"should render correctly"`
45+
- Directory patterns: `components/`
46+
- Vitest patterns: `-t "pattern"` for test name matching
47+
48+
### Test Mocking Rules
49+
50+
Follow the spy mocking rules defined in `.cursor/rules/spy-mocking.mdc` for consistent mocking patterns with Vitest.

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,6 @@
5050
"typescript.preferences.quoteStyle": "single",
5151
"typescript.preferGoToSourceDefinition": true,
5252
"typescript.tsdk": "./code/node_modules/typescript/lib",
53-
"vitest.workspaceConfig": "./code/vitest.workspace.ts"
53+
"vitest.workspaceConfig": "./code/vitest.workspace.ts",
54+
"vitest.rootConfig": "./code/vitest.workspace.ts",
5455
}

MIGRATION.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- [Vitest Addon (former @storybook/experimental-addon-test): Vitest 2.0 support is dropped](#vitest-addon-former-storybookexperimental-addon-test-vitest-20-support-is-dropped)
1818
- [Viewport/Backgrounds Addon synchronized configuration and `globals` usage](#viewportbackgrounds-addon-synchronized-configuration-and-globals-usage)
1919
- [Storysource Addon removed](#storysource-addon-removed)
20+
- [Mdx-gfm Addon removed](#mdx-gfm-addon-removed)
2021
- [API and Component Changes](#api-and-component-changes)
2122
- [Button Component API Changes](#button-component-api-changes)
2223
- [Icon System Updates](#icon-system-updates)
@@ -726,6 +727,10 @@ See here for the ways you have to configure addon viewports & backgrounds:
726727

727728
The `@storybook/addon-storysource` addon and the `@storybook/source-loader` package are removed in Storybook 9.0. Instead, Storybook now provides a Code Panel via `@storybook/addon-docs` that offers similar functionality with improved integration and performance.
728729

730+
#### Mdx-gfm Addon removed
731+
732+
The `@storybook/addon-mdx-gfm` addon is removed in Storybook 9.0 since it is no longer needed.
733+
729734
**Migration Steps:**
730735

731736
1. Remove the old addon

code/addons/a11y/src/postinstall.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ const $ = execa({
1111
});
1212

1313
export default async function postinstall(options: PostinstallOptions) {
14-
const command = ['storybook', 'automigrate', 'addonA11yAddonTest'];
14+
const command = ['storybook', 'automigrate', 'addon-a11y-addon-test'];
15+
16+
command.push('--loglevel', 'silent');
17+
command.push('--skip-doctor');
1518

1619
if (options.yes) {
1720
command.push('--yes');

code/addons/vitest/src/node/vitest-manager.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type {
99
WorkspaceProject,
1010
} from 'vitest/node';
1111

12-
import { resolvePathInStorybookCache } from 'storybook/internal/common';
12+
import { getProjectRoot, resolvePathInStorybookCache } from 'storybook/internal/common';
1313
import type { StoryId, StoryIndex, StoryIndexEntry } from 'storybook/internal/types';
1414

1515
import { findUp } from 'find-up';
@@ -65,10 +65,13 @@ export class VitestManager {
6565
: { enabled: false }
6666
) as CoverageOptions;
6767

68-
const vitestWorkspaceConfig = await findUp([
69-
...VITEST_WORKSPACE_FILE_EXTENSION.map((ext) => `vitest.workspace.${ext}`),
70-
...VITEST_CONFIG_FILE_EXTENSIONS.map((ext) => `vitest.config.${ext}`),
71-
]);
68+
const vitestWorkspaceConfig = await findUp(
69+
[
70+
...VITEST_WORKSPACE_FILE_EXTENSION.map((ext) => `vitest.workspace.${ext}`),
71+
...VITEST_CONFIG_FILE_EXTENSIONS.map((ext) => `vitest.config.${ext}`),
72+
],
73+
{ stopAt: getProjectRoot() }
74+
);
7275

7376
const projectName = 'storybook:' + process.env.STORYBOOK_CONFIG_DIR;
7477

code/addons/vitest/src/postinstall-logger.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { colors, logger } from 'storybook/internal/node-logger';
22

3-
import boxen, { type Options } from 'boxen';
4-
53
const fancy =
64
process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color';
75

@@ -11,14 +9,16 @@ export const success = colors.green(fancy ? '✔' : '√');
119
export const warning = colors.orange(fancy ? '⚠' : '‼');
1210
export const error = colors.red(fancy ? '✖' : '×');
1311

12+
type Options = Parameters<typeof logger.logBox>[1];
13+
1414
const baseOptions: Options = {
1515
borderStyle: 'round',
1616
padding: 1,
1717
};
1818

1919
export const print = (message: string, options: Options) => {
2020
logger.line(1);
21-
logger.plain(boxen(message, { ...baseOptions, ...options }));
21+
logger.logBox(message, { ...baseOptions, ...options });
2222
};
2323

2424
export const printInfo = (title: string, message: string, options?: Options) =>

code/addons/vitest/src/postinstall.ts

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
JsPackageManagerFactory,
88
extractProperFrameworkName,
99
formatFileContent,
10+
getProjectRoot,
1011
loadAllPresets,
1112
loadMainConfig,
1213
scanAndTransformFiles,
@@ -39,7 +40,10 @@ const EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.cts', '.mts', '.cjs', '.mjs'
3940
const addonA11yName = '@storybook/addon-a11y';
4041

4142
const findFile = async (basename: string, extensions = EXTENSIONS) =>
42-
findUp(extensions.map((ext) => basename + ext));
43+
findUp(
44+
extensions.map((ext) => basename + ext),
45+
{ stopAt: getProjectRoot() }
46+
);
4347

4448
export default async function postInstall(options: PostinstallOptions) {
4549
printSuccess(
@@ -56,13 +60,11 @@ export default async function postInstall(options: PostinstallOptions) {
5660
});
5761

5862
const info = await getStorybookInfo(options);
59-
const allDeps = await packageManager.getAllDependencies();
63+
const allDeps = packageManager.getAllDependencies();
6064
// only install these dependencies if they are not already installed
6165
const dependencies = ['vitest', '@vitest/browser', 'playwright'].filter((p) => !allDeps[p]);
6266
const vitestVersionSpecifier = await packageManager.getInstalledVersion('vitest');
6367
const coercedVitestVersion = vitestVersionSpecifier ? coerce(vitestVersionSpecifier) : null;
64-
// if Vitest is installed, we use the same version to keep consistency across Vitest packages
65-
const vitestVersionToInstall = vitestVersionSpecifier ?? 'latest';
6668

6769
const mainJsPath = serverResolve(resolve(options.configDir, 'main')) as string;
6870
const config = await readConfig(mainJsPath);
@@ -88,16 +90,11 @@ export default async function postInstall(options: PostinstallOptions) {
8890
});
8991

9092
if (out.migrateToNextjsVite) {
91-
await packageManager.addDependencies(
92-
{
93-
installAsDevDependencies: true,
94-
skipInstall: options.skipInstall,
95-
packageJson: await packageManager.readPackageJson(),
96-
},
97-
[`@storybook/nextjs-vite@${versions['@storybook/nextjs-vite']}`]
98-
);
93+
await packageManager.addDependencies({ type: 'devDependencies', skipInstall: true }, [
94+
'@storybook/nextjs-vite',
95+
]);
9996

100-
await packageManager.removeDependencies({}, ['@storybook/nextjs']);
97+
await packageManager.removeDependencies(['@storybook/nextjs']);
10198

10299
traverse(config._ast, {
103100
StringLiteral(path) {
@@ -260,27 +257,24 @@ export default async function postInstall(options: PostinstallOptions) {
260257

261258
const versionedDependencies = dependencies.map((p) => {
262259
if (p.includes('vitest')) {
263-
return `${p}@${vitestVersionToInstall ?? 'latest'}`;
260+
return vitestVersionSpecifier ? `${p}@${vitestVersionSpecifier}` : p;
264261
}
265262

266263
return p;
267264
});
268265

269266
if (versionedDependencies.length > 0) {
270-
logger.line(1);
271-
logger.plain(`${step} Installing dependencies:`);
272-
logger.plain(colors.gray(' ' + versionedDependencies.join(', ')));
273-
274267
await packageManager.addDependencies(
275-
{
276-
installAsDevDependencies: true,
277-
skipInstall: options.skipInstall,
278-
packageJson: await packageManager.readPackageJson(),
279-
},
268+
{ type: 'devDependencies', skipInstall: true },
280269
versionedDependencies
281270
);
271+
logger.line(1);
272+
logger.plain(`${step} Installing dependencies:`);
273+
logger.plain(colors.gray(' ' + versionedDependencies.join(', ')));
282274
}
283275

276+
await packageManager.installDependencies();
277+
284278
logger.line(1);
285279

286280
if (options.skipInstall) {
@@ -489,11 +483,10 @@ export default async function postInstall(options: PostinstallOptions) {
489483
if (a11yAddon) {
490484
try {
491485
logger.plain(`${step} Setting up ${addonA11yName} for @storybook/addon-vitest:`);
492-
const command = ['automigrate', 'addonA11yAddonTest'];
486+
const command = ['automigrate', 'addon-a11y-addon-test'];
493487

494-
if (options.yes) {
495-
command.push('--yes');
496-
}
488+
command.push('--loglevel', 'silent');
489+
command.push('--yes', '--skip-doctor');
497490

498491
if (options.packageManager) {
499492
command.push('--package-manager', options.packageManager);
@@ -544,8 +537,8 @@ export default async function postInstall(options: PostinstallOptions) {
544537
}
545538

546539
async function getStorybookInfo({ configDir, packageManager: pkgMgr }: PostinstallOptions) {
547-
const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr });
548-
const packageJson = await packageManager.retrievePackageJson();
540+
const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr, configDir });
541+
const { packageJson } = packageManager.primaryPackageJson;
549542

550543
const config = await loadMainConfig({ configDir, noCache: true });
551544
const { framework } = config;
@@ -559,8 +552,8 @@ async function getStorybookInfo({ configDir, packageManager: pkgMgr }: Postinsta
559552
overridePresets: [
560553
require.resolve('storybook/internal/core-server/presets/common-override-preset'),
561554
],
562-
configDir,
563555
packageJson,
556+
configDir,
564557
isCritical: true,
565558
});
566559

code/addons/vitest/src/vitest-plugin/index.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,22 @@ const getStoryGlobsAndFiles = async (
6767
directories: { configDir: string; workingDir: string }
6868
) => {
6969
const stories = await presets.apply('stories', []);
70-
const docs = await presets.apply('docs', {});
71-
const indexers = await presets.apply('experimental_indexers', []);
72-
const generator = new StoryIndexGenerator(normalizeStories(stories, directories), {
73-
...directories,
74-
indexers,
75-
docs,
70+
71+
const normalizedStories = normalizeStories(stories, {
72+
configDir: directories.configDir,
73+
workingDir: directories.workingDir,
7674
});
77-
await generator.initialize();
75+
76+
const matchingStoryFiles = await StoryIndexGenerator.findMatchingFilesForSpecifiers(
77+
normalizedStories,
78+
directories.workingDir
79+
);
80+
7881
return {
7982
storiesGlobs: stories,
80-
storiesFiles: generator.storyFileNames(),
83+
storiesFiles: StoryIndexGenerator.storyFileNames(
84+
new Map(matchingStoryFiles.map(([specifier, cache]) => [specifier, cache]))
85+
),
8186
};
8287
};
8388

code/addons/vitest/template/stories/basics.stories.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default {
1818
globals: {
1919
sb_theme: 'light',
2020
},
21+
tags: ['!vitest'],
2122
};
2223

2324
export const Validation = {
@@ -45,8 +46,9 @@ export const Step = {
4546
};
4647

4748
export const TypeAndClear = {
48-
play: async ({ canvasElement, userEvent }) => {
49-
const canvas = within(canvasElement);
49+
play: async ({ canvas, userEvent }) => {
50+
await userEvent.clear(canvas.getByTestId('value'));
51+
5052
await userEvent.type(canvas.getByTestId('value'), 'initial value');
5153
await userEvent.clear(canvas.getByTestId('value'));
5254
await userEvent.type(canvas.getByTestId('value'), 'final value');

0 commit comments

Comments
 (0)