Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a93ac51
Add RSBuild-based sandboxes
valentinpalkovic Nov 13, 2025
ca8270b
undo this commit soon
yannbf Nov 13, 2025
62009ba
Revert "undo this commit soon"
yannbf Nov 13, 2025
f280b95
Introduce rsbuild sandboxes to CI tests
yannbf Nov 13, 2025
222d89a
Merge branch 'valentin/cli-init-rework' into valentin/add-rsbuild-san…
valentinpalkovic Nov 13, 2025
a779f78
Merge branch 'valentin/cli-init-rework' into valentin/add-rsbuild-san…
valentinpalkovic Nov 16, 2025
f7a9007
Merge branch 'valentin/cli-init-rework' into valentin/add-rsbuild-san…
valentinpalkovic Nov 19, 2025
4fe74b8
Merge remote-tracking branch 'origin/valentin/cli-init-rework' into v…
valentinpalkovic Nov 19, 2025
ca8093d
Add rsbuild dependencies to base templates and update resolutions for…
valentinpalkovic Nov 19, 2025
d59aba7
Merge branch 'next' into valentin/add-rsbuild-sandboxes
valentinpalkovic Nov 19, 2025
b5e86cd
Filter out 'docs' addon stories for specific RSBUILD frameworks in sa…
valentinpalkovic Nov 19, 2025
05c1652
Refactor getFrameworkInfo to include configDir parameter and update f…
valentinpalkovic Nov 19, 2025
1e02a9e
Make framework name optional in StorybookMetadata type
valentinpalkovic Nov 19, 2025
edd2e2f
Fix tests
valentinpalkovic Nov 19, 2025
e7e59a1
Merge branch 'next' into valentin/add-rsbuild-sandboxes
valentinpalkovic Nov 20, 2025
585d93f
Enhance framework info retrieval by sanitizing package paths and upda…
valentinpalkovic Nov 20, 2025
80bd74b
Fix types
valentinpalkovic Nov 20, 2025
bac0e91
Don't use csf factories for rsbuild
valentinpalkovic Nov 20, 2025
6713380
Update template names to use 'RsBuild' and add experimental test synt…
valentinpalkovic Nov 20, 2025
0dd8595
Add module mocking stories and update e2e test navigation path
valentinpalkovic Nov 20, 2025
60daa33
Merge remote-tracking branch 'origin/next' into valentin/add-rsbuild-…
valentinpalkovic Nov 20, 2025
df5b95f
Remove csf-factories from non-react rsbuild-based sandboxes
valentinpalkovic Nov 20, 2025
b366e4b
Exclude e2e-dev job for rsbuild sandboxes
valentinpalkovic Nov 20, 2025
cc42c4c
Remove console.log
valentinpalkovic Nov 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,7 @@ jobs:
mkdir features-1
cd features-1
npm set registry http://localhost:6001
npx create-storybook --yes --package-manager npm --features docs test a11y
npx create-storybook --yes --package-manager npm --features docs test a11y --loglevel=debug
npx vitest
environment:
IN_STORYBOOK_SANDBOX: true
Expand Down Expand Up @@ -1172,15 +1172,15 @@ workflows:
requires:
- build
- create-sandboxes:
parallelism: 34
parallelism: 38
requires:
- build
- check-sandboxes:
parallelism: 1
requires:
- create-sandboxes
- chromatic-sandboxes:
parallelism: 31
parallelism: 35
requires:
- create-sandboxes
- e2e-production:
Expand All @@ -1192,7 +1192,7 @@ workflows:
requires:
- create-sandboxes
- test-runner-production:
parallelism: 29
parallelism: 33
requires:
- create-sandboxes
- vitest-integration:
Expand Down Expand Up @@ -1286,11 +1286,11 @@ workflows:
requires:
- unit-tests
- create-sandboxes:
parallelism: 19
parallelism: 21
requires:
- build
- chromatic-sandboxes:
parallelism: 16
parallelism: 18
requires:
- create-sandboxes
- e2e-production:
Expand All @@ -1302,7 +1302,7 @@ workflows:
requires:
- create-sandboxes
- test-runner-production:
parallelism: 14
parallelism: 16
requires:
- create-sandboxes
- vitest-integration:
Expand Down Expand Up @@ -1381,11 +1381,11 @@ workflows:
requires:
- unit-tests
- create-sandboxes:
parallelism: 13
parallelism: 14
requires:
- build
- chromatic-sandboxes:
parallelism: 10
parallelism: 11
requires:
- create-sandboxes
- e2e-production:
Expand All @@ -1397,7 +1397,7 @@ workflows:
requires:
- create-sandboxes
- test-runner-production:
parallelism: 8
parallelism: 9
requires:
- create-sandboxes
- vitest-integration:
Expand Down
6 changes: 3 additions & 3 deletions .circleci/src/workflows/daily.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
requires:
- build
- create-sandboxes:
parallelism: 34
parallelism: 38
requires:
- build
- check-sandboxes:
Expand All @@ -42,7 +42,7 @@ jobs:
# requires:
# - create-sandboxes
- chromatic-sandboxes:
parallelism: 31
parallelism: 35
requires:
- create-sandboxes
- e2e-production:
Expand All @@ -54,7 +54,7 @@ jobs:
requires:
- create-sandboxes
- test-runner-production:
parallelism: 29
parallelism: 33
requires:
- create-sandboxes
- vitest-integration:
Expand Down
6 changes: 3 additions & 3 deletions .circleci/src/workflows/merged.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ jobs:
requires:
- unit-tests
- create-sandboxes:
parallelism: 19
parallelism: 21
requires:
- build
- chromatic-sandboxes:
parallelism: 16
parallelism: 18
requires:
- create-sandboxes
- e2e-production:
Expand All @@ -50,7 +50,7 @@ jobs:
requires:
- create-sandboxes
- test-runner-production:
parallelism: 14
parallelism: 16
requires:
- create-sandboxes
- vitest-integration:
Expand Down
6 changes: 3 additions & 3 deletions .circleci/src/workflows/normal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ jobs:
requires:
- unit-tests
- create-sandboxes:
parallelism: 13
parallelism: 14
requires:
- build
- chromatic-sandboxes:
parallelism: 10
parallelism: 11
requires:
- create-sandboxes
- e2e-production:
Expand All @@ -50,7 +50,7 @@ jobs:
requires:
- create-sandboxes
- test-runner-production:
parallelism: 8
parallelism: 9
requires:
- create-sandboxes
- vitest-integration:
Expand Down
2 changes: 1 addition & 1 deletion code/core/src/csf-tools/ConfigFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1179,7 +1179,7 @@ export const isCsfFactoryPreview = (previewConfig: ConfigFile) => {
return !!program.body.find((node) => {
return (
t.isImportDeclaration(node) &&
node.source.value.includes('@storybook') &&
node.source.value.includes('storybook') &&
node.specifiers.some((specifier) => {
return (
t.isImportSpecifier(specifier) &&
Expand Down
73 changes: 31 additions & 42 deletions code/core/src/telemetry/get-framework-info.test.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,47 @@
import { sep } from 'node:path';

import { describe, expect, it, vi } from 'vitest';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import type { StorybookConfig } from 'storybook/internal/types';

import { getFrameworkInfo } from './get-framework-info';
import { getActualPackageJson } from './package-json';

vi.mock('./package-json', () => ({
getActualPackageJson: vi.fn(),
vi.mock('storybook/internal/common', () => ({
getStorybookInfo: vi.fn(),
}));

describe('getFrameworkInfo', () => {
it('should return an empty object if mainConfig.framework is undefined', async () => {
const result = await getFrameworkInfo({} as StorybookConfig);
expect(result).toEqual({});
const defaultInfo = {
frameworkPackage: '@storybook/react',
rendererPackage: '@storybook/react',
builderPackage: '@storybook/builder-vite',
};

beforeEach(async () => {
const { getStorybookInfo } = await import('storybook/internal/common');
vi.mocked(getStorybookInfo).mockResolvedValue(defaultInfo as any);
});

it('should return an empty object if mainConfig.framework name is undefined', async () => {
const result = await getFrameworkInfo({ framework: {} } as StorybookConfig);
expect(result).toEqual({});
it('returns framework/builder/renderer with empty options when no framework provided', async () => {
const result = await getFrameworkInfo({} as StorybookConfig, '/tmp/.storybook');
expect(result).toEqual({
framework: { name: defaultInfo.frameworkPackage, options: {} },
builder: defaultInfo.builderPackage,
renderer: defaultInfo.rendererPackage,
});
});

it('should call getActualPackageJson with the correct package name', async () => {
const packageName = '@storybook/react';
const framework = { name: packageName };
await getFrameworkInfo({ framework } as StorybookConfig);
expect(getActualPackageJson).toHaveBeenCalledWith(packageName);
it('passes configDir to getStorybookInfo', async () => {
const configDir = '/my/project/.storybook';
const { getStorybookInfo } = await import('storybook/internal/common');
await getFrameworkInfo({} as StorybookConfig, configDir);
expect(getStorybookInfo).toHaveBeenCalledWith(configDir);
});

it('should resolve the framework package json correctly and strip project paths in the metadata', async () => {
const packageName = `${process.cwd()}/@storybook/react`.split('/').join(sep);
const framework = { name: packageName };
const frameworkPackageJson = {
name: packageName,
dependencies: {
'@storybook/react': '7.0.0',
'@storybook/builder-vite': '7.0.0',
},
};

vi.mocked(getActualPackageJson).mockResolvedValueOnce(frameworkPackageJson);

const result = await getFrameworkInfo({ framework } as StorybookConfig);

expect(getActualPackageJson).toHaveBeenCalledWith(packageName);

expect(result).toEqual({
framework: {
name: '$SNIP/@storybook/react'.split('/').join(sep),
options: undefined,
},
builder: '@storybook/builder-vite',
renderer: '@storybook/react',
});
it('returns provided framework options when object is passed', async () => {
const options = { foo: 'bar' } as any;
const result = await getFrameworkInfo(
{ framework: { name: '@storybook/react', options } } as any,
'/tmp/.storybook'
);
expect(result.framework.options).toEqual(options);
});
});
74 changes: 9 additions & 65 deletions code/core/src/telemetry/get-framework-info.ts
Original file line number Diff line number Diff line change
@@ -1,80 +1,24 @@
import { normalize } from 'node:path';
import { getStorybookInfo } from 'storybook/internal/common';
import type { StorybookConfig } from 'storybook/internal/types';

import { frameworkPackages } from 'storybook/internal/common';
import type { PackageJson, StorybookConfig } from 'storybook/internal/types';

import { getActualPackageJson } from './package-json';
import { cleanPaths } from './sanitize';

const knownRenderers = [
'html',
'react',
'svelte',
'vue3',
'preact',
'server',
'vue',
'web-components',
'angular',
'ember',
];

const knownBuilders = ['builder-webpack5', 'builder-vite'];

function findMatchingPackage(packageJson: PackageJson, suffixes: string[]) {
const { name = '', version, dependencies, devDependencies, peerDependencies } = packageJson;

const allDependencies = {
// We include the framework itself because it may be a renderer too (e.g. angular)
[name]: version,
...dependencies,
...devDependencies,
...peerDependencies,
};

return suffixes.map((suffix) => `@storybook/${suffix}`).find((pkg) => allDependencies[pkg]);
}

export const getFrameworkPackageName = (packageNameOrPath: string) => {
const normalizedPath = normalize(packageNameOrPath).replace(new RegExp(/\\/, 'g'), '/');

const knownFramework = Object.keys(frameworkPackages).find((pkg) => normalizedPath.endsWith(pkg));

return knownFramework || cleanPaths(packageNameOrPath).replace(/.*node_modules[\\/]/, '');
const cleanAndSanitizePath = (path: string) => {
return cleanPaths(path).replace(/.*node_modules[\\/]/, '');
};

export async function getFrameworkInfo(mainConfig: StorybookConfig) {
if (!mainConfig?.framework) {
return {};
}

const rawName =
typeof mainConfig.framework === 'string' ? mainConfig.framework : mainConfig.framework?.name;
if (!rawName) {
return {};
}

const frameworkPackageJson = await getActualPackageJson(rawName);

if (!frameworkPackageJson) {
return {};
}

const builder = findMatchingPackage(frameworkPackageJson, knownBuilders);
const renderer = findMatchingPackage(frameworkPackageJson, knownRenderers);
export async function getFrameworkInfo(mainConfig: StorybookConfig, configDir: string) {
const { frameworkPackage, rendererPackage, builderPackage } = await getStorybookInfo(configDir);

// TODO: Evaluate if this is correct after removing pnp compatibility code in SB11
// parse framework name and strip off pnp paths etc.
const sanitizedFrameworkName = getFrameworkPackageName(rawName);
const frameworkOptions =
typeof mainConfig.framework === 'object' ? mainConfig.framework.options : {};

return {
framework: {
name: sanitizedFrameworkName,
name: frameworkPackage ? cleanAndSanitizePath(frameworkPackage) : undefined,
options: frameworkOptions,
},
builder,
renderer,
builder: builderPackage ? cleanAndSanitizePath(builderPackage) : undefined,
renderer: rendererPackage ? cleanAndSanitizePath(rendererPackage) : undefined,
};
}
Loading