From 0c8244783b84dc7aede83d8dd335e1d64a24e959 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:00:58 +0000 Subject: [PATCH 1/2] Initial plan From 5aac90b0a943d9544f94098a08d11264983f6367 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:09:13 +0000 Subject: [PATCH 2/2] Fix glob special character escaping in optimizeDeps.entries to prevent cold-cache failures Co-authored-by: valentinpalkovic <5889929+valentinpalkovic@users.noreply.github.com> --- .../storybook-optimize-deps-plugin.test.ts | 44 +++++++++++++++++++ .../plugins/storybook-optimize-deps-plugin.ts | 16 ++++++- 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.test.ts diff --git a/code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.test.ts b/code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.test.ts new file mode 100644 index 000000000000..f42d94a5fe12 --- /dev/null +++ b/code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from 'vitest'; + +import { escapeGlobPath } from './storybook-optimize-deps-plugin'; + +describe('escapeGlobPath', () => { + it('should not modify a plain path without special characters', () => { + expect(escapeGlobPath('./src/Button.stories.tsx')).toBe('./src/Button.stories.tsx'); + }); + + it('should escape parentheses in path segments (e.g. Next.js route groups)', () => { + expect(escapeGlobPath('./src/(group)/Button.stories.tsx')).toBe( + './src/\\(group\\)/Button.stories.tsx' + ); + }); + + it('should escape square brackets in path segments', () => { + expect(escapeGlobPath('./src/[id]/Button.stories.tsx')).toBe( + './src/\\[id\\]/Button.stories.tsx' + ); + }); + + it('should escape curly braces in path segments', () => { + expect(escapeGlobPath('./src/{group}/Button.stories.tsx')).toBe( + './src/\\{group\\}/Button.stories.tsx' + ); + }); + + it('should escape glob wildcard characters', () => { + expect(escapeGlobPath('./src/Button*.stories.tsx')).toBe('./src/Button\\*.stories.tsx'); + expect(escapeGlobPath('./src/Button?.stories.tsx')).toBe('./src/Button\\?.stories.tsx'); + }); + + it('should escape all special glob characters together', () => { + expect(escapeGlobPath('./src/(group)/[id]/{name}/*.stories.tsx')).toBe( + './src/\\(group\\)/\\[id\\]/\\{name\\}/\\*.stories.tsx' + ); + }); + + it('should not modify paths that contain no special glob characters', () => { + expect(escapeGlobPath('./src/my-component/Button.stories.tsx')).toBe( + './src/my-component/Button.stories.tsx' + ); + }); +}); diff --git a/code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.ts b/code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.ts index 7bee90301998..e75ab293dff1 100644 --- a/code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.ts +++ b/code/builders/builder-vite/src/plugins/storybook-optimize-deps-plugin.ts @@ -8,6 +8,16 @@ import { type Plugin } from 'vite'; import { processPreviewAnnotation } from '../utils/process-preview-annotation'; import { getUniqueImportPaths } from '../utils/unique-import-paths'; +/** + * Escapes special glob characters in a file path so Vite's dep optimizer treats it as a literal + * path rather than a glob pattern. This is necessary for paths containing characters like `(` and + * `)` (e.g. Next.js route group directories such as `src/(group)/...`) which would otherwise be + * interpreted as extglob patterns by fast-glob. + */ +export function escapeGlobPath(filePath: string): string { + return filePath.replace(/[()[\]{}!*?|+@]/g, '\\$&'); +} + /** A Vite plugin that configures dependency optimization for Storybook's dev server. */ export function storybookOptimizeDepsPlugin(options: Options): Plugin { return { @@ -40,12 +50,14 @@ export function storybookOptimizeDepsPlugin(options: Options): Plugin { // Story files + preview annotation files as entry points for the dep optimizer. // Vite will crawl these to discover all transitive CJS dependencies that need // pre-bundling, removing the need for a hard-coded include list. + // Paths are escaped so that special glob characters (e.g. parentheses in Next.js route + // group directories) are treated as literal characters, not glob syntax. entries: [ ...(typeof config.optimizeDeps?.entries === 'string' ? [config.optimizeDeps.entries] : (config.optimizeDeps?.entries ?? [])), - ...getUniqueImportPaths(index), - ...previewAnnotationEntries, + ...getUniqueImportPaths(index).map(escapeGlobPath), + ...previewAnnotationEntries.map(escapeGlobPath), ], // Extra deps explicitly included by Storybook presets (e.g. framework-specific packages). include: [...extraOptimizeDeps, ...(config.optimizeDeps?.include ?? [])],