Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d5235df
send cancel telemetry when canceling init prompts
JReinhold May 1, 2026
a98eb1d
more resilient handling of cancelation, not during prompts
JReinhold May 1, 2026
5f38c8f
Merge branch 'next' into jeppe/fix-cancel-telemetry
JReinhold May 1, 2026
c98252f
format
JReinhold May 1, 2026
bad3563
clear timeout
JReinhold May 1, 2026
839b566
implement pnpm precheck against conflicting minimumReleaseAge configs
JReinhold May 11, 2026
b313a3c
improve messaging around minimumreleaseage
JReinhold May 12, 2026
1b2e1e5
add Yarn handling of minimalAgeGate configuration
JReinhold May 12, 2026
d2339d6
improve code reusability, don't log HandledErrors anyway
JReinhold May 12, 2026
457f45d
Merge branch 'next' into jeppe/handle-minimum-release-age
JReinhold May 12, 2026
81fd30a
Merge branch 'next' into jeppe/fix-cancel-telemetry
JReinhold May 12, 2026
e92a8f6
handle bun and npm, only bail early for the dependency age
JReinhold May 12, 2026
cf6fe62
Merge branch 'jeppe/handle-minimum-release-age' of github.com:storybo…
JReinhold May 12, 2026
6f8dbae
refactor: clean up unused imports and improve telemetry service integ…
JReinhold May 12, 2026
c931dd4
fixes
JReinhold May 12, 2026
2c2087e
Revert nextjs-vite typing change
JReinhold May 12, 2026
97716bd
Merge pull request #34680 from storybookjs/jeppe/fix-cancel-telemetry
JReinhold May 12, 2026
5d64bac
Merge branch 'next' of github.com:storybookjs/storybook into jeppe/ha…
JReinhold May 12, 2026
2642c83
correctly use MinimumReleaseAgeHandledError, correctly modify bunfig
JReinhold May 12, 2026
abf3c2d
detect when minimumReleaseAgeExclude is already correctly configured,…
JReinhold May 12, 2026
3ebdd02
undo the "run" prefix on ghost stories and self healing payload
yannbf May 13, 2026
79a8667
format
JReinhold May 13, 2026
d1d63f0
fix type
JReinhold May 13, 2026
8eb16b8
unknown errors during precheck should not halt the process completely.
JReinhold May 13, 2026
fd0b833
Fix Vite 8 + Vitest + Svelte breaking rolldown deps scanner
JReinhold May 13, 2026
a5d6744
Merge pull request #34782 from storybookjs/yann/fix-payload-names
yannbf May 13, 2026
f1b35fc
Docs: Add in preview notice for change detection
valentinpalkovic May 13, 2026
27d8183
Merge pull request #34786 from storybookjs/valentin/change-detection-…
JReinhold May 13, 2026
d695817
Merge pull request #34783 from storybookjs/jeppe/fix-vite8-svelte
JReinhold May 13, 2026
eed7d2a
simplify error handling
JReinhold May 13, 2026
bdc688f
fix documentation string
JReinhold May 13, 2026
db9d52b
format
JReinhold May 13, 2026
f9810c7
Merge pull request #34769 from storybookjs/jeppe/handle-minimum-relea…
JReinhold May 13, 2026
6796435
Write changelog for 10.4.0-alpha.19 [skip ci]
storybook-bot May 13, 2026
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
18 changes: 18 additions & 0 deletions CHANGELOG.prerelease.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
## 10.4.0-alpha.19

- Agentic Setup: Add --extensive for an extra prompt - [#34730](https://github.com/storybookjs/storybook/pull/34730), thanks @Sidnioulz!
- Agentic Setup: Rework ai-init-opt-in logic - [#34739](https://github.com/storybookjs/storybook/pull/34739), thanks @Sidnioulz!
- CLI: Handle minimumReleaseAge conflicts across package managers - [#34769](https://github.com/storybookjs/storybook/pull/34769), thanks @JReinhold!
- CLI: Improve package incompatibility detection and warning - [#34559](https://github.com/storybookjs/storybook/pull/34559), thanks @copilot-swe-agent!
- CLI: Remove extensive prompt option - [#34740](https://github.com/storybookjs/storybook/pull/34740), thanks @yannbf!
- Cli: Set ai prompt to yes if yes flag for react-vite to tanstack migration - [#34743](https://github.com/storybookjs/storybook/pull/34743), thanks @huang-julien!
- Core: Fix "Open In Editor" support for VSCode - [#34747](https://github.com/storybookjs/storybook/pull/34747), thanks @JReinhold!
- Core: Fix telemetry not handling canceling of prompts - [#34680](https://github.com/storybookjs/storybook/pull/34680), thanks @JReinhold!
- Core: Quiet change-detection regex warning and swap clear icon - [#34758](https://github.com/storybookjs/storybook/pull/34758), thanks @valentinpalkovic!
- Maintenance: Fix self healing payload - [#34782](https://github.com/storybookjs/storybook/pull/34782), thanks @yannbf!
- ReactNative: AppRegistry component name in template - [#34742](https://github.com/storybookjs/storybook/pull/34742), thanks @ndelangen!
- Sidebar: Fix clear filter button not refreshing story list - [#34737](https://github.com/storybookjs/storybook/pull/34737), thanks @valentinpalkovic!
- Sidebar: Show same status icon at story and group level - [#34702](https://github.com/storybookjs/storybook/pull/34702), thanks @valentinpalkovic!
- Svelte: Fix Vite 8 + Vitest breaking rolldown deps scanner - [#34783](https://github.com/storybookjs/storybook/pull/34783), thanks @JReinhold!
- Tanstack: Treeshake top-level unused functions - [#34760](https://github.com/storybookjs/storybook/pull/34760), thanks @huang-julien!

## 10.4.0-alpha.18

- Agentic Setup: Allow failed stories to persist - [#34717](https://github.com/storybookjs/storybook/pull/34717), thanks @Sidnioulz!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,12 @@ describe('AgentTelemetryReporter', () => {
expect.objectContaining({
agent: { name: 'claude' },
analysis: expect.objectContaining({
runTotal: 3,
runPassed: 2,
runPassedButEmptyRender: 1,
runSuccessRate: 0.67,
runSuccessRateWithoutEmptyRender: 0.33,
runUniqueErrorCount: 1,
total: 3,
passed: 2,
passedButEmptyRender: 1,
successRate: 0.67,
successRateWithoutEmptyRender: 0.33,
uniqueErrorCount: 1,
}),
unhandledErrorCount: 0,
watch: false,
Expand Down Expand Up @@ -167,8 +167,8 @@ describe('AgentTelemetryReporter', () => {
'ai-setup-self-healing-scoring',
expect.objectContaining({
analysis: expect.objectContaining({
runTotal: 1,
runPassed: 0,
total: 1,
passed: 0,
cumulativeTotal: 3,
cumulativePassed: 2,
}),
Expand Down Expand Up @@ -218,8 +218,8 @@ describe('AgentTelemetryReporter', () => {
'ai-setup-self-healing-scoring',
expect.objectContaining({
analysis: expect.objectContaining({
runTotal: 1,
runPassed: 1,
total: 1,
passed: 1,
}),
}),
expect.anything()
Expand Down Expand Up @@ -262,8 +262,8 @@ describe('AgentTelemetryReporter', () => {
expect(secondCall[1]).toEqual(
expect.objectContaining({
analysis: expect.objectContaining({
runTotal: 1,
runPassed: 0,
total: 1,
passed: 0,
}),
})
);
Expand Down
222 changes: 222 additions & 0 deletions code/core/src/common/js-package-manager/BUNProxy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

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

import { logger, prompt } from 'storybook/internal/node-logger';
import { MinimumReleaseAgeHandledError } from 'storybook/internal/server-errors';

import { executeCommand } from '../utils/command.ts';
import { JsPackageManager } from './JsPackageManager.ts';
import { BUNProxy } from './BUNProxy.ts';

vi.mock('storybook/internal/node-logger', () => ({
prompt: {
executeTaskWithSpinner: vi.fn(),
getPreferredStdio: vi.fn(() => 'inherit'),
select: vi.fn(),
},
logger: {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
}));

vi.mock(import('../utils/command.ts'), { spy: true });

const mockedExecuteCommand = vi.mocked(executeCommand);

describe('BUN Proxy', () => {
let bunProxy: BUNProxy;

beforeEach(() => {
vi.useRealTimers();
bunProxy = new BUNProxy();
JsPackageManager.clearLatestVersionCache();
vi.clearAllMocks();
});

it('type should be bun', () => {
expect(bunProxy.type).toEqual('bun');
});

describe('installDependencies', () => {
it('should run `bun install`', async () => {
vi.mocked(prompt.executeTaskWithSpinner).mockImplementationOnce(async (fn: any) => {
await Promise.resolve(fn());
});
const executeCommandSpy = mockedExecuteCommand.mockResolvedValue({ stdout: '' } as any);

await bunProxy.installDependencies();

expect(executeCommandSpy).toHaveBeenCalledWith(
expect.objectContaining({ command: 'bun', args: ['install'] })
);
});

it('should rethrow minimum-release-age install errors as handled errors', async () => {
vi.mocked(prompt.executeTaskWithSpinner).mockImplementationOnce(async (fn: any) => {
await Promise.resolve(fn());
});
const originalError = new Error(
'error: @storybook/react@10.4.0-alpha.17 blocked by minimum-release-age'
);
mockedExecuteCommand.mockRejectedValueOnce(originalError);

const error = await bunProxy.installDependencies().then(
() => null,
(caughtError) => caughtError
);

expect(error).toBeInstanceOf(MinimumReleaseAgeHandledError);
expect(error).toMatchObject({ cause: originalError });
expect(error?.message).toContain('minimumReleaseAge');
expect(error?.message).toContain('minimumReleaseAgeExcludes');
});
});

describe('precheckStorybookPackageInstall', () => {
it('updates minimumReleaseAgeExcludes in non-interactive mode when bun would block Storybook', async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2025-01-10T00:00:00.000Z'));
vi.spyOn(bunProxy as any, 'readBunfig').mockReturnValue('minimumReleaseAge = 3600\n');
const updateSpy = vi
.spyOn(bunProxy as any, 'updateMinimumReleaseAgeExcludes')
.mockImplementation(() => undefined);
mockedExecuteCommand.mockResolvedValueOnce({
stdout: JSON.stringify({
'10.4.0-alpha.17': '2025-01-09T23:30:00.000Z',
'10.3.9': '2025-01-09T20:00:00.000Z',
}),
} as any);

await bunProxy.precheckStorybookPackageInstall({
storybookVersion: '10.4.0-alpha.17',
nonInteractive: true,
installContext: 'create',
});

expect(updateSpy).toHaveBeenCalled();
expect(vi.mocked(logger.info)).toHaveBeenCalledWith(
expect.stringContaining('minimumReleaseAgeExcludes')
);
});

it('lets the user update minimumReleaseAgeExcludes interactively', async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2025-01-10T00:00:00.000Z'));
vi.spyOn(bunProxy as any, 'readBunfig').mockReturnValue('minimumReleaseAge = 3600\n');
const updateSpy = vi
.spyOn(bunProxy as any, 'updateMinimumReleaseAgeExcludes')
.mockImplementation(() => undefined);
mockedExecuteCommand.mockResolvedValueOnce({
stdout: JSON.stringify({
'10.4.0-alpha.17': '2025-01-09T23:30:00.000Z',
'10.3.9': '2025-01-09T20:00:00.000Z',
}),
} as any);
vi.mocked(prompt.select).mockResolvedValueOnce('exclude');

await bunProxy.precheckStorybookPackageInstall({
storybookVersion: '10.4.0-alpha.17',
nonInteractive: false,
installContext: 'create',
});

expect(updateSpy).toHaveBeenCalled();
});

it('throws rerun guidance when the user chooses rerun', async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2025-01-10T00:00:00.000Z'));
vi.spyOn(bunProxy as any, 'readBunfig').mockReturnValue('minimumReleaseAge = 3600\n');
mockedExecuteCommand.mockResolvedValueOnce({
stdout: JSON.stringify({
'10.4.0-alpha.17': '2025-01-09T23:30:00.000Z',
'10.3.9': '2025-01-09T20:00:00.000Z',
}),
} as any);
vi.mocked(prompt.select).mockResolvedValueOnce('rerun');

const error = await bunProxy
.precheckStorybookPackageInstall({
storybookVersion: '10.4.0-alpha.17',
nonInteractive: false,
installContext: 'upgrade',
})
.then(
() => null,
(caughtError) => caughtError
);

expect(error).toBeInstanceOf(MinimumReleaseAgeHandledError);
expect(error?.message).toContain('npx storybook@10.3.9 upgrade');
});

it('skips the precheck when Storybook packages are already excluded', async () => {
vi.spyOn(bunProxy as any, 'readBunfig').mockReturnValue(
[
'[install]',
'minimumReleaseAge = 3600',
'minimumReleaseAgeExcludes = ["storybook", "@storybook/*", "eslint-plugin-storybook", "@chromatic-com/storybook"]',
'',
].join('\n')
);
const updateSpy = vi.spyOn(bunProxy as any, 'updateMinimumReleaseAgeExcludes');

await expect(
bunProxy.precheckStorybookPackageInstall({
storybookVersion: '10.4.0-alpha.17',
nonInteractive: false,
installContext: 'upgrade',
})
).resolves.toBeUndefined();

expect(mockedExecuteCommand).not.toHaveBeenCalled();
expect(vi.mocked(prompt.select)).not.toHaveBeenCalled();
expect(vi.mocked(logger.warn)).not.toHaveBeenCalled();
expect(updateSpy).not.toHaveBeenCalled();
});
});

describe('updateMinimumReleaseAgeExcludes', () => {
it('adds minimumReleaseAgeExcludes inside the install section', () => {
const tempDir = mkdtempSync(join(tmpdir(), 'storybook-bun-proxy-'));
const bunfigPath = join(tempDir, 'bunfig.toml');

writeFileSync(join(tempDir, 'package.json'), '{}\n');
const tempBunProxy = new BUNProxy({ cwd: tempDir });
writeFileSync(
bunfigPath,
['[install]', 'minimumReleaseAge = 900000', '[test]', 'coverageThreshold = 0.9', ''].join(
'\n'
)
);

try {
(tempBunProxy as any).updateMinimumReleaseAgeExcludes();

expect(readFileSync(bunfigPath, 'utf-8')).toBe(
[
'[install]',
'minimumReleaseAge = 900000',
'minimumReleaseAgeExcludes = [',
' "storybook",',
' "@storybook/*",',
' "eslint-plugin-storybook",',
' "@chromatic-com/storybook",',
']',
'[test]',
'coverageThreshold = 0.9',
'',
].join('\n')
);
} finally {
rmSync(tempDir, { recursive: true, force: true });
}
});
});
});
Loading