From 23e007670c946a7be6397e8e2be57ae60626bb7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 11:18:38 +0000 Subject: [PATCH 01/14] Initial plan From 6e408d5d8fbbd40ad3356dc3e1b06f61d3aa00c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 11:31:50 +0000 Subject: [PATCH 02/14] fix: scope localStorage persistence per Storybook instance and purge stale tag filters - Add STORYBOOK_INSTANCE_ID global derived from configDir hash to isolate localStorage keys per Storybook project - Include migration from old shared storage key on first load - Automatically purge tag filters that don't match any entries in the current index after loading (prevents stale filters from other instances) - Keep built-in filters (_docs, _play, _test) regardless of index content Co-authored-by: Sidnioulz <5108577+Sidnioulz@users.noreply.github.com> --- .../src/builder-manager/utils/framework.ts | 11 ++ code/core/src/manager-api/modules/stories.ts | 46 +++++++ code/core/src/manager-api/store.ts | 35 ++++- code/core/src/manager-api/tests/store.test.js | 4 + .../src/manager-api/tests/stories.test.ts | 123 ++++++++++++++++++ code/core/src/manager-api/typings.d.ts | 1 + code/core/src/manager/typings.d.ts | 5 + 7 files changed, 223 insertions(+), 2 deletions(-) diff --git a/code/core/src/builder-manager/utils/framework.ts b/code/core/src/builder-manager/utils/framework.ts index df89e68a6336..50538c26cdfe 100644 --- a/code/core/src/builder-manager/utils/framework.ts +++ b/code/core/src/builder-manager/utils/framework.ts @@ -1,3 +1,5 @@ +import { createHash } from 'node:crypto'; + import { extractFrameworkPackageName, frameworkPackages, @@ -27,5 +29,14 @@ export const buildFrameworkGlobalsFromOptions = async (options: Options) => { globals.STORYBOOK_RENDERER = renderer; globals.STORYBOOK_NETWORK_ADDRESS = options.networkAddress; + // Compute a unique instance ID from the configDir so the manager can scope localStorage + // keys per Storybook instance. This prevents shared state across multiple projects. + if (options.configDir) { + globals.STORYBOOK_INSTANCE_ID = createHash('sha256') + .update(options.configDir) + .digest('hex') + .slice(0, 12); + } + return globals; }; diff --git a/code/core/src/manager-api/modules/stories.ts b/code/core/src/manager-api/modules/stories.ts index 15803f447faa..f368a00fb64b 100644 --- a/code/core/src/manager-api/modules/stories.ts +++ b/code/core/src/manager-api/modules/stories.ts @@ -888,6 +888,46 @@ export const init: ModuleFn = ({ ); }; + /** + * Purge tag filters that don't match any entries in the current unfiltered index. Built-in + * filters (like _docs, _play, _test) are always kept because they filter on entry metadata, not + * tags. + */ + const purgeStaleTagFilters = async () => { + const state = store.getState(); + const { internal_index, includedTagFilters, excludedTagFilters } = state; + + if (!internal_index || !includedTagFilters || !excludedTagFilters) { + return; + } + + // Collect all tags present across every entry in the unfiltered index + const allTags = new Set(); + for (const entry of Object.values(internal_index.entries)) { + entry.tags?.forEach((tag: string) => allTags.add(tag)); + } + + // Keep a tag if it is a built-in filter key or actually appears in the index + const isValidTag = (tag: Tag) => Object.hasOwn(BUILT_IN_FILTERS, tag) || allTags.has(tag); + + const validIncluded = includedTagFilters.filter(isValidTag); + const validExcluded = excludedTagFilters.filter(isValidTag); + + const includedChanged = validIncluded.length !== includedTagFilters.length; + const excludedChanged = validExcluded.length !== excludedTagFilters.length; + + if (includedChanged || excludedChanged) { + await store.setState( + { + includedTagFilters: validIncluded, + excludedTagFilters: validExcluded, + }, + { persistence: 'permanent' } + ); + recomputeFilters(); + } + }; + // On initial load, the local iframe will select the first story (or other "selection specifier") // and emit STORY_SPECIFIED with the id. We need to ensure we respond to this change. provider.channel?.on( @@ -1158,6 +1198,12 @@ export const init: ModuleFn = ({ provider.channel?.on(STORY_INDEX_INVALIDATED, () => api.fetchIndex()); await api.fetchIndex(); + + // After the first index load, purge any stale tag filters that don't match + // any entries in the unfiltered index. This prevents leftover filters from + // another Storybook instance (or a previous project state) from hiding all + // sidebar entries. + purgeStaleTagFilters(); }, }; }; diff --git a/code/core/src/manager-api/store.ts b/code/core/src/manager-api/store.ts index b1071e47002b..f9c2e456b774 100644 --- a/code/core/src/manager-api/store.ts +++ b/code/core/src/manager-api/store.ts @@ -1,3 +1,5 @@ +import { global } from '@storybook/global'; + import type { StoreAPI } from 'store2'; import store from 'store2'; @@ -7,10 +9,39 @@ import type { State } from './root'; // setting up the store, overriding set and get to use telejson storeSetup(store._); -export const STORAGE_KEY = '@storybook/manager/store'; +const STORAGE_KEY_BASE = '@storybook/manager/store'; + +/** + * Build the storage key, scoped per Storybook instance when possible. The STORYBOOK_INSTANCE_ID + * global is injected by the builder from a hash of configDir, which ensures each Storybook project + * uses its own localStorage namespace. + */ +function buildStorageKey(): string { + try { + const instanceId = global.STORYBOOK_INSTANCE_ID; + if (instanceId) { + return `${STORAGE_KEY_BASE}/${instanceId}`; + } + } catch { + // Ignore - fall back to the base key + } + return STORAGE_KEY_BASE; +} + +export const STORAGE_KEY = buildStorageKey(); function get(storage: StoreAPI) { - const data = storage.get(STORAGE_KEY); + let data = storage.get(STORAGE_KEY); + + // Migration: if no data exists under the instance-specific key, try the old shared key. + // This is a one-time migration that preserves existing settings when upgrading. + if (!data && STORAGE_KEY !== STORAGE_KEY_BASE) { + data = storage.get(STORAGE_KEY_BASE); + if (data) { + storage.set(STORAGE_KEY, data); + } + } + return data || {}; } diff --git a/code/core/src/manager-api/tests/store.test.js b/code/core/src/manager-api/tests/store.test.js index e77bc1cd289e..105c50a5faa4 100644 --- a/code/core/src/manager-api/tests/store.test.js +++ b/code/core/src/manager-api/tests/store.test.js @@ -20,6 +20,10 @@ vi.mock('store2', () => ({ })); describe('store', () => { + it('uses the base storage key when STORYBOOK_INSTANCE_ID is not set', () => { + expect(STORAGE_KEY).toBe('@storybook/manager/store'); + }); + it('sensibly combines local+session storage for initial state', () => { store2.session.get.mockReturnValueOnce({ foo: 'bar', combined: { a: 'b' } }); store2.local.get.mockReturnValueOnce({ foo: 'baz', another: 'value', combined: { c: 'd' } }); diff --git a/code/core/src/manager-api/tests/stories.test.ts b/code/core/src/manager-api/tests/stories.test.ts index 3bdbeec046fd..85a789f0cad7 100644 --- a/code/core/src/manager-api/tests/stories.test.ts +++ b/code/core/src/manager-api/tests/stories.test.ts @@ -1672,4 +1672,127 @@ describe('stories API', () => { `); }); }); + + describe('purgeStaleTagFilters', () => { + it('removes user tag filters that do not match any entries in the index', async () => { + const entriesWithTags = { + 'a--1': { + type: 'story' as const, + subtype: 'story' as const, + title: 'a', + name: '1', + id: 'a--1', + importPath: './a.ts', + tags: ['dev', 'existing-tag'], + }, + }; + + fetch.mockReturnValue( + Promise.resolve({ + status: 200, + ok: true, + json: () => ({ v: 5, entries: entriesWithTags }), + } as any as Response) + ); + + const moduleArgs = createMockModuleArgs({ + initialState: { + // Simulate leftover stale tag from another SB instance + includedTagFilters: ['existing-tag', 'stale-tag-from-other-project'], + excludedTagFilters: ['another-stale-tag'], + } as any, + }); + const { init } = initStories(moduleArgs as unknown as ModuleArgs); + const { store } = moduleArgs; + + await init!(); + await wait(16); + + const { includedTagFilters, excludedTagFilters } = store.getState(); + expect(includedTagFilters).toEqual(['existing-tag']); + expect(excludedTagFilters).toEqual([]); + }); + + it('keeps built-in filter tags even if they do not appear in entry tags', async () => { + const entriesWithTags = { + 'a--1': { + type: 'story' as const, + subtype: 'story' as const, + title: 'a', + name: '1', + id: 'a--1', + importPath: './a.ts', + tags: ['dev'], + }, + }; + + fetch.mockReturnValue( + Promise.resolve({ + status: 200, + ok: true, + json: () => ({ v: 5, entries: entriesWithTags }), + } as any as Response) + ); + + const moduleArgs = createMockModuleArgs({ + initialState: { + // _docs and _play are built-in filters and should be kept + includedTagFilters: ['_docs', '_play'], + excludedTagFilters: ['_test', 'stale-tag'], + } as any, + }); + const { init } = initStories(moduleArgs as unknown as ModuleArgs); + const { store } = moduleArgs; + + await init!(); + await wait(16); + + const { includedTagFilters, excludedTagFilters } = store.getState(); + expect(includedTagFilters).toEqual(['_docs', '_play']); + expect(excludedTagFilters).toEqual(['_test']); + }); + + it('does not update state if all tag filters are valid', async () => { + const entriesWithTags = { + 'a--1': { + type: 'story' as const, + subtype: 'story' as const, + title: 'a', + name: '1', + id: 'a--1', + importPath: './a.ts', + tags: ['dev', 'my-tag'], + }, + }; + + fetch.mockReturnValue( + Promise.resolve({ + status: 200, + ok: true, + json: () => ({ v: 5, entries: entriesWithTags }), + } as any as Response) + ); + + const moduleArgs = createMockModuleArgs({ + initialState: { + includedTagFilters: ['my-tag'], + excludedTagFilters: ['_docs'], + } as any, + }); + const { init } = initStories(moduleArgs as unknown as ModuleArgs); + const { store } = moduleArgs; + + const stateBeforeInit = store.getState(); + await init!(); + await wait(16); + + // setState should have been called for setIndex but not for purging tag filters + const setStateCalls = (store.setState as any).mock.calls; + const tagFilterUpdates = setStateCalls.filter( + (call: any[]) => + call[0] && 'includedTagFilters' in call[0] && 'excludedTagFilters' in call[0] + ); + expect(tagFilterUpdates).toHaveLength(0); + }); + }); }); diff --git a/code/core/src/manager-api/typings.d.ts b/code/core/src/manager-api/typings.d.ts index 7bf67c97fc33..a4043e9265f9 100644 --- a/code/core/src/manager-api/typings.d.ts +++ b/code/core/src/manager-api/typings.d.ts @@ -10,3 +10,4 @@ declare var STORYBOOK_ADDON_STATE: Record; declare var STORYBOOK_FRAMEWORK: import('storybook/internal/types').SupportedFramework | undefined; declare var STORYBOOK_RENDERER: import('storybook/internal/types').SupportedRenderer | undefined; declare var STORYBOOK_BUILDER: import('storybook/internal/types').SupportedBuilder | undefined; +declare var STORYBOOK_INSTANCE_ID: string | undefined; diff --git a/code/core/src/manager/typings.d.ts b/code/core/src/manager/typings.d.ts index fb3fa90c7557..13e99af96c5a 100644 --- a/code/core/src/manager/typings.d.ts +++ b/code/core/src/manager/typings.d.ts @@ -34,4 +34,9 @@ declare var __STORYBOOK_CLIENT_LOGGER__: any; declare var __STORYBOOK_ADDONS_CHANNEL__: any; declare var __STORYBOOK_TYPES__: any; declare var STORYBOOK_RENDERER: string | undefined; +/** + * A short hash derived from the Storybook config directory path. Used to scope localStorage keys + * per Storybook instance so that persistence is not shared across different projects. + */ +declare var STORYBOOK_INSTANCE_ID: string | undefined; declare var sendTelemetryError: (error: any) => void; From 0aa1bbce807fd0c4f7e1634373a360370d2aab39 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 11:35:11 +0000 Subject: [PATCH 03/14] fix: address code review feedback - await purge, clarify catch block and hash length Co-authored-by: Sidnioulz <5108577+Sidnioulz@users.noreply.github.com> --- code/core/src/builder-manager/utils/framework.ts | 1 + code/core/src/manager-api/modules/stories.ts | 2 +- code/core/src/manager-api/store.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/code/core/src/builder-manager/utils/framework.ts b/code/core/src/builder-manager/utils/framework.ts index 50538c26cdfe..10d6e1c0f5c7 100644 --- a/code/core/src/builder-manager/utils/framework.ts +++ b/code/core/src/builder-manager/utils/framework.ts @@ -31,6 +31,7 @@ export const buildFrameworkGlobalsFromOptions = async (options: Options) => { // Compute a unique instance ID from the configDir so the manager can scope localStorage // keys per Storybook instance. This prevents shared state across multiple projects. + // 12 hex characters (48 bits of entropy) is more than sufficient for uniqueness. if (options.configDir) { globals.STORYBOOK_INSTANCE_ID = createHash('sha256') .update(options.configDir) diff --git a/code/core/src/manager-api/modules/stories.ts b/code/core/src/manager-api/modules/stories.ts index f368a00fb64b..586870aa889e 100644 --- a/code/core/src/manager-api/modules/stories.ts +++ b/code/core/src/manager-api/modules/stories.ts @@ -1203,7 +1203,7 @@ export const init: ModuleFn = ({ // any entries in the unfiltered index. This prevents leftover filters from // another Storybook instance (or a previous project state) from hiding all // sidebar entries. - purgeStaleTagFilters(); + await purgeStaleTagFilters(); }, }; }; diff --git a/code/core/src/manager-api/store.ts b/code/core/src/manager-api/store.ts index f9c2e456b774..fa099e1779d7 100644 --- a/code/core/src/manager-api/store.ts +++ b/code/core/src/manager-api/store.ts @@ -23,7 +23,7 @@ function buildStorageKey(): string { return `${STORAGE_KEY_BASE}/${instanceId}`; } } catch { - // Ignore - fall back to the base key + // In edge cases (e.g. restricted globals), fall back to the shared base key } return STORAGE_KEY_BASE; } From 95c10fbf6a852174a47adfd0d7234c04793b6c4d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 18:14:20 +0000 Subject: [PATCH 04/14] fix: address all PR review feedback - Use getAnonymousProjectId() from telemetry instead of configDir hash - Use 'anonymous' as fallback instead of shared base key - Store current version in localStorage for future migration logic - Remove migration logic from get() that ran on every call - Replace wait(16) timers in tests with vi.waitFor - Export getAnonymousProjectId from storybook/internal/telemetry Co-authored-by: Sidnioulz <5108577+Sidnioulz@users.noreply.github.com> --- .../src/builder-manager/utils/framework.ts | 16 +++----- code/core/src/manager-api/store.ts | 38 +++++++++++-------- code/core/src/manager-api/tests/store.test.js | 16 +++++++- .../src/manager-api/tests/stories.test.ts | 20 +++++----- code/core/src/manager/typings.d.ts | 5 ++- code/core/src/telemetry/index.ts | 2 + 6 files changed, 56 insertions(+), 41 deletions(-) diff --git a/code/core/src/builder-manager/utils/framework.ts b/code/core/src/builder-manager/utils/framework.ts index 10d6e1c0f5c7..726c117220e0 100644 --- a/code/core/src/builder-manager/utils/framework.ts +++ b/code/core/src/builder-manager/utils/framework.ts @@ -1,11 +1,10 @@ -import { createHash } from 'node:crypto'; - import { extractFrameworkPackageName, frameworkPackages, frameworkToRenderer, getFrameworkName, } from 'storybook/internal/common'; +import { getAnonymousProjectId } from 'storybook/internal/telemetry'; import { type Options, SupportedBuilder } from 'storybook/internal/types'; export const buildFrameworkGlobalsFromOptions = async (options: Options) => { @@ -29,15 +28,10 @@ export const buildFrameworkGlobalsFromOptions = async (options: Options) => { globals.STORYBOOK_RENDERER = renderer; globals.STORYBOOK_NETWORK_ADDRESS = options.networkAddress; - // Compute a unique instance ID from the configDir so the manager can scope localStorage - // keys per Storybook instance. This prevents shared state across multiple projects. - // 12 hex characters (48 bits of entropy) is more than sufficient for uniqueness. - if (options.configDir) { - globals.STORYBOOK_INSTANCE_ID = createHash('sha256') - .update(options.configDir) - .digest('hex') - .slice(0, 12); - } + // Derive a stable, anonymous project ID from Git info (remote URL + working directory) so the + // manager can scope localStorage keys per Storybook instance. Falls back to 'anonymous' when + // Git info is unavailable (e.g. in non-Git projects or CI without Git). + globals.STORYBOOK_INSTANCE_ID = getAnonymousProjectId() ?? 'anonymous'; return globals; }; diff --git a/code/core/src/manager-api/store.ts b/code/core/src/manager-api/store.ts index fa099e1779d7..127b735f66c3 100644 --- a/code/core/src/manager-api/store.ts +++ b/code/core/src/manager-api/store.ts @@ -5,6 +5,7 @@ import store from 'store2'; import storeSetup from './lib/store-setup'; import type { State } from './root'; +import { version as currentVersion } from './version'; // setting up the store, overriding set and get to use telejson storeSetup(store._); @@ -13,8 +14,8 @@ const STORAGE_KEY_BASE = '@storybook/manager/store'; /** * Build the storage key, scoped per Storybook instance when possible. The STORYBOOK_INSTANCE_ID - * global is injected by the builder from a hash of configDir, which ensures each Storybook project - * uses its own localStorage namespace. + * global is injected by the builder from getAnonymousProjectId() (a Git-derived project hash). When + * unavailable, we use 'anonymous' to keep new data separate from the legacy shared key. */ function buildStorageKey(): string { try { @@ -23,25 +24,25 @@ function buildStorageKey(): string { return `${STORAGE_KEY_BASE}/${instanceId}`; } } catch { - // In edge cases (e.g. restricted globals), fall back to the shared base key + // In edge cases (e.g. restricted globals), fall back to anonymous } - return STORAGE_KEY_BASE; + return `${STORAGE_KEY_BASE}/anonymous`; } export const STORAGE_KEY = buildStorageKey(); -function get(storage: StoreAPI) { - let data = storage.get(STORAGE_KEY); - - // Migration: if no data exists under the instance-specific key, try the old shared key. - // This is a one-time migration that preserves existing settings when upgrading. - if (!data && STORAGE_KEY !== STORAGE_KEY_BASE) { - data = storage.get(STORAGE_KEY_BASE); - if (data) { - storage.set(STORAGE_KEY, data); - } - } +/** + * Key used to store the Storybook version alongside persisted data. This allows future migration + * logic to be version-aware when the storage format changes. + */ +const VERSION_KEY = `${STORAGE_KEY}/__version__`; +function persistVersion(storage: StoreAPI) { + storage.set(VERSION_KEY, currentVersion); +} + +function get(storage: StoreAPI) { + const data = storage.get(STORAGE_KEY); return data || {}; } @@ -98,7 +99,12 @@ export default class Store { // We don't only merge at the very top level (the same way as React setState) // when you set keys, so it makes sense to do the same in combining the two storage modes // Really, you shouldn't store the same key in both places - return { ...base, ...get(store.local), ...get(store.session) }; + const initialState = { ...base, ...get(store.local), ...get(store.session) }; + + // Record the current version in localStorage so future migrations can be version-aware + persistVersion(store.local); + + return initialState; } getState() { diff --git a/code/core/src/manager-api/tests/store.test.js b/code/core/src/manager-api/tests/store.test.js index 105c50a5faa4..7635521dfb5e 100644 --- a/code/core/src/manager-api/tests/store.test.js +++ b/code/core/src/manager-api/tests/store.test.js @@ -19,9 +19,21 @@ vi.mock('store2', () => ({ }, })); +vi.mock('../version', () => ({ version: '0.0.0-test' })); + describe('store', () => { - it('uses the base storage key when STORYBOOK_INSTANCE_ID is not set', () => { - expect(STORAGE_KEY).toBe('@storybook/manager/store'); + it('falls back to anonymous storage key when STORYBOOK_INSTANCE_ID is not set', () => { + expect(STORAGE_KEY).toBe('@storybook/manager/store/anonymous'); + }); + + it('persists the current version in localStorage on getInitialState', () => { + store2.session.get.mockReturnValueOnce({}); + store2.local.get.mockReturnValueOnce({}); + + const s = new Store({}); + s.getInitialState(); + + expect(store2.local.set).toHaveBeenCalledWith(`${STORAGE_KEY}/__version__`, '0.0.0-test'); }); it('sensibly combines local+session storage for initial state', () => { diff --git a/code/core/src/manager-api/tests/stories.test.ts b/code/core/src/manager-api/tests/stories.test.ts index 85a789f0cad7..9c66f891d074 100644 --- a/code/core/src/manager-api/tests/stories.test.ts +++ b/code/core/src/manager-api/tests/stories.test.ts @@ -1706,11 +1706,12 @@ describe('stories API', () => { const { store } = moduleArgs; await init!(); - await wait(16); - const { includedTagFilters, excludedTagFilters } = store.getState(); - expect(includedTagFilters).toEqual(['existing-tag']); - expect(excludedTagFilters).toEqual([]); + await vi.waitFor(() => { + const { includedTagFilters, excludedTagFilters } = store.getState(); + expect(includedTagFilters).toEqual(['existing-tag']); + expect(excludedTagFilters).toEqual([]); + }); }); it('keeps built-in filter tags even if they do not appear in entry tags', async () => { @@ -1745,11 +1746,12 @@ describe('stories API', () => { const { store } = moduleArgs; await init!(); - await wait(16); - const { includedTagFilters, excludedTagFilters } = store.getState(); - expect(includedTagFilters).toEqual(['_docs', '_play']); - expect(excludedTagFilters).toEqual(['_test']); + await vi.waitFor(() => { + const { includedTagFilters, excludedTagFilters } = store.getState(); + expect(includedTagFilters).toEqual(['_docs', '_play']); + expect(excludedTagFilters).toEqual(['_test']); + }); }); it('does not update state if all tag filters are valid', async () => { @@ -1782,9 +1784,7 @@ describe('stories API', () => { const { init } = initStories(moduleArgs as unknown as ModuleArgs); const { store } = moduleArgs; - const stateBeforeInit = store.getState(); await init!(); - await wait(16); // setState should have been called for setIndex but not for purging tag filters const setStateCalls = (store.setState as any).mock.calls; diff --git a/code/core/src/manager/typings.d.ts b/code/core/src/manager/typings.d.ts index 13e99af96c5a..4b67ef96c38e 100644 --- a/code/core/src/manager/typings.d.ts +++ b/code/core/src/manager/typings.d.ts @@ -35,8 +35,9 @@ declare var __STORYBOOK_ADDONS_CHANNEL__: any; declare var __STORYBOOK_TYPES__: any; declare var STORYBOOK_RENDERER: string | undefined; /** - * A short hash derived from the Storybook config directory path. Used to scope localStorage keys - * per Storybook instance so that persistence is not shared across different projects. + * A stable, anonymous project identifier derived from Git info (remote URL + working directory + * path). Used to scope localStorage keys per Storybook instance so that persistence is not shared + * across different projects. Falls back to 'anonymous' when Git info is unavailable. */ declare var STORYBOOK_INSTANCE_ID: string | undefined; declare var sendTelemetryError: (error: any) => void; diff --git a/code/core/src/telemetry/index.ts b/code/core/src/telemetry/index.ts index bfa5840eb1bd..ae9229c3ccaa 100644 --- a/code/core/src/telemetry/index.ts +++ b/code/core/src/telemetry/index.ts @@ -8,6 +8,8 @@ import type { EventType, Options, Payload, TelemetryData } from './types'; export { oneWayHash } from './one-way-hash'; +export { getAnonymousProjectId } from './anonymous-id'; + export * from './storybook-metadata'; export * from './types'; From 39d15de65d79ea9fcc398e88f0aed600b43c9b4a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 18:27:13 +0000 Subject: [PATCH 05/14] refactor: address review feedback - JSDoc comment, better variable names, simplify storage key Co-authored-by: Sidnioulz <5108577+Sidnioulz@users.noreply.github.com> --- .../src/builder-manager/utils/framework.ts | 7 ++++--- code/core/src/manager-api/modules/stories.ts | 13 ++++++------- code/core/src/manager-api/store.ts | 19 +++---------------- 3 files changed, 13 insertions(+), 26 deletions(-) diff --git a/code/core/src/builder-manager/utils/framework.ts b/code/core/src/builder-manager/utils/framework.ts index 726c117220e0..392faf743507 100644 --- a/code/core/src/builder-manager/utils/framework.ts +++ b/code/core/src/builder-manager/utils/framework.ts @@ -28,9 +28,10 @@ export const buildFrameworkGlobalsFromOptions = async (options: Options) => { globals.STORYBOOK_RENDERER = renderer; globals.STORYBOOK_NETWORK_ADDRESS = options.networkAddress; - // Derive a stable, anonymous project ID from Git info (remote URL + working directory) so the - // manager can scope localStorage keys per Storybook instance. Falls back to 'anonymous' when - // Git info is unavailable (e.g. in non-Git projects or CI without Git). + /** + * Anonymous project ID derived from Git info, falls back to `anonymous` when it can't be + * computed. + */ globals.STORYBOOK_INSTANCE_ID = getAnonymousProjectId() ?? 'anonymous'; return globals; diff --git a/code/core/src/manager-api/modules/stories.ts b/code/core/src/manager-api/modules/stories.ts index 586870aa889e..10e6d543e0bf 100644 --- a/code/core/src/manager-api/modules/stories.ts +++ b/code/core/src/manager-api/modules/stories.ts @@ -901,17 +901,16 @@ export const init: ModuleFn = ({ return; } - // Collect all tags present across every entry in the unfiltered index - const allTags = new Set(); + const allExistingTags = new Set(); for (const entry of Object.values(internal_index.entries)) { - entry.tags?.forEach((tag: string) => allTags.add(tag)); + entry.tags?.forEach((tag: string) => allExistingTags.add(tag)); } - // Keep a tag if it is a built-in filter key or actually appears in the index - const isValidTag = (tag: Tag) => Object.hasOwn(BUILT_IN_FILTERS, tag) || allTags.has(tag); + const isExistingTag = (tag: Tag) => + Object.hasOwn(BUILT_IN_FILTERS, tag) || allExistingTags.has(tag); - const validIncluded = includedTagFilters.filter(isValidTag); - const validExcluded = excludedTagFilters.filter(isValidTag); + const validIncluded = includedTagFilters.filter(isExistingTag); + const validExcluded = excludedTagFilters.filter(isExistingTag); const includedChanged = validIncluded.length !== includedTagFilters.length; const excludedChanged = validExcluded.length !== excludedTagFilters.length; diff --git a/code/core/src/manager-api/store.ts b/code/core/src/manager-api/store.ts index 127b735f66c3..d9a1c45e6506 100644 --- a/code/core/src/manager-api/store.ts +++ b/code/core/src/manager-api/store.ts @@ -13,23 +13,10 @@ storeSetup(store._); const STORAGE_KEY_BASE = '@storybook/manager/store'; /** - * Build the storage key, scoped per Storybook instance when possible. The STORYBOOK_INSTANCE_ID - * global is injected by the builder from getAnonymousProjectId() (a Git-derived project hash). When - * unavailable, we use 'anonymous' to keep new data separate from the legacy shared key. + * Storage key scoped per Storybook instance via `STORYBOOK_INSTANCE_ID` (a Git-derived project + * hash). */ -function buildStorageKey(): string { - try { - const instanceId = global.STORYBOOK_INSTANCE_ID; - if (instanceId) { - return `${STORAGE_KEY_BASE}/${instanceId}`; - } - } catch { - // In edge cases (e.g. restricted globals), fall back to anonymous - } - return `${STORAGE_KEY_BASE}/anonymous`; -} - -export const STORAGE_KEY = buildStorageKey(); +export const STORAGE_KEY = `${STORAGE_KEY_BASE}/${global.STORYBOOK_INSTANCE_ID || 'anonymous'}`; /** * Key used to store the Storybook version alongside persisted data. This allows future migration From 98d01ed7ce9dc00a898eec929d0e4363d235e8d6 Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Fri, 20 Mar 2026 18:04:58 +0100 Subject: [PATCH 06/14] Dedupe project id logic and avoid globals --- .../src/builder-manager/utils/framework.ts | 23 +-- code/core/src/manager-api/store.ts | 6 +- code/core/src/telemetry/anonymous-id.test.ts | 186 ++++++++++-------- 3 files changed, 113 insertions(+), 102 deletions(-) diff --git a/code/core/src/builder-manager/utils/framework.ts b/code/core/src/builder-manager/utils/framework.ts index 392faf743507..e5e47e33c01f 100644 --- a/code/core/src/builder-manager/utils/framework.ts +++ b/code/core/src/builder-manager/utils/framework.ts @@ -3,23 +3,26 @@ import { frameworkPackages, frameworkToRenderer, getFrameworkName, -} from 'storybook/internal/common'; -import { getAnonymousProjectId } from 'storybook/internal/telemetry'; -import { type Options, SupportedBuilder } from 'storybook/internal/types'; +} from "storybook/internal/common"; +import { type Options, SupportedBuilder } from "storybook/internal/types"; export const buildFrameworkGlobalsFromOptions = async (options: Options) => { const globals: Record = {}; - const { builder: builderConfig, channelOptions } = await options.presets.apply('core'); - const builderName = typeof builderConfig === 'string' ? builderConfig : builderConfig?.name; - const builder = Object.values(SupportedBuilder).find((builder) => builderName?.includes(builder)); + const { builder: builderConfig, channelOptions } = + await options.presets.apply("core"); + const builderName = + typeof builderConfig === "string" ? builderConfig : builderConfig?.name; + const builder = Object.values(SupportedBuilder).find((builder) => + builderName?.includes(builder), + ); const frameworkName = await getFrameworkName(options); const frameworkPackageName = extractFrameworkPackageName(frameworkName); const framework = frameworkPackages[frameworkPackageName]; const renderer = frameworkToRenderer[framework]; - if (options.configType === 'DEVELOPMENT') { + if (options.configType === "DEVELOPMENT") { // Manager only needs the token currently, so we don't pass any other channel options. globals.CHANNEL_OPTIONS = { wsToken: channelOptions?.wsToken }; } @@ -28,11 +31,5 @@ export const buildFrameworkGlobalsFromOptions = async (options: Options) => { globals.STORYBOOK_RENDERER = renderer; globals.STORYBOOK_NETWORK_ADDRESS = options.networkAddress; - /** - * Anonymous project ID derived from Git info, falls back to `anonymous` when it can't be - * computed. - */ - globals.STORYBOOK_INSTANCE_ID = getAnonymousProjectId() ?? 'anonymous'; - return globals; }; diff --git a/code/core/src/manager-api/store.ts b/code/core/src/manager-api/store.ts index d9a1c45e6506..33742796bbec 100644 --- a/code/core/src/manager-api/store.ts +++ b/code/core/src/manager-api/store.ts @@ -1,11 +1,11 @@ -import { global } from '@storybook/global'; - import type { StoreAPI } from 'store2'; import store from 'store2'; import storeSetup from './lib/store-setup'; import type { State } from './root'; import { version as currentVersion } from './version'; +import { getAnonymousProjectId } from 'storybook/internal/telemetry'; + // setting up the store, overriding set and get to use telejson storeSetup(store._); @@ -16,7 +16,7 @@ const STORAGE_KEY_BASE = '@storybook/manager/store'; * Storage key scoped per Storybook instance via `STORYBOOK_INSTANCE_ID` (a Git-derived project * hash). */ -export const STORAGE_KEY = `${STORAGE_KEY_BASE}/${global.STORYBOOK_INSTANCE_ID || 'anonymous'}`; +export const STORAGE_KEY = `${STORAGE_KEY_BASE}/${getAnonymousProjectId() || 'anonymous'}`; /** * Key used to store the Storybook version alongside persisted data. This allows future migration diff --git a/code/core/src/telemetry/anonymous-id.test.ts b/code/core/src/telemetry/anonymous-id.test.ts index cdee088797ab..148046633b40 100644 --- a/code/core/src/telemetry/anonymous-id.test.ts +++ b/code/core/src/telemetry/anonymous-id.test.ts @@ -1,21 +1,21 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from "vitest"; -import { executeCommandSync } from 'storybook/internal/common'; +import { executeCommandSync } from "storybook/internal/common"; import { getAnonymousProjectId, getProjectSince, normalizeGitUrl, unhashedProjectId, -} from './anonymous-id'; +} from "./anonymous-id"; -vi.mock(import('storybook/internal/common'), async (actualModule) => { +vi.mock(import("storybook/internal/common"), async (actualModule) => { const actual = await actualModule(); return { ...actual, executeCommandSync: vi.fn(actual.executeCommandSync), - getProjectRoot: () => '/path/to/project/root', + getProjectRoot: () => "/path/to/project/root", }; }); @@ -23,163 +23,177 @@ beforeEach(() => { vi.mocked(executeCommandSync).mockReset(); }); -describe('normalizeGitUrl', () => { - it('trims off https://', () => { - expect(normalizeGitUrl('https://github.com/storybookjs/storybook.git')).toEqual( - 'github.com/storybookjs/storybook.git' - ); +describe("normalizeGitUrl", () => { + it("trims off https://", () => { + expect( + normalizeGitUrl("https://github.com/storybookjs/storybook.git"), + ).toEqual("github.com/storybookjs/storybook.git"); }); - it('trims off http://', () => { - expect(normalizeGitUrl('http://github.com/storybookjs/storybook.git')).toEqual( - 'github.com/storybookjs/storybook.git' - ); + it("trims off http://", () => { + expect( + normalizeGitUrl("http://github.com/storybookjs/storybook.git"), + ).toEqual("github.com/storybookjs/storybook.git"); }); - it('trims off git+https://', () => { - expect(normalizeGitUrl('git+https://github.com/storybookjs/storybook.git')).toEqual( - 'github.com/storybookjs/storybook.git' - ); + it("trims off git+https://", () => { + expect( + normalizeGitUrl("git+https://github.com/storybookjs/storybook.git"), + ).toEqual("github.com/storybookjs/storybook.git"); }); - it('trims off https://username@', () => { - expect(normalizeGitUrl('https://username@github.com/storybookjs/storybook.git')).toEqual( - 'github.com/storybookjs/storybook.git' - ); + it("trims off https://username@", () => { + expect( + normalizeGitUrl("https://username@github.com/storybookjs/storybook.git"), + ).toEqual("github.com/storybookjs/storybook.git"); }); - it('trims off http://username@', () => { - expect(normalizeGitUrl('http://username@github.com/storybookjs/storybook.git')).toEqual( - 'github.com/storybookjs/storybook.git' - ); + it("trims off http://username@", () => { + expect( + normalizeGitUrl("http://username@github.com/storybookjs/storybook.git"), + ).toEqual("github.com/storybookjs/storybook.git"); }); - it('trims off https://username:password@', () => { + it("trims off https://username:password@", () => { expect( - normalizeGitUrl('https://username:password@github.com/storybookjs/storybook.git') - ).toEqual('github.com/storybookjs/storybook.git'); + normalizeGitUrl( + "https://username:password@github.com/storybookjs/storybook.git", + ), + ).toEqual("github.com/storybookjs/storybook.git"); }); - it('trims off http://username:password@', () => { + it("trims off http://username:password@", () => { expect( - normalizeGitUrl('http://username:password@github.com/storybookjs/storybook.git') - ).toEqual('github.com/storybookjs/storybook.git'); + normalizeGitUrl( + "http://username:password@github.com/storybookjs/storybook.git", + ), + ).toEqual("github.com/storybookjs/storybook.git"); }); - it('trims off git://', () => { - expect(normalizeGitUrl('git://github.com/storybookjs/storybook.git')).toEqual( - 'github.com/storybookjs/storybook.git' - ); + it("trims off git://", () => { + expect( + normalizeGitUrl("git://github.com/storybookjs/storybook.git"), + ).toEqual("github.com/storybookjs/storybook.git"); }); - it('trims off git@', () => { - expect(normalizeGitUrl('git@github.com:storybookjs/storybook.git')).toEqual( - 'github.com/storybookjs/storybook.git' + it("trims off git@", () => { + expect(normalizeGitUrl("git@github.com:storybookjs/storybook.git")).toEqual( + "github.com/storybookjs/storybook.git", ); }); - it('trims off git+ssh://git@', () => { - expect(normalizeGitUrl('git+ssh://git@github.com:storybookjs/storybook.git')).toEqual( - 'github.com/storybookjs/storybook.git' - ); + it("trims off git+ssh://git@", () => { + expect( + normalizeGitUrl("git+ssh://git@github.com:storybookjs/storybook.git"), + ).toEqual("github.com/storybookjs/storybook.git"); }); - it('trims off ssh://git@', () => { - expect(normalizeGitUrl('ssh://git@github.com:storybookjs/storybook.git')).toEqual( - 'github.com/storybookjs/storybook.git' - ); + it("trims off ssh://git@", () => { + expect( + normalizeGitUrl("ssh://git@github.com:storybookjs/storybook.git"), + ).toEqual("github.com/storybookjs/storybook.git"); }); - it('adds .git if missing', () => { - expect(normalizeGitUrl('https://github.com/storybookjs/storybook')).toEqual( - 'github.com/storybookjs/storybook.git' + it("adds .git if missing", () => { + expect(normalizeGitUrl("https://github.com/storybookjs/storybook")).toEqual( + "github.com/storybookjs/storybook.git", ); }); - it('trims off #hash', () => { - expect(normalizeGitUrl('https://github.com/storybookjs/storybook.git#next')).toEqual( - 'github.com/storybookjs/storybook.git' - ); + it("trims off #hash", () => { + expect( + normalizeGitUrl("https://github.com/storybookjs/storybook.git#next"), + ).toEqual("github.com/storybookjs/storybook.git"); }); - it('trims off extra whitespace', () => { - expect(normalizeGitUrl('https://github.com/storybookjs/storybook.git#next\n')).toEqual( - 'github.com/storybookjs/storybook.git' - ); + it("trims off extra whitespace", () => { + expect( + normalizeGitUrl("https://github.com/storybookjs/storybook.git#next\n"), + ).toEqual("github.com/storybookjs/storybook.git"); - expect(normalizeGitUrl('https://github.com/storybookjs/storybook.git\n')).toEqual( - 'github.com/storybookjs/storybook.git' - ); + expect( + normalizeGitUrl("https://github.com/storybookjs/storybook.git\n"), + ).toEqual("github.com/storybookjs/storybook.git"); }); }); -describe('unhashedProjectId', () => { - it('does not touch unix paths', () => { +describe("unhashedProjectId", () => { + it("does not touch unix paths", () => { expect( - unhashedProjectId('https://github.com/storybookjs/storybook.git\n', 'path/to/storybook') - ).toBe('github.com/storybookjs/storybook.gitpath/to/storybook'); + unhashedProjectId( + "https://github.com/storybookjs/storybook.git\n", + "path/to/storybook", + ), + ).toBe("github.com/storybookjs/storybook.gitpath/to/storybook"); }); - it('normalizes windows paths', () => { + it("normalizes windows paths", () => { expect( - unhashedProjectId('https://github.com/storybookjs/storybook.git\n', 'path\\to\\storybook') - ).toBe('github.com/storybookjs/storybook.gitpath/to/storybook'); + unhashedProjectId( + "https://github.com/storybookjs/storybook.git\n", + "path\\to\\storybook", + ), + ).toBe("github.com/storybookjs/storybook.gitpath/to/storybook"); }); }); -describe('getProjectSince', () => { +describe("getProjectSince", () => { beforeEach(() => { vi.clearAllMocks(); vi.resetModules(); }); - it('returns the Storybook creation date from git log output', () => { + it("returns the Storybook creation date from git log output", () => { vi.mocked(executeCommandSync).mockReturnValue( - '2025-12-11 16:24:01 +0530\n' + '2014-12-11 19:09:10 +0530' + "2025-12-11 16:24:01 +0530\n" + "2014-12-11 19:09:10 +0530", ); - expect(getProjectSince()).toEqual(new Date('2025-12-11T10:54:01.000Z')); + expect(getProjectSince()).toEqual(new Date("2025-12-11T10:54:01.000Z")); }); - it('returns undefined if git log output is empty', async () => { - vi.mocked(executeCommandSync).mockReturnValue(''); + it("returns undefined if git log output is empty", async () => { + vi.mocked(executeCommandSync).mockReturnValue(""); - const { getProjectSince: getProjSince } = await import('./anonymous-id'); + const { getProjectSince: getProjSince } = await import("./anonymous-id"); expect(getProjSince()).toBeUndefined(); }); - it('returns undefined if git log fails', async () => { + it("returns undefined if git log fails", async () => { vi.mocked(executeCommandSync).mockImplementation(() => { - throw new Error('git not available'); + throw new Error("git not available"); }); - const { getProjectSince: getProjSince } = await import('./anonymous-id'); + const { getProjectSince: getProjSince } = await import("./anonymous-id"); expect(getProjSince()).toBeUndefined(); }); }); -describe('getAnonymousProjectId', () => { +describe("getAnonymousProjectId", () => { beforeEach(() => { vi.clearAllMocks(); vi.resetModules(); - vi.spyOn(process, 'cwd').mockReturnValue('/path/to/project/root'); + vi.spyOn(process, "cwd").mockReturnValue("/path/to/project/root"); }); - it('returns hashed project id for Storybook repo when git command succeeds', async () => { - vi.mocked(executeCommandSync).mockReturnValue('git@github.com:storybookjs/storybook.git'); + it("returns hashed project id for Storybook repo when git command succeeds", async () => { + vi.mocked(executeCommandSync).mockReturnValue( + "git@github.com:storybookjs/storybook.git", + ); const result = getAnonymousProjectId(); - expect(result).toMatch('061e4ee22a1f7c079849d97234b3be94d016fb1f24ba11878c41f8b48c0213bf'); + expect(result).toMatch( + "061e4ee22a1f7c079849d97234b3be94d016fb1f24ba11878c41f8b48c0213bf", + ); }); - it('returns undefined when git command fails', async () => { - const { getAnonymousProjectId: getAnonId } = await import('./anonymous-id'); + it("returns undefined when git command fails", async () => { + const { getAnonymousProjectId: getAnonId } = await import("./anonymous-id"); vi.mocked(executeCommandSync).mockImplementation(() => { - throw new Error('git not available'); + throw new Error("git not available"); }); const result = getAnonId(); From e9bde7cd0ff83de9a330b85f9e942da5672f1a45 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 17:20:13 +0000 Subject: [PATCH 07/14] Split STORAGE_KEY test into two: mock telemetry to test project ID vs anonymous fallback --- code/core/src/manager-api/tests/store.test.js | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/code/core/src/manager-api/tests/store.test.js b/code/core/src/manager-api/tests/store.test.js index 7635521dfb5e..0e23b613c524 100644 --- a/code/core/src/manager-api/tests/store.test.js +++ b/code/core/src/manager-api/tests/store.test.js @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import flushPromises from 'flush-promises'; import store2 from 'store2'; @@ -21,9 +21,31 @@ vi.mock('store2', () => ({ vi.mock('../version', () => ({ version: '0.0.0-test' })); +vi.mock('storybook/internal/telemetry', () => ({ + getAnonymousProjectId: vi.fn(), +})); + describe('store', () => { - it('falls back to anonymous storage key when STORYBOOK_INSTANCE_ID is not set', () => { - expect(STORAGE_KEY).toBe('@storybook/manager/store/anonymous'); + describe('STORAGE_KEY', () => { + beforeEach(() => { + vi.resetModules(); + }); + + it('scopes storage key to the project ID when available', async () => { + vi.doMock('storybook/internal/telemetry', () => ({ + getAnonymousProjectId: () => 'test-project-id', + })); + const { STORAGE_KEY: key } = await import('../store'); + expect(key).toBe('@storybook/manager/store/test-project-id'); + }); + + it('falls back to anonymous when project ID is unavailable', async () => { + vi.doMock('storybook/internal/telemetry', () => ({ + getAnonymousProjectId: () => undefined, + })); + const { STORAGE_KEY: key } = await import('../store'); + expect(key).toBe('@storybook/manager/store/anonymous'); + }); }); it('persists the current version in localStorage on getInitialState', () => { From a381d845f221c1f9a2987974a430dcdd4dc51014 Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Mon, 23 Mar 2026 20:26:01 +0100 Subject: [PATCH 08/14] style: format files --- .../src/builder-manager/utils/framework.ts | 16 +- code/core/src/manager-api/store.ts | 1 - code/core/src/telemetry/anonymous-id.test.ts | 186 ++++++++---------- 3 files changed, 92 insertions(+), 111 deletions(-) diff --git a/code/core/src/builder-manager/utils/framework.ts b/code/core/src/builder-manager/utils/framework.ts index e5e47e33c01f..df89e68a6336 100644 --- a/code/core/src/builder-manager/utils/framework.ts +++ b/code/core/src/builder-manager/utils/framework.ts @@ -3,26 +3,22 @@ import { frameworkPackages, frameworkToRenderer, getFrameworkName, -} from "storybook/internal/common"; -import { type Options, SupportedBuilder } from "storybook/internal/types"; +} from 'storybook/internal/common'; +import { type Options, SupportedBuilder } from 'storybook/internal/types'; export const buildFrameworkGlobalsFromOptions = async (options: Options) => { const globals: Record = {}; - const { builder: builderConfig, channelOptions } = - await options.presets.apply("core"); - const builderName = - typeof builderConfig === "string" ? builderConfig : builderConfig?.name; - const builder = Object.values(SupportedBuilder).find((builder) => - builderName?.includes(builder), - ); + const { builder: builderConfig, channelOptions } = await options.presets.apply('core'); + const builderName = typeof builderConfig === 'string' ? builderConfig : builderConfig?.name; + const builder = Object.values(SupportedBuilder).find((builder) => builderName?.includes(builder)); const frameworkName = await getFrameworkName(options); const frameworkPackageName = extractFrameworkPackageName(frameworkName); const framework = frameworkPackages[frameworkPackageName]; const renderer = frameworkToRenderer[framework]; - if (options.configType === "DEVELOPMENT") { + if (options.configType === 'DEVELOPMENT') { // Manager only needs the token currently, so we don't pass any other channel options. globals.CHANNEL_OPTIONS = { wsToken: channelOptions?.wsToken }; } diff --git a/code/core/src/manager-api/store.ts b/code/core/src/manager-api/store.ts index 33742796bbec..a92748e67705 100644 --- a/code/core/src/manager-api/store.ts +++ b/code/core/src/manager-api/store.ts @@ -6,7 +6,6 @@ import type { State } from './root'; import { version as currentVersion } from './version'; import { getAnonymousProjectId } from 'storybook/internal/telemetry'; - // setting up the store, overriding set and get to use telejson storeSetup(store._); diff --git a/code/core/src/telemetry/anonymous-id.test.ts b/code/core/src/telemetry/anonymous-id.test.ts index 148046633b40..cdee088797ab 100644 --- a/code/core/src/telemetry/anonymous-id.test.ts +++ b/code/core/src/telemetry/anonymous-id.test.ts @@ -1,21 +1,21 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { executeCommandSync } from "storybook/internal/common"; +import { executeCommandSync } from 'storybook/internal/common'; import { getAnonymousProjectId, getProjectSince, normalizeGitUrl, unhashedProjectId, -} from "./anonymous-id"; +} from './anonymous-id'; -vi.mock(import("storybook/internal/common"), async (actualModule) => { +vi.mock(import('storybook/internal/common'), async (actualModule) => { const actual = await actualModule(); return { ...actual, executeCommandSync: vi.fn(actual.executeCommandSync), - getProjectRoot: () => "/path/to/project/root", + getProjectRoot: () => '/path/to/project/root', }; }); @@ -23,177 +23,163 @@ beforeEach(() => { vi.mocked(executeCommandSync).mockReset(); }); -describe("normalizeGitUrl", () => { - it("trims off https://", () => { - expect( - normalizeGitUrl("https://github.com/storybookjs/storybook.git"), - ).toEqual("github.com/storybookjs/storybook.git"); +describe('normalizeGitUrl', () => { + it('trims off https://', () => { + expect(normalizeGitUrl('https://github.com/storybookjs/storybook.git')).toEqual( + 'github.com/storybookjs/storybook.git' + ); }); - it("trims off http://", () => { - expect( - normalizeGitUrl("http://github.com/storybookjs/storybook.git"), - ).toEqual("github.com/storybookjs/storybook.git"); + it('trims off http://', () => { + expect(normalizeGitUrl('http://github.com/storybookjs/storybook.git')).toEqual( + 'github.com/storybookjs/storybook.git' + ); }); - it("trims off git+https://", () => { - expect( - normalizeGitUrl("git+https://github.com/storybookjs/storybook.git"), - ).toEqual("github.com/storybookjs/storybook.git"); + it('trims off git+https://', () => { + expect(normalizeGitUrl('git+https://github.com/storybookjs/storybook.git')).toEqual( + 'github.com/storybookjs/storybook.git' + ); }); - it("trims off https://username@", () => { - expect( - normalizeGitUrl("https://username@github.com/storybookjs/storybook.git"), - ).toEqual("github.com/storybookjs/storybook.git"); + it('trims off https://username@', () => { + expect(normalizeGitUrl('https://username@github.com/storybookjs/storybook.git')).toEqual( + 'github.com/storybookjs/storybook.git' + ); }); - it("trims off http://username@", () => { - expect( - normalizeGitUrl("http://username@github.com/storybookjs/storybook.git"), - ).toEqual("github.com/storybookjs/storybook.git"); + it('trims off http://username@', () => { + expect(normalizeGitUrl('http://username@github.com/storybookjs/storybook.git')).toEqual( + 'github.com/storybookjs/storybook.git' + ); }); - it("trims off https://username:password@", () => { + it('trims off https://username:password@', () => { expect( - normalizeGitUrl( - "https://username:password@github.com/storybookjs/storybook.git", - ), - ).toEqual("github.com/storybookjs/storybook.git"); + normalizeGitUrl('https://username:password@github.com/storybookjs/storybook.git') + ).toEqual('github.com/storybookjs/storybook.git'); }); - it("trims off http://username:password@", () => { + it('trims off http://username:password@', () => { expect( - normalizeGitUrl( - "http://username:password@github.com/storybookjs/storybook.git", - ), - ).toEqual("github.com/storybookjs/storybook.git"); + normalizeGitUrl('http://username:password@github.com/storybookjs/storybook.git') + ).toEqual('github.com/storybookjs/storybook.git'); }); - it("trims off git://", () => { - expect( - normalizeGitUrl("git://github.com/storybookjs/storybook.git"), - ).toEqual("github.com/storybookjs/storybook.git"); + it('trims off git://', () => { + expect(normalizeGitUrl('git://github.com/storybookjs/storybook.git')).toEqual( + 'github.com/storybookjs/storybook.git' + ); }); - it("trims off git@", () => { - expect(normalizeGitUrl("git@github.com:storybookjs/storybook.git")).toEqual( - "github.com/storybookjs/storybook.git", + it('trims off git@', () => { + expect(normalizeGitUrl('git@github.com:storybookjs/storybook.git')).toEqual( + 'github.com/storybookjs/storybook.git' ); }); - it("trims off git+ssh://git@", () => { - expect( - normalizeGitUrl("git+ssh://git@github.com:storybookjs/storybook.git"), - ).toEqual("github.com/storybookjs/storybook.git"); + it('trims off git+ssh://git@', () => { + expect(normalizeGitUrl('git+ssh://git@github.com:storybookjs/storybook.git')).toEqual( + 'github.com/storybookjs/storybook.git' + ); }); - it("trims off ssh://git@", () => { - expect( - normalizeGitUrl("ssh://git@github.com:storybookjs/storybook.git"), - ).toEqual("github.com/storybookjs/storybook.git"); + it('trims off ssh://git@', () => { + expect(normalizeGitUrl('ssh://git@github.com:storybookjs/storybook.git')).toEqual( + 'github.com/storybookjs/storybook.git' + ); }); - it("adds .git if missing", () => { - expect(normalizeGitUrl("https://github.com/storybookjs/storybook")).toEqual( - "github.com/storybookjs/storybook.git", + it('adds .git if missing', () => { + expect(normalizeGitUrl('https://github.com/storybookjs/storybook')).toEqual( + 'github.com/storybookjs/storybook.git' ); }); - it("trims off #hash", () => { - expect( - normalizeGitUrl("https://github.com/storybookjs/storybook.git#next"), - ).toEqual("github.com/storybookjs/storybook.git"); + it('trims off #hash', () => { + expect(normalizeGitUrl('https://github.com/storybookjs/storybook.git#next')).toEqual( + 'github.com/storybookjs/storybook.git' + ); }); - it("trims off extra whitespace", () => { - expect( - normalizeGitUrl("https://github.com/storybookjs/storybook.git#next\n"), - ).toEqual("github.com/storybookjs/storybook.git"); + it('trims off extra whitespace', () => { + expect(normalizeGitUrl('https://github.com/storybookjs/storybook.git#next\n')).toEqual( + 'github.com/storybookjs/storybook.git' + ); - expect( - normalizeGitUrl("https://github.com/storybookjs/storybook.git\n"), - ).toEqual("github.com/storybookjs/storybook.git"); + expect(normalizeGitUrl('https://github.com/storybookjs/storybook.git\n')).toEqual( + 'github.com/storybookjs/storybook.git' + ); }); }); -describe("unhashedProjectId", () => { - it("does not touch unix paths", () => { +describe('unhashedProjectId', () => { + it('does not touch unix paths', () => { expect( - unhashedProjectId( - "https://github.com/storybookjs/storybook.git\n", - "path/to/storybook", - ), - ).toBe("github.com/storybookjs/storybook.gitpath/to/storybook"); + unhashedProjectId('https://github.com/storybookjs/storybook.git\n', 'path/to/storybook') + ).toBe('github.com/storybookjs/storybook.gitpath/to/storybook'); }); - it("normalizes windows paths", () => { + it('normalizes windows paths', () => { expect( - unhashedProjectId( - "https://github.com/storybookjs/storybook.git\n", - "path\\to\\storybook", - ), - ).toBe("github.com/storybookjs/storybook.gitpath/to/storybook"); + unhashedProjectId('https://github.com/storybookjs/storybook.git\n', 'path\\to\\storybook') + ).toBe('github.com/storybookjs/storybook.gitpath/to/storybook'); }); }); -describe("getProjectSince", () => { +describe('getProjectSince', () => { beforeEach(() => { vi.clearAllMocks(); vi.resetModules(); }); - it("returns the Storybook creation date from git log output", () => { + it('returns the Storybook creation date from git log output', () => { vi.mocked(executeCommandSync).mockReturnValue( - "2025-12-11 16:24:01 +0530\n" + "2014-12-11 19:09:10 +0530", + '2025-12-11 16:24:01 +0530\n' + '2014-12-11 19:09:10 +0530' ); - expect(getProjectSince()).toEqual(new Date("2025-12-11T10:54:01.000Z")); + expect(getProjectSince()).toEqual(new Date('2025-12-11T10:54:01.000Z')); }); - it("returns undefined if git log output is empty", async () => { - vi.mocked(executeCommandSync).mockReturnValue(""); + it('returns undefined if git log output is empty', async () => { + vi.mocked(executeCommandSync).mockReturnValue(''); - const { getProjectSince: getProjSince } = await import("./anonymous-id"); + const { getProjectSince: getProjSince } = await import('./anonymous-id'); expect(getProjSince()).toBeUndefined(); }); - it("returns undefined if git log fails", async () => { + it('returns undefined if git log fails', async () => { vi.mocked(executeCommandSync).mockImplementation(() => { - throw new Error("git not available"); + throw new Error('git not available'); }); - const { getProjectSince: getProjSince } = await import("./anonymous-id"); + const { getProjectSince: getProjSince } = await import('./anonymous-id'); expect(getProjSince()).toBeUndefined(); }); }); -describe("getAnonymousProjectId", () => { +describe('getAnonymousProjectId', () => { beforeEach(() => { vi.clearAllMocks(); vi.resetModules(); - vi.spyOn(process, "cwd").mockReturnValue("/path/to/project/root"); + vi.spyOn(process, 'cwd').mockReturnValue('/path/to/project/root'); }); - it("returns hashed project id for Storybook repo when git command succeeds", async () => { - vi.mocked(executeCommandSync).mockReturnValue( - "git@github.com:storybookjs/storybook.git", - ); + it('returns hashed project id for Storybook repo when git command succeeds', async () => { + vi.mocked(executeCommandSync).mockReturnValue('git@github.com:storybookjs/storybook.git'); const result = getAnonymousProjectId(); - expect(result).toMatch( - "061e4ee22a1f7c079849d97234b3be94d016fb1f24ba11878c41f8b48c0213bf", - ); + expect(result).toMatch('061e4ee22a1f7c079849d97234b3be94d016fb1f24ba11878c41f8b48c0213bf'); }); - it("returns undefined when git command fails", async () => { - const { getAnonymousProjectId: getAnonId } = await import("./anonymous-id"); + it('returns undefined when git command fails', async () => { + const { getAnonymousProjectId: getAnonId } = await import('./anonymous-id'); vi.mocked(executeCommandSync).mockImplementation(() => { - throw new Error("git not available"); + throw new Error('git not available'); }); const result = getAnonId(); From f6aeb66ecbb5067c13b03175c6d102b0e7404bbe Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Mon, 23 Mar 2026 20:41:10 +0100 Subject: [PATCH 09/14] Clean up after agent --- code/core/src/manager-api/store.ts | 7 ++++--- code/core/src/manager-api/typings.d.ts | 1 - code/core/src/manager/typings.d.ts | 6 ------ 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/code/core/src/manager-api/store.ts b/code/core/src/manager-api/store.ts index a92748e67705..3c12a98ef389 100644 --- a/code/core/src/manager-api/store.ts +++ b/code/core/src/manager-api/store.ts @@ -12,8 +12,7 @@ storeSetup(store._); const STORAGE_KEY_BASE = '@storybook/manager/store'; /** - * Storage key scoped per Storybook instance via `STORYBOOK_INSTANCE_ID` (a Git-derived project - * hash). + * Storage key scoped per Storybook instance (a Git-derived project hash). */ export const STORAGE_KEY = `${STORAGE_KEY_BASE}/${getAnonymousProjectId() || 'anonymous'}`; @@ -88,7 +87,9 @@ export default class Store { const initialState = { ...base, ...get(store.local), ...get(store.session) }; // Record the current version in localStorage so future migrations can be version-aware - persistVersion(store.local); + if (this.upstreamPersistence) { + persistVersion(store.local); + } return initialState; } diff --git a/code/core/src/manager-api/typings.d.ts b/code/core/src/manager-api/typings.d.ts index a4043e9265f9..7bf67c97fc33 100644 --- a/code/core/src/manager-api/typings.d.ts +++ b/code/core/src/manager-api/typings.d.ts @@ -10,4 +10,3 @@ declare var STORYBOOK_ADDON_STATE: Record; declare var STORYBOOK_FRAMEWORK: import('storybook/internal/types').SupportedFramework | undefined; declare var STORYBOOK_RENDERER: import('storybook/internal/types').SupportedRenderer | undefined; declare var STORYBOOK_BUILDER: import('storybook/internal/types').SupportedBuilder | undefined; -declare var STORYBOOK_INSTANCE_ID: string | undefined; diff --git a/code/core/src/manager/typings.d.ts b/code/core/src/manager/typings.d.ts index 4b67ef96c38e..fb3fa90c7557 100644 --- a/code/core/src/manager/typings.d.ts +++ b/code/core/src/manager/typings.d.ts @@ -34,10 +34,4 @@ declare var __STORYBOOK_CLIENT_LOGGER__: any; declare var __STORYBOOK_ADDONS_CHANNEL__: any; declare var __STORYBOOK_TYPES__: any; declare var STORYBOOK_RENDERER: string | undefined; -/** - * A stable, anonymous project identifier derived from Git info (remote URL + working directory - * path). Used to scope localStorage keys per Storybook instance so that persistence is not shared - * across different projects. Falls back to 'anonymous' when Git info is unavailable. - */ -declare var STORYBOOK_INSTANCE_ID: string | undefined; declare var sendTelemetryError: (error: any) => void; From 690f85667dbafda80d94097dbae4f04f257d43fd Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Mon, 23 Mar 2026 21:14:05 +0100 Subject: [PATCH 10/14] Pass instance ID via globals to not load Node in manager --- .../src/builder-manager/utils/framework.ts | 2 + code/core/src/manager-api/store.ts | 13 ++++-- code/core/src/manager-api/tests/store.test.js | 44 ++++++++++++------- code/core/src/manager-api/typings.d.ts | 1 + code/core/src/manager/typings.d.ts | 1 + code/core/src/typings.d.ts | 1 + 6 files changed, 42 insertions(+), 20 deletions(-) diff --git a/code/core/src/builder-manager/utils/framework.ts b/code/core/src/builder-manager/utils/framework.ts index df89e68a6336..68924bfbda85 100644 --- a/code/core/src/builder-manager/utils/framework.ts +++ b/code/core/src/builder-manager/utils/framework.ts @@ -4,6 +4,7 @@ import { frameworkToRenderer, getFrameworkName, } from 'storybook/internal/common'; +import { getAnonymousProjectId } from 'storybook/internal/telemetry'; import { type Options, SupportedBuilder } from 'storybook/internal/types'; export const buildFrameworkGlobalsFromOptions = async (options: Options) => { @@ -26,6 +27,7 @@ export const buildFrameworkGlobalsFromOptions = async (options: Options) => { globals.STORYBOOK_FRAMEWORK = framework; globals.STORYBOOK_RENDERER = renderer; globals.STORYBOOK_NETWORK_ADDRESS = options.networkAddress; + globals.STORYBOOK_ANONYMOUS_PROJECT_ID = getAnonymousProjectId(); return globals; }; diff --git a/code/core/src/manager-api/store.ts b/code/core/src/manager-api/store.ts index 3c12a98ef389..d7057fa50bbb 100644 --- a/code/core/src/manager-api/store.ts +++ b/code/core/src/manager-api/store.ts @@ -4,7 +4,6 @@ import store from 'store2'; import storeSetup from './lib/store-setup'; import type { State } from './root'; import { version as currentVersion } from './version'; -import { getAnonymousProjectId } from 'storybook/internal/telemetry'; // setting up the store, overriding set and get to use telejson storeSetup(store._); @@ -12,9 +11,11 @@ storeSetup(store._); const STORAGE_KEY_BASE = '@storybook/manager/store'; /** - * Storage key scoped per Storybook instance (a Git-derived project hash). + * Storage key scoped per Storybook instance (a Git-derived project hash). The project ID is + * injected as a window global by the manager builder at build time so the manager (a browser app) + * does not need to depend on Node-only telemetry code. */ -export const STORAGE_KEY = `${STORAGE_KEY_BASE}/${getAnonymousProjectId() || 'anonymous'}`; +export const STORAGE_KEY = `${STORAGE_KEY_BASE}/${globalThis.STORYBOOK_ANONYMOUS_PROJECT_ID || 'anonymous'}`; /** * Key used to store the Storybook version alongside persisted data. This allows future migration @@ -84,7 +85,11 @@ export default class Store { // We don't only merge at the very top level (the same way as React setState) // when you set keys, so it makes sense to do the same in combining the two storage modes // Really, you shouldn't store the same key in both places - const initialState = { ...base, ...get(store.local), ...get(store.session) }; + const initialState = { + ...base, + ...get(store.local), + ...get(store.session), + }; // Record the current version in localStorage so future migrations can be version-aware if (this.upstreamPersistence) { diff --git a/code/core/src/manager-api/tests/store.test.js b/code/core/src/manager-api/tests/store.test.js index 0e23b613c524..27c0c4d5861f 100644 --- a/code/core/src/manager-api/tests/store.test.js +++ b/code/core/src/manager-api/tests/store.test.js @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import flushPromises from 'flush-promises'; import store2 from 'store2'; @@ -21,28 +21,27 @@ vi.mock('store2', () => ({ vi.mock('../version', () => ({ version: '0.0.0-test' })); -vi.mock('storybook/internal/telemetry', () => ({ - getAnonymousProjectId: vi.fn(), -})); - describe('store', () => { describe('STORAGE_KEY', () => { + let originalProjectId; + beforeEach(() => { vi.resetModules(); + originalProjectId = globalThis.STORYBOOK_ANONYMOUS_PROJECT_ID; + }); + + afterEach(() => { + globalThis.STORYBOOK_ANONYMOUS_PROJECT_ID = originalProjectId; }); it('scopes storage key to the project ID when available', async () => { - vi.doMock('storybook/internal/telemetry', () => ({ - getAnonymousProjectId: () => 'test-project-id', - })); + globalThis.STORYBOOK_ANONYMOUS_PROJECT_ID = 'test-project-id'; const { STORAGE_KEY: key } = await import('../store'); expect(key).toBe('@storybook/manager/store/test-project-id'); }); it('falls back to anonymous when project ID is unavailable', async () => { - vi.doMock('storybook/internal/telemetry', () => ({ - getAnonymousProjectId: () => undefined, - })); + globalThis.STORYBOOK_ANONYMOUS_PROJECT_ID = undefined; const { STORAGE_KEY: key } = await import('../store'); expect(key).toBe('@storybook/manager/store/anonymous'); }); @@ -59,8 +58,15 @@ describe('store', () => { }); it('sensibly combines local+session storage for initial state', () => { - store2.session.get.mockReturnValueOnce({ foo: 'bar', combined: { a: 'b' } }); - store2.local.get.mockReturnValueOnce({ foo: 'baz', another: 'value', combined: { c: 'd' } }); + store2.session.get.mockReturnValueOnce({ + foo: 'bar', + combined: { a: 'b' }, + }); + store2.local.get.mockReturnValueOnce({ + foo: 'baz', + another: 'value', + combined: { c: 'd' }, + }); const store = new Store({}); expect(store.getInitialState()).toEqual({ @@ -101,7 +107,9 @@ describe('store', () => { await store.setState({ foo: 'bar' }, { persistence: 'session' }); expect(setState).toHaveBeenCalledWith({ foo: 'bar' }, expect.any(Function)); - expect(store2.session.set).toHaveBeenCalledWith(STORAGE_KEY, { foo: 'bar' }); + expect(store2.session.set).toHaveBeenCalledWith(STORAGE_KEY, { + foo: 'bar', + }); expect(store2.local.set).not.toHaveBeenCalled(); }); @@ -114,7 +122,9 @@ describe('store', () => { expect(setState).toHaveBeenCalledWith({ foo: 'bar' }, expect.any(Function)); expect(store2.session.set).not.toHaveBeenCalled(); - expect(store2.local.set).toHaveBeenCalledWith(STORAGE_KEY, { foo: 'bar' }); + expect(store2.local.set).toHaveBeenCalledWith(STORAGE_KEY, { + foo: 'bar', + }); }); it('properly patches existing values', async () => { @@ -197,7 +207,9 @@ describe('store', () => { await store.setState(patch, { persistence: 'session' }); expect(patch).toHaveBeenCalledWith('OLD_STATE'); - expect(store2.session.set).toHaveBeenCalledWith(STORAGE_KEY, { foo: 'bar' }); + expect(store2.session.set).toHaveBeenCalledWith(STORAGE_KEY, { + foo: 'bar', + }); }); }); }); diff --git a/code/core/src/manager-api/typings.d.ts b/code/core/src/manager-api/typings.d.ts index 7bf67c97fc33..c72db4ff54b9 100644 --- a/code/core/src/manager-api/typings.d.ts +++ b/code/core/src/manager-api/typings.d.ts @@ -10,3 +10,4 @@ declare var STORYBOOK_ADDON_STATE: Record; declare var STORYBOOK_FRAMEWORK: import('storybook/internal/types').SupportedFramework | undefined; declare var STORYBOOK_RENDERER: import('storybook/internal/types').SupportedRenderer | undefined; declare var STORYBOOK_BUILDER: import('storybook/internal/types').SupportedBuilder | undefined; +declare var STORYBOOK_ANONYMOUS_PROJECT_ID: string | undefined; diff --git a/code/core/src/manager/typings.d.ts b/code/core/src/manager/typings.d.ts index fb3fa90c7557..01bd88981c53 100644 --- a/code/core/src/manager/typings.d.ts +++ b/code/core/src/manager/typings.d.ts @@ -33,5 +33,6 @@ declare var __STORYBOOK_ICONS__: any; declare var __STORYBOOK_CLIENT_LOGGER__: any; declare var __STORYBOOK_ADDONS_CHANNEL__: any; declare var __STORYBOOK_TYPES__: any; +declare var STORYBOOK_ANONYMOUS_PROJECT_ID: string | undefined; declare var STORYBOOK_RENDERER: string | undefined; declare var sendTelemetryError: (error: any) => void; diff --git a/code/core/src/typings.d.ts b/code/core/src/typings.d.ts index 512933868d83..aefbc2249ec8 100644 --- a/code/core/src/typings.d.ts +++ b/code/core/src/typings.d.ts @@ -17,6 +17,7 @@ declare var STORYBOOK_RENDERER: import('./types/modules/renderers').SupportedRen declare var STORYBOOK_HOOKS_CONTEXT: any; declare var STORYBOOK_CURRENT_TASK_LOG: undefined | null | Array; +declare var STORYBOOK_ANONYMOUS_PROJECT_ID: string | undefined; declare var STORYBOOK_NETWORK_ADDRESS: string | undefined; declare var PREVIEW_URL: string | undefined; From fc4038387e30a407162d1b54592ae034fe836ad2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 08:58:04 +0000 Subject: [PATCH 11/14] Plan for addressing review feedback Co-authored-by: Sidnioulz <5108577+Sidnioulz@users.noreply.github.com> Agent-Logs-Url: https://github.com/storybookjs/storybook/sessions/1172a9ba-085a-4149-916f-63bf6a11d4d3 --- .../docs/rules/await-interactions.md | 8 ++--- .../eslint-plugin/docs/rules/csf-component.md | 4 +-- .../docs/rules/default-exports.md | 2 +- .../docs/rules/hierarchy-separator.md | 4 +-- .../docs/rules/meta-inline-properties.md | 4 +-- .../docs/rules/meta-satisfies-type.md | 8 ++--- .../docs/rules/no-redundant-story-name.md | 4 +-- .../docs/rules/no-renderer-packages.md | 14 ++++---- .../eslint-plugin/docs/rules/no-stories-of.md | 6 ++-- .../docs/rules/no-title-property-in-meta.md | 2 +- .../docs/rules/no-uninstalled-addons.md | 13 +++++--- .../eslint-plugin/docs/rules/story-exports.md | 4 +-- .../docs/rules/use-storybook-expect.md | 4 +-- .../rules/use-storybook-testing-library.md | 6 ++-- docs/configure/integration/eslint-plugin.mdx | 32 +++++++++---------- 15 files changed, 59 insertions(+), 56 deletions(-) diff --git a/code/lib/eslint-plugin/docs/rules/await-interactions.md b/code/lib/eslint-plugin/docs/rules/await-interactions.md index 16cde9402495..1945dc55f802 100644 --- a/code/lib/eslint-plugin/docs/rules/await-interactions.md +++ b/code/lib/eslint-plugin/docs/rules/await-interactions.md @@ -13,28 +13,28 @@ Storybook provides an instrumented version of the testing library in the [storyb Examples of **incorrect** code for this rule: ```js -import { userEvent, within } from 'storybook/test'; +import { userEvent, within } from "storybook/test"; // or from the legacy package "@storybook/testing-library"; MyStory.play = (context) => { const canvas = within(context.canvasElement); // not awaited! - userEvent.click(canvas.getByRole('button')); + userEvent.click(canvas.getByRole("button")); }; ``` Examples of **correct** code for this rule: ```js -import { userEvent, within } from 'storybook/test'; +import { userEvent, within } from "storybook/test"; // or from the legacy package "@storybook/testing-library"; MyStory.play = async (context) => { const canvas = within(context.canvasElement); // awaited 👍 - await userEvent.click(canvas.getByRole('button')); + await userEvent.click(canvas.getByRole("button")); }; ``` diff --git a/code/lib/eslint-plugin/docs/rules/csf-component.md b/code/lib/eslint-plugin/docs/rules/csf-component.md index c37678324a05..ed50122f004b 100644 --- a/code/lib/eslint-plugin/docs/rules/csf-component.md +++ b/code/lib/eslint-plugin/docs/rules/csf-component.md @@ -14,7 +14,7 @@ Examples of **incorrect** code for this rule: ```js export default { - title: 'Button', + title: "Button", }; ``` @@ -22,7 +22,7 @@ Examples of **correct** code for this rule: ```js export default { - title: 'Button', + title: "Button", component: Button, }; ``` diff --git a/code/lib/eslint-plugin/docs/rules/default-exports.md b/code/lib/eslint-plugin/docs/rules/default-exports.md index 57393270964b..ff3bd7456d8e 100644 --- a/code/lib/eslint-plugin/docs/rules/default-exports.md +++ b/code/lib/eslint-plugin/docs/rules/default-exports.md @@ -21,7 +21,7 @@ Examples of **correct** code for this rule: ```js export default { - title: 'Button', + title: "Button", args: { primary: true }, component: Button, }; diff --git a/code/lib/eslint-plugin/docs/rules/hierarchy-separator.md b/code/lib/eslint-plugin/docs/rules/hierarchy-separator.md index ce5dfb0ed340..a67d4392f34b 100644 --- a/code/lib/eslint-plugin/docs/rules/hierarchy-separator.md +++ b/code/lib/eslint-plugin/docs/rules/hierarchy-separator.md @@ -14,7 +14,7 @@ Examples of **incorrect** code for this rule: ```js export default { - title: 'Components|Forms/Input', + title: "Components|Forms/Input", component: Input, }; ``` @@ -23,7 +23,7 @@ Examples of **correct** code for this rule: ```js export default { - title: 'Components/Forms/Input', + title: "Components/Forms/Input", component: Input, }; ``` diff --git a/code/lib/eslint-plugin/docs/rules/meta-inline-properties.md b/code/lib/eslint-plugin/docs/rules/meta-inline-properties.md index 05347855dc23..ba0bc088ffd7 100644 --- a/code/lib/eslint-plugin/docs/rules/meta-inline-properties.md +++ b/code/lib/eslint-plugin/docs/rules/meta-inline-properties.md @@ -13,7 +13,7 @@ This rule encourages you to use inline property definitions for the default expo Examples of **incorrect** code for this rule: ```js -const title = 'Button'; +const title = "Button"; const args = { primary: true }; export default { @@ -27,7 +27,7 @@ Examples of **correct** code for this rule: ```js export default { - title: 'Button', + title: "Button", args: { primary: true }, component: Button, }; diff --git a/code/lib/eslint-plugin/docs/rules/meta-satisfies-type.md b/code/lib/eslint-plugin/docs/rules/meta-satisfies-type.md index 52afe887e3a8..52378e3791e0 100644 --- a/code/lib/eslint-plugin/docs/rules/meta-satisfies-type.md +++ b/code/lib/eslint-plugin/docs/rules/meta-satisfies-type.md @@ -16,7 +16,7 @@ Examples of **incorrect** code for this rule: ```ts export default { - title: 'Button', + title: "Button", args: { primary: true }, component: Button, }; @@ -24,7 +24,7 @@ export default { ```ts const meta: Meta = { - title: 'Button', + title: "Button", args: { primary: true }, component: Button, }; @@ -35,7 +35,7 @@ Examples of **correct** code for this rule: ```ts export default { - title: 'Button', + title: "Button", args: { primary: true }, component: Button, } satisfies Meta; @@ -43,7 +43,7 @@ export default { ```ts const meta = { - title: 'Button', + title: "Button", args: { primary: true }, component: Button, } satisfies Meta; diff --git a/code/lib/eslint-plugin/docs/rules/no-redundant-story-name.md b/code/lib/eslint-plugin/docs/rules/no-redundant-story-name.md index 44079b842c52..4528de5c3aa1 100644 --- a/code/lib/eslint-plugin/docs/rules/no-redundant-story-name.md +++ b/code/lib/eslint-plugin/docs/rules/no-redundant-story-name.md @@ -15,7 +15,7 @@ Examples of **incorrect** code for this rule: ```js export const PrimaryButton = { // no need for this, as Storybook will resolve to this name already - name: 'Primary Button', + name: "Primary Button", }; ``` @@ -23,7 +23,7 @@ Examples of **correct** code for this rule: ```js export const PrimaryButton = { - name: 'I am the primary', + name: "I am the primary", }; ``` diff --git a/code/lib/eslint-plugin/docs/rules/no-renderer-packages.md b/code/lib/eslint-plugin/docs/rules/no-renderer-packages.md index 8fa50646898a..72b9ec657039 100644 --- a/code/lib/eslint-plugin/docs/rules/no-renderer-packages.md +++ b/code/lib/eslint-plugin/docs/rules/no-renderer-packages.md @@ -24,19 +24,19 @@ Examples of **incorrect** code for this rule: ```js // Don't import renderer packages directly -import { something } from '@storybook/react'; -import { something } from '@storybook/vue3'; -import { something } from '@storybook/web-components'; +import { something } from "@storybook/react"; +import { something } from "@storybook/vue3"; +import { something } from "@storybook/web-components"; ``` Examples of **correct** code for this rule: ```js // Do use the appropriate framework package for your build tool -import { something } from '@storybook/react-vite'; // For Vite -import { something } from '@storybook/vue3-vite'; // For Vite -import { something } from '@storybook/web-components-vite'; // For Vite -import { something } from '@storybook/nextjs'; // For Next.js +import { something } from "@storybook/react-vite"; // For Vite +import { something } from "@storybook/vue3-vite"; // For Vite +import { something } from "@storybook/web-components-vite"; // For Vite +import { something } from "@storybook/nextjs"; // For Next.js ``` ## When Not To Use It diff --git a/code/lib/eslint-plugin/docs/rules/no-stories-of.md b/code/lib/eslint-plugin/docs/rules/no-stories-of.md index fba61b3bfc6c..d4e42f9b1183 100644 --- a/code/lib/eslint-plugin/docs/rules/no-stories-of.md +++ b/code/lib/eslint-plugin/docs/rules/no-stories-of.md @@ -13,11 +13,11 @@ Starting with Storybook 5.2, the Component Story Format ([CSF](https://storybook Examples of **incorrect** code for this rule: ```js -import { storiesOf } from '@storybook/react'; +import { storiesOf } from "@storybook/react"; -import Button from '../components/Button'; +import Button from "../components/Button"; -storiesOf('Button', module).add('primary', () => + )} - -
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
- + } /> ), }; diff --git a/code/addons/pseudo-states/src/stories/CSSAtRules.stories.tsx b/code/addons/pseudo-states/src/stories/CSSAtRules.stories.tsx index 4f9e7fc01c28..2e73940db5f2 100644 --- a/code/addons/pseudo-states/src/stories/CSSAtRules.stories.tsx +++ b/code/addons/pseudo-states/src/stories/CSSAtRules.stories.tsx @@ -6,8 +6,9 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { useChannel, useStoryContext } from 'storybook/preview-api'; -import { Button } from './CSSAtRules'; +import { Button } from './CSSAtRules.tsx'; import './grid.css'; +import { PseudoStateGrid } from './PseudoStateGrid.tsx'; const meta = { title: 'CSSAtRules', @@ -21,32 +22,7 @@ type Story = StoryObj; export const All: Story = { render: (args: ComponentProps) => ( -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
+ } /> ), }; diff --git a/code/addons/pseudo-states/src/stories/CustomElement.stories.tsx b/code/addons/pseudo-states/src/stories/CustomElement.stories.tsx index eaed0e1a4da9..402e0fe685f5 100644 --- a/code/addons/pseudo-states/src/stories/CustomElement.stories.tsx +++ b/code/addons/pseudo-states/src/stories/CustomElement.stories.tsx @@ -2,8 +2,9 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react-vite'; -import './CustomElement'; +import './CustomElement.tsx'; import './grid.css'; +import { PseudoStateGrid } from './PseudoStateGrid.tsx'; const meta = { title: 'CustomElement', @@ -20,40 +21,12 @@ type Story = StoryObj; export const All: Story = { render: () => ( -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Normal -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Hover -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Focus -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Active -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Hover Focus -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Hover Active -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Focus Active -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Hover Focus Active -
-
+ ( + // @ts-expect-error We're dealing with a web component here + {label} + )} + /> ), }; diff --git a/code/addons/pseudo-states/src/stories/CustomElementNested.stories.tsx b/code/addons/pseudo-states/src/stories/CustomElementNested.stories.tsx index 4c004bd33903..df41f0ec31bd 100644 --- a/code/addons/pseudo-states/src/stories/CustomElementNested.stories.tsx +++ b/code/addons/pseudo-states/src/stories/CustomElementNested.stories.tsx @@ -2,8 +2,9 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react-vite'; -import './CustomElementNested'; +import './CustomElementNested.tsx'; import './grid.css'; +import { PseudoStateGrid } from './PseudoStateGrid.tsx'; const meta = { title: 'CustomElementNested', @@ -20,40 +21,12 @@ type Story = StoryObj; export const All: Story = { render: () => ( -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Normal -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Hover -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Focus -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Active -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Hover Focus -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Hover Active -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Focus Active -
-
- {/* @ts-expect-error We're dealing with a web component here */} - Hover Focus Active -
-
+ ( + // @ts-expect-error We're dealing with a web component here + {label} + )} + /> ), }; diff --git a/code/addons/pseudo-states/src/stories/CustomElementNested.tsx b/code/addons/pseudo-states/src/stories/CustomElementNested.tsx index e9854a0bd3b6..f8d0f7ada46e 100644 --- a/code/addons/pseudo-states/src/stories/CustomElementNested.tsx +++ b/code/addons/pseudo-states/src/stories/CustomElementNested.tsx @@ -1,4 +1,4 @@ -import './CustomElement'; +import './CustomElement.tsx'; export class CustomElementNested extends HTMLElement { constructor() { diff --git a/code/addons/pseudo-states/src/stories/Input.stories.tsx b/code/addons/pseudo-states/src/stories/Input.stories.tsx index bf7ca9c66f3c..7b5a00784bd6 100644 --- a/code/addons/pseudo-states/src/stories/Input.stories.tsx +++ b/code/addons/pseudo-states/src/stories/Input.stories.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Input } from './Input'; +import { Input } from './Input.tsx'; import './grid.css'; const meta = { diff --git a/code/addons/pseudo-states/src/stories/NestedRules.stories.tsx b/code/addons/pseudo-states/src/stories/NestedRules.stories.tsx index 3b52327a73f0..043ea7b4893b 100644 --- a/code/addons/pseudo-states/src/stories/NestedRules.stories.tsx +++ b/code/addons/pseudo-states/src/stories/NestedRules.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Button } from './NestedRules'; +import { Button } from './NestedRules.tsx'; const meta = { title: 'NestedRules', diff --git a/code/addons/pseudo-states/src/stories/Portal.stories.tsx b/code/addons/pseudo-states/src/stories/Portal.stories.tsx index 4e67f1cac1d5..12dd9ff2b9a7 100644 --- a/code/addons/pseudo-states/src/stories/Portal.stories.tsx +++ b/code/addons/pseudo-states/src/stories/Portal.stories.tsx @@ -3,7 +3,7 @@ import { createPortal } from 'react-dom'; import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Button } from './Button'; +import { Button } from './Button.tsx'; import './grid.css'; const PortalButton = (props: ComponentProps) => diff --git a/code/addons/pseudo-states/src/stories/PseudoStateGrid.tsx b/code/addons/pseudo-states/src/stories/PseudoStateGrid.tsx new file mode 100644 index 000000000000..fee7199453ba --- /dev/null +++ b/code/addons/pseudo-states/src/stories/PseudoStateGrid.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from 'react'; + +interface PseudoStateGridProps { + render: (label: string) => ReactNode; +} + +export const PseudoStateGrid = ({ render }: PseudoStateGridProps) => ( +
+
{render('Normal')}
+
{render('Hover')}
+
{render('Focus')}
+
{render('Active')}
+
{render('Hover Focus')}
+
{render('Hover Active')}
+
{render('Focus Active')}
+
+ {render('Hover Focus Active')} +
+
+); diff --git a/code/addons/pseudo-states/src/stories/ShadowRoot.stories.tsx b/code/addons/pseudo-states/src/stories/ShadowRoot.stories.tsx index f8d11a84d290..05cc117a4069 100644 --- a/code/addons/pseudo-states/src/stories/ShadowRoot.stories.tsx +++ b/code/addons/pseudo-states/src/stories/ShadowRoot.stories.tsx @@ -2,8 +2,9 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react-vite'; -import { ShadowRoot } from './ShadowRoot'; +import { ShadowRoot } from './ShadowRoot.tsx'; import './grid.css'; +import { PseudoStateGrid } from './PseudoStateGrid.tsx'; const meta = { title: 'ShadowRoot', @@ -15,34 +16,7 @@ export default meta; type Story = StoryObj; export const All: Story = { - render: () => ( -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- ), + render: () => } />, }; export const Default: Story = {}; diff --git a/code/addons/pseudo-states/src/stories/ShadowRootWithPart.stories.tsx b/code/addons/pseudo-states/src/stories/ShadowRootWithPart.stories.tsx index 8dae2e4b111e..b02465481dae 100644 --- a/code/addons/pseudo-states/src/stories/ShadowRootWithPart.stories.tsx +++ b/code/addons/pseudo-states/src/stories/ShadowRootWithPart.stories.tsx @@ -2,8 +2,9 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react-vite'; -import { ShadowRoot } from './ShadowRootWithPart'; +import { ShadowRoot } from './ShadowRootWithPart.tsx'; import './grid.css'; +import { PseudoStateGrid } from './PseudoStateGrid.tsx'; const meta = { title: 'ShadowRootWithPart', @@ -15,34 +16,7 @@ export default meta; type Story = StoryObj; export const All: Story = { - render: () => ( -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- ), + render: () => } />, }; export const Default: Story = {}; diff --git a/code/addons/pseudo-states/src/types.test-d.ts b/code/addons/pseudo-states/src/types.test-d.ts index 871f32492adc..5ff85dbcfd12 100644 --- a/code/addons/pseudo-states/src/types.test-d.ts +++ b/code/addons/pseudo-states/src/types.test-d.ts @@ -2,8 +2,8 @@ import { describe, expectTypeOf, it } from 'vitest'; import { definePreview } from 'storybook/internal/csf'; -import pseudoAddon from '.'; -import '../../../renderers/react/src/typings'; +import pseudoAddon from './index.ts'; +import '../../../renderers/react/src/typings.ts'; describe('addon parameters are injected to csf factory', () => { // Define preview with pseudo addon diff --git a/code/addons/pseudo-states/src/types.ts b/code/addons/pseudo-states/src/types.ts index 3215f44105a2..fd63677964f2 100644 --- a/code/addons/pseudo-states/src/types.ts +++ b/code/addons/pseudo-states/src/types.ts @@ -1,4 +1,4 @@ -import type { PSEUDO_STATES } from './constants'; +import type { PSEUDO_STATES } from './constants.ts'; export type PseudoState = keyof typeof PSEUDO_STATES; diff --git a/code/addons/pseudo-states/vitest.config.ts b/code/addons/pseudo-states/vitest.config.ts index 0e10a6f31d49..170b1e3a9931 100644 --- a/code/addons/pseudo-states/vitest.config.ts +++ b/code/addons/pseudo-states/vitest.config.ts @@ -1,6 +1,6 @@ import { defineConfig, mergeConfig } from 'vitest/config'; -import { vitestCommonConfig } from '../../vitest.shared'; +import { vitestCommonConfig } from '../../vitest.shared.ts'; export default mergeConfig( vitestCommonConfig, diff --git a/code/addons/themes/build-config.ts b/code/addons/themes/build-config.ts index 863fe3dc87f2..afa0b4a6f52d 100644 --- a/code/addons/themes/build-config.ts +++ b/code/addons/themes/build-config.ts @@ -1,4 +1,4 @@ -import type { BuildEntries } from '../../../scripts/build/utils/entry-utils'; +import type { BuildEntries } from '../../../scripts/build/utils/entry-utils.ts'; const config: BuildEntries = { entries: { diff --git a/code/addons/themes/package.json b/code/addons/themes/package.json index 9aabef44c7a9..ecd04723bc36 100644 --- a/code/addons/themes/package.json +++ b/code/addons/themes/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-themes", - "version": "10.4.0-alpha.3", + "version": "10.4.0-alpha.7", "description": "Storybook Themes addon: Switch between themes from the toolbar", "keywords": [ "css", diff --git a/code/addons/themes/src/constants.ts b/code/addons/themes/src/constants.ts index b1f84fea67b6..4a96a6d5f43b 100644 --- a/code/addons/themes/src/constants.ts +++ b/code/addons/themes/src/constants.ts @@ -1,4 +1,4 @@ -import type { ThemeAddonState, ThemesParameters } from './types'; +import type { ThemeAddonState, ThemesParameters } from './types.ts'; export const PARAM_KEY = 'themes' as const; export const ADDON_ID = `storybook/${PARAM_KEY}` as const; diff --git a/code/addons/themes/src/decorators/class-name.decorator.tsx b/code/addons/themes/src/decorators/class-name.decorator.tsx index 007bb9f59a7c..fce978c1ca79 100644 --- a/code/addons/themes/src/decorators/class-name.decorator.tsx +++ b/code/addons/themes/src/decorators/class-name.decorator.tsx @@ -2,8 +2,8 @@ import type { DecoratorFunction, Renderer } from 'storybook/internal/types'; import { useEffect } from 'storybook/preview-api'; -import { PARAM_KEY } from '../constants'; -import { initializeThemeState, pluckThemeFromContext } from './helpers'; +import { PARAM_KEY } from '../constants.ts'; +import { initializeThemeState, pluckThemeFromContext } from './helpers.ts'; export interface ClassNameStrategyConfiguration { themes: Record; diff --git a/code/addons/themes/src/decorators/data-attribute.decorator.tsx b/code/addons/themes/src/decorators/data-attribute.decorator.tsx index 356e66a535a4..168b32e86a09 100644 --- a/code/addons/themes/src/decorators/data-attribute.decorator.tsx +++ b/code/addons/themes/src/decorators/data-attribute.decorator.tsx @@ -2,8 +2,8 @@ import type { DecoratorFunction, Renderer } from 'storybook/internal/types'; import { useEffect } from 'storybook/preview-api'; -import { PARAM_KEY } from '../constants'; -import { initializeThemeState, pluckThemeFromContext } from './helpers'; +import { PARAM_KEY } from '../constants.ts'; +import { initializeThemeState, pluckThemeFromContext } from './helpers.ts'; export interface DataAttributeStrategyConfiguration { themes: Record; diff --git a/code/addons/themes/src/decorators/helpers.ts b/code/addons/themes/src/decorators/helpers.ts index 3e147a6046ba..8322c2f4fdb3 100644 --- a/code/addons/themes/src/decorators/helpers.ts +++ b/code/addons/themes/src/decorators/helpers.ts @@ -4,8 +4,8 @@ import type { StoryContext } from 'storybook/internal/types'; import { addons, useParameter } from 'storybook/preview-api'; import { dedent } from 'ts-dedent'; -import { DEFAULT_THEME_PARAMETERS, GLOBAL_KEY, PARAM_KEY, THEMING_EVENTS } from '../constants'; -import type { ThemesParameters as Parameters } from '../types'; +import { DEFAULT_THEME_PARAMETERS, GLOBAL_KEY, PARAM_KEY, THEMING_EVENTS } from '../constants.ts'; +import type { ThemesParameters as Parameters } from '../types.ts'; type ThemesParameters = Parameters['themes']; diff --git a/code/addons/themes/src/decorators/index.ts b/code/addons/themes/src/decorators/index.ts index ce07c553332e..ed3c429eb683 100644 --- a/code/addons/themes/src/decorators/index.ts +++ b/code/addons/themes/src/decorators/index.ts @@ -1,5 +1,5 @@ -export * from './class-name.decorator'; -export * from './data-attribute.decorator'; -export * from './provider.decorator'; +export * from './class-name.decorator.tsx'; +export * from './data-attribute.decorator.tsx'; +export * from './provider.decorator.tsx'; -export * as DecoratorHelpers from './helpers'; +export * as DecoratorHelpers from './helpers.ts'; diff --git a/code/addons/themes/src/decorators/provider.decorator.tsx b/code/addons/themes/src/decorators/provider.decorator.tsx index 79af1a5fa07a..2fb155d58de8 100644 --- a/code/addons/themes/src/decorators/provider.decorator.tsx +++ b/code/addons/themes/src/decorators/provider.decorator.tsx @@ -4,8 +4,8 @@ import type { DecoratorFunction, Renderer } from 'storybook/internal/types'; import { useMemo } from 'storybook/preview-api'; -import { PARAM_KEY } from '../constants'; -import { initializeThemeState, pluckThemeFromContext } from './helpers'; +import { PARAM_KEY } from '../constants.ts'; +import { initializeThemeState, pluckThemeFromContext } from './helpers.ts'; type Theme = Record; type ThemeMap = Record; diff --git a/code/addons/themes/src/index.ts b/code/addons/themes/src/index.ts index f99c1096a117..338380edc4d9 100644 --- a/code/addons/themes/src/index.ts +++ b/code/addons/themes/src/index.ts @@ -1,10 +1,10 @@ import { definePreviewAddon } from 'storybook/internal/csf'; -import * as addonAnnotations from './preview'; -import type { ThemesTypes } from './types'; +import * as addonAnnotations from './preview.ts'; +import type { ThemesTypes } from './types.ts'; -export type { ThemesGlobals, ThemesTypes } from './types'; +export type { ThemesGlobals, ThemesTypes } from './types.ts'; export default () => definePreviewAddon(addonAnnotations); -export * from './decorators'; +export * from './decorators/index.ts'; diff --git a/code/addons/themes/src/manager.tsx b/code/addons/themes/src/manager.tsx index 8551013dd3c3..c19f5a8c019b 100644 --- a/code/addons/themes/src/manager.tsx +++ b/code/addons/themes/src/manager.tsx @@ -1,7 +1,7 @@ import { addons, types } from 'storybook/manager-api'; -import { ADDON_ID, PARAM_KEY, THEME_SWITCHER_ID } from './constants'; -import { ThemeSwitcher } from './theme-switcher'; +import { ADDON_ID, PARAM_KEY, THEME_SWITCHER_ID } from './constants.ts'; +import { ThemeSwitcher } from './theme-switcher.tsx'; addons.register(ADDON_ID, () => { addons.add(THEME_SWITCHER_ID, { diff --git a/code/addons/themes/src/preview.ts b/code/addons/themes/src/preview.ts index 5d33f51b94f7..515b7b314a98 100644 --- a/code/addons/themes/src/preview.ts +++ b/code/addons/themes/src/preview.ts @@ -1,6 +1,6 @@ import type { ProjectAnnotations, Renderer } from 'storybook/internal/types'; -import { GLOBAL_KEY as KEY } from './constants'; +import { GLOBAL_KEY as KEY } from './constants.ts'; export const initialGlobals: ProjectAnnotations['initialGlobals'] = { [KEY]: '', diff --git a/code/addons/themes/src/theme-switcher.tsx b/code/addons/themes/src/theme-switcher.tsx index 3865cef7cedf..40ae9cd1ff9b 100644 --- a/code/addons/themes/src/theme-switcher.tsx +++ b/code/addons/themes/src/theme-switcher.tsx @@ -13,8 +13,8 @@ import { PARAM_KEY, THEME_SWITCHER_ID, THEMING_EVENTS, -} from './constants'; -import type { ThemesParameters as Parameters, ThemeAddonState } from './types'; +} from './constants.ts'; +import type { ThemesParameters as Parameters, ThemeAddonState } from './types.ts'; type ThemesParameters = NonNullable; diff --git a/code/addons/themes/vitest.config.ts b/code/addons/themes/vitest.config.ts index 16d2fd08535c..25fae90d1f77 100644 --- a/code/addons/themes/vitest.config.ts +++ b/code/addons/themes/vitest.config.ts @@ -1,6 +1,6 @@ import { defineConfig, mergeConfig } from 'vitest/config'; -import { vitestCommonConfig } from '../../vitest.shared'; +import { vitestCommonConfig } from '../../vitest.shared.ts'; export default mergeConfig( vitestCommonConfig, diff --git a/code/addons/vitest/build-config.ts b/code/addons/vitest/build-config.ts index 2f0cad6b8165..c324af90450d 100644 --- a/code/addons/vitest/build-config.ts +++ b/code/addons/vitest/build-config.ts @@ -1,4 +1,4 @@ -import type { BuildEntries } from '../../../scripts/build/utils/entry-utils'; +import type { BuildEntries } from '../../../scripts/build/utils/entry-utils.ts'; const config: BuildEntries = { entries: { diff --git a/code/addons/vitest/package.json b/code/addons/vitest/package.json index defac0d0a757..ec60bd9543e5 100644 --- a/code/addons/vitest/package.json +++ b/code/addons/vitest/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-vitest", - "version": "10.4.0-alpha.3", + "version": "10.4.0-alpha.7", "description": "Storybook Vitest addon: Blazing fast component testing using stories", "keywords": [ "storybook", diff --git a/code/addons/vitest/src/components/Description.tsx b/code/addons/vitest/src/components/Description.tsx index d45aaaaca0d8..69a9e5bc08e1 100644 --- a/code/addons/vitest/src/components/Description.tsx +++ b/code/addons/vitest/src/components/Description.tsx @@ -5,9 +5,9 @@ import type { TestProviderState } from 'storybook/internal/types'; import { styled } from 'storybook/theming'; -import type { StoreState } from '../types'; -import { GlobalErrorContext } from './GlobalErrorModal'; -import { RelativeTime } from './RelativeTime'; +import type { StoreState } from '../types.ts'; +import { GlobalErrorContext } from './GlobalErrorModal.tsx'; +import { RelativeTime } from './RelativeTime.tsx'; export const Wrapper = styled.div(({ theme }) => ({ overflow: 'hidden', diff --git a/code/addons/vitest/src/components/GlobalErrorModal.stories.tsx b/code/addons/vitest/src/components/GlobalErrorModal.stories.tsx index c1c35c19921a..678fac36b018 100644 --- a/code/addons/vitest/src/components/GlobalErrorModal.stories.tsx +++ b/code/addons/vitest/src/components/GlobalErrorModal.stories.tsx @@ -6,8 +6,8 @@ import { ManagerContext } from 'storybook/manager-api'; import { expect, fn, userEvent, within } from 'storybook/test'; import { dedent } from 'ts-dedent'; -import { storeOptions } from '../constants'; -import { GlobalErrorContext, GlobalErrorModal } from './GlobalErrorModal'; +import { storeOptions } from '../constants.ts'; +import { GlobalErrorContext, GlobalErrorModal } from './GlobalErrorModal.tsx'; type Story = StoryObj; diff --git a/code/addons/vitest/src/components/GlobalErrorModal.tsx b/code/addons/vitest/src/components/GlobalErrorModal.tsx index 686f6cea57db..897f446773f4 100644 --- a/code/addons/vitest/src/components/GlobalErrorModal.tsx +++ b/code/addons/vitest/src/components/GlobalErrorModal.tsx @@ -7,8 +7,8 @@ import { SyncIcon } from '@storybook/icons'; import { useStorybookApi } from 'storybook/manager-api'; import { styled } from 'storybook/theming'; -import { DOCUMENTATION_FATAL_ERROR_LINK } from '../constants'; -import type { ErrorLike, StoreState } from '../types'; +import { DOCUMENTATION_FATAL_ERROR_LINK } from '../constants.ts'; +import type { ErrorLike, StoreState } from '../types.ts'; const ModalBar = styled.div({ display: 'flex', diff --git a/code/addons/vitest/src/components/RelativeTime.stories.tsx b/code/addons/vitest/src/components/RelativeTime.stories.tsx index aecc461c8b74..449c9e6b497e 100644 --- a/code/addons/vitest/src/components/RelativeTime.stories.tsx +++ b/code/addons/vitest/src/components/RelativeTime.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; -import { RelativeTime } from './RelativeTime'; +import { RelativeTime } from './RelativeTime.tsx'; const meta = { component: RelativeTime, diff --git a/code/addons/vitest/src/components/SidebarContextMenu.tsx b/code/addons/vitest/src/components/SidebarContextMenu.tsx index c8fe04772012..30f3a6afffad 100644 --- a/code/addons/vitest/src/components/SidebarContextMenu.tsx +++ b/code/addons/vitest/src/components/SidebarContextMenu.tsx @@ -5,8 +5,8 @@ import type { API_HashEntry } from 'storybook/internal/types'; import { type API } from 'storybook/manager-api'; -import { useTestProvider } from '../use-test-provider-state'; -import { TestProviderRender } from './TestProviderRender'; +import { useTestProvider } from '../use-test-provider-state.ts'; +import { TestProviderRender } from './TestProviderRender.tsx'; type SidebarContextMenuProps = { api: API; diff --git a/code/addons/vitest/src/components/TestProviderRender.stories.tsx b/code/addons/vitest/src/components/TestProviderRender.stories.tsx index bf7c6671f734..7108686527a1 100644 --- a/code/addons/vitest/src/components/TestProviderRender.stories.tsx +++ b/code/addons/vitest/src/components/TestProviderRender.stories.tsx @@ -8,10 +8,10 @@ import { ManagerContext, addons } from 'storybook/manager-api'; import { fn } from 'storybook/test'; import { styled } from 'storybook/theming'; -import { ADDON_ID as A11Y_ADDON_ID } from '../../../a11y/src/constants'; -import { storeOptions } from '../constants'; -import { store as mockStore } from '../manager-store.mock'; -import { TestProviderRender } from './TestProviderRender'; +import { ADDON_ID as A11Y_ADDON_ID } from '../../../a11y/src/constants.ts'; +import { storeOptions } from '../constants.ts'; +import { store as mockStore } from '../manager-store.mock.ts'; +import { TestProviderRender } from './TestProviderRender.tsx'; const managerContext: any = { api: { diff --git a/code/addons/vitest/src/components/TestProviderRender.tsx b/code/addons/vitest/src/components/TestProviderRender.tsx index 9611b45ba7ff..4c3f3f56ea90 100644 --- a/code/addons/vitest/src/components/TestProviderRender.tsx +++ b/code/addons/vitest/src/components/TestProviderRender.tsx @@ -21,11 +21,11 @@ import { A11Y_PANEL_ID, COMPONENT_TESTING_PANEL_ID, FULL_RUN_TRIGGERS, -} from '../constants'; -import type { StoreState } from '../types'; -import type { StatusValueToStoryIds } from '../use-test-provider-state'; -import { Description } from './Description'; -import { TestStatusIcon } from './TestStatusIcon'; +} from '../constants.ts'; +import type { StoreState } from '../types.ts'; +import type { StatusValueToStoryIds } from '../use-test-provider-state.ts'; +import { Description } from './Description.tsx'; +import { TestStatusIcon } from './TestStatusIcon.tsx'; const Container = styled.div<{ inContextMenu?: boolean }>(({ inContextMenu }) => ({ display: 'flex', diff --git a/code/addons/vitest/src/components/TestStatusIcon.stories.tsx b/code/addons/vitest/src/components/TestStatusIcon.stories.tsx index 07d403b6d643..4253edea94e6 100644 --- a/code/addons/vitest/src/components/TestStatusIcon.stories.tsx +++ b/code/addons/vitest/src/components/TestStatusIcon.stories.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react-vite'; -import { TestStatusIcon } from './TestStatusIcon'; +import { TestStatusIcon } from './TestStatusIcon.tsx'; const meta = { component: TestStatusIcon, diff --git a/code/addons/vitest/src/constants.ts b/code/addons/vitest/src/constants.ts index e49ef528030e..81113de40931 100644 --- a/code/addons/vitest/src/constants.ts +++ b/code/addons/vitest/src/constants.ts @@ -1,12 +1,12 @@ import type { StoreOptions } from 'storybook/internal/types'; -import type { CurrentRun, RunTrigger, StoreState } from './types'; +import type { CurrentRun, RunTrigger, StoreState } from './types.ts'; -export { PANEL_ID as COMPONENT_TESTING_PANEL_ID } from '../../../core/src/component-testing/constants'; +export { PANEL_ID as COMPONENT_TESTING_PANEL_ID } from '../../../core/src/component-testing/constants.ts'; export { PANEL_ID as A11Y_PANEL_ID, ADDON_ID as A11Y_ADDON_ID, -} from '../../../addons/a11y/src/constants'; +} from '../../../addons/a11y/src/constants.ts'; export const ADDON_ID = 'storybook/test'; export const TEST_PROVIDER_ID = `${ADDON_ID}/test-provider`; diff --git a/code/addons/vitest/src/logger.ts b/code/addons/vitest/src/logger.ts index 9846c7e3e7fa..466775db19f2 100644 --- a/code/addons/vitest/src/logger.ts +++ b/code/addons/vitest/src/logger.ts @@ -2,7 +2,7 @@ import { logger } from 'storybook/internal/node-logger'; import picocolors from 'picocolors'; -import { ADDON_ID } from './constants'; +import { ADDON_ID } from './constants.ts'; export const log = (message: any) => { logger.log( diff --git a/code/addons/vitest/src/manager-store.mock.ts b/code/addons/vitest/src/manager-store.mock.ts index 4477db032c01..78b247eb12c6 100644 --- a/code/addons/vitest/src/manager-store.mock.ts +++ b/code/addons/vitest/src/manager-store.mock.ts @@ -12,7 +12,7 @@ import { STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST, storeOptions, -} from './constants'; +} from './constants.ts'; export const store = testUtils.mocked(new experimental_MockUniversalStore(storeOptions, testUtils)); diff --git a/code/addons/vitest/src/manager-store.ts b/code/addons/vitest/src/manager-store.ts index fbf8f21154a0..441389c3dd30 100644 --- a/code/addons/vitest/src/manager-store.ts +++ b/code/addons/vitest/src/manager-store.ts @@ -9,8 +9,8 @@ import { STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST, storeOptions, -} from './constants'; -import type { StoreEvent, StoreState } from './types'; +} from './constants.ts'; +import type { StoreEvent, StoreState } from './types.ts'; export const store = experimental_UniversalStore.create({ ...storeOptions, diff --git a/code/addons/vitest/src/manager.tsx b/code/addons/vitest/src/manager.tsx index 1be9319fdc4d..d0d34f51c58a 100644 --- a/code/addons/vitest/src/manager.tsx +++ b/code/addons/vitest/src/manager.tsx @@ -10,17 +10,17 @@ import { } from '#manager-store'; import { Tag, addons } from 'storybook/manager-api'; -import { GlobalErrorContext, GlobalErrorModal } from './components/GlobalErrorModal'; -import { SidebarContextMenu } from './components/SidebarContextMenu'; -import { TestProviderRender } from './components/TestProviderRender'; +import { GlobalErrorContext, GlobalErrorModal } from './components/GlobalErrorModal.tsx'; +import { SidebarContextMenu } from './components/SidebarContextMenu.tsx'; +import { TestProviderRender } from './components/TestProviderRender.tsx'; import { A11Y_PANEL_ID, ADDON_ID, COMPONENT_TESTING_PANEL_ID, STORYBOOK_ADDON_TEST_CHANNEL, TEST_PROVIDER_ID, -} from './constants'; -import { useTestProvider } from './use-test-provider-state'; +} from './constants.ts'; +import { useTestProvider } from './use-test-provider-state.ts'; addons.register(ADDON_ID, (api) => { if (globalThis.STORYBOOK_BUILDER === SupportedBuilder.VITE) { diff --git a/code/addons/vitest/src/node/boot-test-runner.test.ts b/code/addons/vitest/src/node/boot-test-runner.test.ts index 80f30bf856fe..9db7f8f30516 100644 --- a/code/addons/vitest/src/node/boot-test-runner.test.ts +++ b/code/addons/vitest/src/node/boot-test-runner.test.ts @@ -6,11 +6,11 @@ import { Channel, type ChannelTransport } from 'storybook/internal/channels'; import { executeNodeCommand } from 'storybook/internal/common'; import type { Options } from 'storybook/internal/types'; -import { storeOptions } from '../constants'; -import { log } from '../logger'; -import type { StoreEvent } from '../types'; -import type { StoreState } from '../types'; -import { killTestRunner, runTestRunner } from './boot-test-runner'; +import { storeOptions } from '../constants.ts'; +import { log } from '../logger.ts'; +import type { StoreEvent } from '../types.ts'; +import type { StoreState } from '../types.ts'; +import { killTestRunner, runTestRunner } from './boot-test-runner.ts'; let stdout: (chunk: Buffer | string) => void; let stderr: (chunk: Buffer | string) => void; diff --git a/code/addons/vitest/src/node/boot-test-runner.ts b/code/addons/vitest/src/node/boot-test-runner.ts index 48cc96549744..d08b496302b5 100644 --- a/code/addons/vitest/src/node/boot-test-runner.ts +++ b/code/addons/vitest/src/node/boot-test-runner.ts @@ -11,14 +11,15 @@ import type { EventInfo, Options } from 'storybook/internal/types'; import { normalize } from 'pathe'; -import { importMetaResolve } from '../../../../core/src/shared/utils/module'; +import { importMetaResolve } from '../../../../core/src/shared/utils/module.ts'; import { STATUS_STORE_CHANNEL_EVENT_NAME, STORE_CHANNEL_EVENT_NAME, TEST_PROVIDER_STORE_CHANNEL_EVENT_NAME, -} from '../constants'; -import { log } from '../logger'; -import type { Store } from '../types'; +} from '../constants.ts'; +import { log } from '../logger.ts'; +import { errorToErrorLike } from '../utils.ts'; +import type { Store } from '../types.ts'; const MAX_START_TIME = 30000; @@ -144,12 +145,7 @@ const bootTestRunner = async ({ type: 'FATAL_ERROR', payload: { message: 'Failed to start test runner process', - error: { - message: error.message, - name: error.name, - stack: error.stack, - cause: error.cause, - }, + error: error instanceof Error ? errorToErrorLike(error) : { message: String(error) }, }, }); eventQueue.length = 0; diff --git a/code/addons/vitest/src/node/coverage-reporter.ts b/code/addons/vitest/src/node/coverage-reporter.ts index dd1562103105..462dc457e0e8 100644 --- a/code/addons/vitest/src/node/coverage-reporter.ts +++ b/code/addons/vitest/src/node/coverage-reporter.ts @@ -3,8 +3,8 @@ import type { ResolvedCoverageOptions } from 'vitest/node'; import type { ReportNode, Visitor } from 'istanbul-lib-report'; import { ReportBase } from 'istanbul-lib-report'; -import type { StoreState } from '../types'; -import type { TestManager } from './test-manager'; +import type { StoreState } from '../types.ts'; +import type { TestManager } from './test-manager.ts'; export type StorybookCoverageReporterOptions = { testManager: TestManager; diff --git a/code/addons/vitest/src/node/reporter.ts b/code/addons/vitest/src/node/reporter.ts index d8246fcc119f..493541170323 100644 --- a/code/addons/vitest/src/node/reporter.ts +++ b/code/addons/vitest/src/node/reporter.ts @@ -5,8 +5,8 @@ import { type Reporter } from 'vitest/reporters'; import type { TaskMeta } from '@vitest/runner'; import type { Report } from 'storybook/preview-api'; -import type { VitestError } from '../types'; -import type { TestManager } from './test-manager'; +import type { VitestError } from '../types.ts'; +import type { TestManager } from './test-manager.ts'; export class StorybookReporter implements Reporter { ctx!: Vitest; diff --git a/code/addons/vitest/src/node/test-manager.test.ts b/code/addons/vitest/src/node/test-manager.test.ts index bdc919b1b354..12e884fb000d 100644 --- a/code/addons/vitest/src/node/test-manager.test.ts +++ b/code/addons/vitest/src/node/test-manager.test.ts @@ -11,10 +11,10 @@ import type { import path from 'pathe'; -import { STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST, storeOptions } from '../constants'; -import type { StoreEvent, StoreState } from '../types'; -import { TestManager, type TestManagerOptions } from './test-manager'; -import { DOUBLE_SPACES } from './vitest-manager'; +import { STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST, storeOptions } from '../constants.ts'; +import type { StoreEvent, StoreState } from '../types.ts'; +import { TestManager, type TestManagerOptions } from './test-manager.ts'; +import { DOUBLE_SPACES } from './vitest-manager.ts'; const setTestNamePattern = vi.hoisted(() => vi.fn()); const vitest = vi.hoisted(() => ({ diff --git a/code/addons/vitest/src/node/test-manager.ts b/code/addons/vitest/src/node/test-manager.ts index 7f11a90f2599..d795ab1ecea6 100644 --- a/code/addons/vitest/src/node/test-manager.ts +++ b/code/addons/vitest/src/node/test-manager.ts @@ -13,7 +13,7 @@ import type { A11yReport } from '@storybook/addon-a11y'; import { throttle } from 'es-toolkit/function'; import type { Report } from 'storybook/preview-api'; -import { STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST, storeOptions } from '../constants'; +import { STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST, storeOptions } from '../constants.ts'; import type { CurrentRun, RunTrigger, @@ -21,9 +21,9 @@ import type { StoreState, TriggerRunEvent, VitestError, -} from '../types'; -import { errorToErrorLike } from '../utils'; -import { VitestManager } from './vitest-manager'; +} from '../types.ts'; +import { errorToErrorLike } from '../utils.ts'; +import { VitestManager } from './vitest-manager.ts'; export type TestManagerOptions = { storybookOptions: Options; diff --git a/code/addons/vitest/src/node/vitest-manager.ts b/code/addons/vitest/src/node/vitest-manager.ts index 018165da983c..ca1ed83f9f9a 100644 --- a/code/addons/vitest/src/node/vitest-manager.ts +++ b/code/addons/vitest/src/node/vitest-manager.ts @@ -19,12 +19,12 @@ import path, { dirname, join, normalize, resolve } from 'pathe'; // eslint-disable-next-line depend/ban-dependencies import slash from 'slash'; -import { COVERAGE_DIRECTORY } from '../constants'; -import { log } from '../logger'; -import type { TriggerRunEvent } from '../types'; -import type { StorybookCoverageReporterOptions } from './coverage-reporter'; -import { StorybookReporter } from './reporter'; -import type { TestManager } from './test-manager'; +import { COVERAGE_DIRECTORY } from '../constants.ts'; +import { log } from '../logger.ts'; +import type { TriggerRunEvent } from '../types.ts'; +import type { StorybookCoverageReporterOptions } from './coverage-reporter.ts'; +import { StorybookReporter } from './reporter.ts'; +import type { TestManager } from './test-manager.ts'; const VITEST_CONFIG_FILE_EXTENSIONS = ['mts', 'mjs', 'cts', 'cjs', 'ts', 'tsx', 'js', 'jsx']; const VITEST_WORKSPACE_FILE_EXTENSION = ['ts', 'js', 'json']; diff --git a/code/addons/vitest/src/node/vitest.ts b/code/addons/vitest/src/node/vitest.ts index 92d5871ff485..6d855168ba80 100644 --- a/code/addons/vitest/src/node/vitest.ts +++ b/code/addons/vitest/src/node/vitest.ts @@ -12,9 +12,9 @@ import { STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST, storeOptions, -} from '../constants'; -import type { ErrorLike, FatalErrorEvent, StoreEvent, StoreState } from '../types'; -import { TestManager } from './test-manager'; +} from '../constants.ts'; +import type { ErrorLike, FatalErrorEvent, StoreEvent, StoreState } from '../types.ts'; +import { TestManager } from './test-manager.ts'; // Destructure the imported functions for easier access const UniversalStore = experimental_UniversalStore; diff --git a/code/addons/vitest/src/postinstall.test.ts b/code/addons/vitest/src/postinstall.test.ts index 497a6d7f24d3..f7082efee9a3 100644 --- a/code/addons/vitest/src/postinstall.test.ts +++ b/code/addons/vitest/src/postinstall.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { isConfigAlreadySetup } from './postinstall'; +import { isConfigAlreadySetup } from './postinstall.ts'; describe('postinstall helpers', () => { it('detects a fully configured Vitest config with addon plugin', () => { diff --git a/code/addons/vitest/src/postinstall.ts b/code/addons/vitest/src/postinstall.ts index 7c426e701c9e..33d75788a37d 100644 --- a/code/addons/vitest/src/postinstall.ts +++ b/code/addons/vitest/src/postinstall.ts @@ -27,9 +27,9 @@ import { dirname, relative, resolve } from 'pathe'; import { coerce, satisfies } from 'semver'; import { dedent } from 'ts-dedent'; -import { type PostinstallOptions } from '../../../lib/cli-storybook/src/add'; -import { DOCUMENTATION_LINK } from './constants'; -import { loadTemplate, updateConfigFile, updateWorkspaceFile } from './updateVitestFile'; +import { type PostinstallOptions } from '../../../lib/cli-storybook/src/add.ts'; +import { DOCUMENTATION_LINK } from './constants.ts'; +import { loadTemplate, updateConfigFile, updateWorkspaceFile } from './updateVitestFile.ts'; const ADDON_NAME = '@storybook/addon-vitest' as const; const EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.cts', '.mts', '.cjs', '.mjs']; diff --git a/code/addons/vitest/src/preset.ts b/code/addons/vitest/src/preset.ts index bb0f035959c7..c617621b8af9 100644 --- a/code/addons/vitest/src/preset.ts +++ b/code/addons/vitest/src/preset.ts @@ -35,11 +35,11 @@ import { type TriggerTestRunRequestPayload, type TriggerTestRunResponsePayload, storeOptions, -} from './constants'; -import { log } from './logger'; -import { runTestRunner } from './node/boot-test-runner'; -import type { CachedState, ErrorLike, StoreState } from './types'; -import type { StoreEvent } from './types'; +} from './constants.ts'; +import { log } from './logger.ts'; +import { runTestRunner } from './node/boot-test-runner.ts'; +import type { CachedState, ErrorLike, StoreState } from './types.ts'; +import type { StoreEvent } from './types.ts'; type Event = | { diff --git a/code/addons/vitest/src/updateVitestFile.config.3.2.test.ts b/code/addons/vitest/src/updateVitestFile.config.3.2.test.ts index 477c8c0ddcba..28ddb27aa149 100644 --- a/code/addons/vitest/src/updateVitestFile.config.3.2.test.ts +++ b/code/addons/vitest/src/updateVitestFile.config.3.2.test.ts @@ -4,8 +4,8 @@ import { describe, expect, it, vi } from 'vitest'; import * as babel from 'storybook/internal/babel'; -import { getDiff } from '../../../core/src/core-server/utils/save-story/getDiff'; -import { loadTemplate, updateConfigFile } from './updateVitestFile'; +import { getDiff } from '../../../core/src/core-server/utils/save-story/getDiff.ts'; +import { loadTemplate, updateConfigFile } from './updateVitestFile.ts'; vi.mock('storybook/internal/node-logger', () => ({ logger: { diff --git a/code/addons/vitest/src/updateVitestFile.config.4.test.ts b/code/addons/vitest/src/updateVitestFile.config.4.test.ts index 4cf0e1f576f4..f0d17a06c0df 100644 --- a/code/addons/vitest/src/updateVitestFile.config.4.test.ts +++ b/code/addons/vitest/src/updateVitestFile.config.4.test.ts @@ -4,8 +4,8 @@ import { describe, expect, it, vi } from 'vitest'; import * as babel from 'storybook/internal/babel'; -import { getDiff } from '../../../core/src/core-server/utils/save-story/getDiff'; -import { loadTemplate, updateConfigFile } from './updateVitestFile'; +import { getDiff } from '../../../core/src/core-server/utils/save-story/getDiff.ts'; +import { loadTemplate, updateConfigFile } from './updateVitestFile.ts'; vi.mock('storybook/internal/node-logger', () => ({ logger: { diff --git a/code/addons/vitest/src/updateVitestFile.config.test.ts b/code/addons/vitest/src/updateVitestFile.config.test.ts index adfb71f7e876..685cc866db54 100644 --- a/code/addons/vitest/src/updateVitestFile.config.test.ts +++ b/code/addons/vitest/src/updateVitestFile.config.test.ts @@ -4,8 +4,8 @@ import { describe, expect, it, vi } from 'vitest'; import * as babel from 'storybook/internal/babel'; -import { getDiff } from '../../../core/src/core-server/utils/save-story/getDiff'; -import { loadTemplate, updateConfigFile } from './updateVitestFile'; +import { getDiff } from '../../../core/src/core-server/utils/save-story/getDiff.ts'; +import { loadTemplate, updateConfigFile } from './updateVitestFile.ts'; vi.mock('storybook/internal/node-logger', () => ({ logger: { diff --git a/code/addons/vitest/src/updateVitestFile.config.workspace.test.ts b/code/addons/vitest/src/updateVitestFile.config.workspace.test.ts index 5f5784816d21..09ac35568784 100644 --- a/code/addons/vitest/src/updateVitestFile.config.workspace.test.ts +++ b/code/addons/vitest/src/updateVitestFile.config.workspace.test.ts @@ -4,8 +4,8 @@ import { describe, expect, it, vi } from 'vitest'; import * as babel from 'storybook/internal/babel'; -import { getDiff } from '../../../core/src/core-server/utils/save-story/getDiff'; -import { loadTemplate, updateWorkspaceFile } from './updateVitestFile'; +import { getDiff } from '../../../core/src/core-server/utils/save-story/getDiff.ts'; +import { loadTemplate, updateWorkspaceFile } from './updateVitestFile.ts'; vi.mock('storybook/internal/node-logger', () => ({ logger: { diff --git a/code/addons/vitest/src/updateVitestFile.test.ts b/code/addons/vitest/src/updateVitestFile.test.ts index 321918e77221..28a25578806b 100644 --- a/code/addons/vitest/src/updateVitestFile.test.ts +++ b/code/addons/vitest/src/updateVitestFile.test.ts @@ -2,7 +2,7 @@ import { join } from 'node:path'; import { describe, expect, it, vi } from 'vitest'; -import { loadTemplate } from './updateVitestFile'; +import { loadTemplate } from './updateVitestFile.ts'; vi.mock('storybook/internal/node-logger', () => ({ logger: { diff --git a/code/addons/vitest/src/updateVitestFile.ts b/code/addons/vitest/src/updateVitestFile.ts index 5ba8f8efd349..157aacad84df 100644 --- a/code/addons/vitest/src/updateVitestFile.ts +++ b/code/addons/vitest/src/updateVitestFile.ts @@ -16,13 +16,13 @@ import { normalize } from 'pathe'; async function getTemplatePath(name: string) { switch (name) { case 'vitest.config.template': - return import('../templates/vitest.config.template?raw'); + return import('../templates/vitest.config.template.ts?raw'); case 'vitest.config.4.template': - return import('../templates/vitest.config.4.template?raw'); + return import('../templates/vitest.config.4.template.ts?raw'); case 'vitest.config.3.2.template': - return import('../templates/vitest.config.3.2.template?raw'); + return import('../templates/vitest.config.3.2.template.ts?raw'); case 'vitest.workspace.template': - return import('../templates/vitest.workspace.template?raw'); + return import('../templates/vitest.workspace.template.ts?raw'); default: throw new Error(`Unknown template: ${name}`); } diff --git a/code/addons/vitest/src/use-test-provider-state.ts b/code/addons/vitest/src/use-test-provider-state.ts index 57ef86103464..79ebc267b88b 100644 --- a/code/addons/vitest/src/use-test-provider-state.ts +++ b/code/addons/vitest/src/use-test-provider-state.ts @@ -17,10 +17,15 @@ import { experimental_useUniversalStore, } from 'storybook/manager-api'; -import { ADDON_ID, STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST } from './constants'; -import type { StoreState } from './types'; +import { ADDON_ID, STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST } from './constants.ts'; +import type { StoreState } from './types.ts'; -export type StatusValueToStoryIds = Record; +type TestStatusValue = Extract< + StatusValue, + `status-value:${'pending' | 'success' | 'error' | 'warning' | 'unknown'}` +>; + +export type StatusValueToStoryIds = Record; const statusValueToStoryIds = ( allStatuses: StatusesByStoryIdAndTypeId, @@ -43,7 +48,7 @@ const statusValueToStoryIds = ( if (!status) { return; } - statusValueToStoryIdsMap[status.value].push(status.storyId); + statusValueToStoryIdsMap[status.value as TestStatusValue].push(status.storyId); }); return statusValueToStoryIdsMap; diff --git a/code/addons/vitest/src/utils.ts b/code/addons/vitest/src/utils.ts index ce8fed22e9d2..4b7b280e5876 100644 --- a/code/addons/vitest/src/utils.ts +++ b/code/addons/vitest/src/utils.ts @@ -1,4 +1,4 @@ -import type { ErrorLike } from './types'; +import type { ErrorLike } from './types.ts'; export function errorToErrorLike(error: Error): ErrorLike { return { diff --git a/code/addons/vitest/src/vitest-plugin/index.ts b/code/addons/vitest/src/vitest-plugin/index.ts index 8d3f7c2666a4..17a42dd20882 100644 --- a/code/addons/vitest/src/vitest-plugin/index.ts +++ b/code/addons/vitest/src/vitest-plugin/index.ts @@ -39,9 +39,9 @@ import { dedent } from 'ts-dedent'; import type { PluginOption } from 'vite'; // Shared plugins from builder-vite (relative import to prebundle without adding a package dependency) -import { withoutVitePlugins } from '../../../../builders/builder-vite/src/utils/without-vite-plugins'; -import type { InternalOptions, UserOptions } from './types'; -import { requiresProjectAnnotations } from './utils'; +import { withoutVitePlugins } from '../../../../builders/builder-vite/src/utils/without-vite-plugins.ts'; +import type { InternalOptions, UserOptions } from './types.ts'; +import { requiresProjectAnnotations } from './utils.ts'; const WORKING_DIR = process.cwd(); @@ -303,14 +303,9 @@ export const storybookTest = async (options?: UserOptions): Promise => finalOptions.includeStories = includeStories; const projectId = oneWayHash(finalOptions.configDir); - const previewOrConfigFile = loadPreviewOrConfigFile({ configDir: finalOptions.configDir }); - const previewConfig = previewOrConfigFile ? await readConfig(previewOrConfigFile) : undefined; - const isCSF4 = previewConfig ? isCsfFactoryPreview(previewConfig) : false; - const areProjectAnnotationRequired = await requiresProjectAnnotations( nonMutableInputConfig.test, - finalOptions, - isCSF4 + finalOptions ); const internalSetupFiles = ( @@ -318,7 +313,6 @@ export const storybookTest = async (options?: UserOptions): Promise => '@storybook/addon-vitest/internal/setup-file', areProjectAnnotationRequired && '@storybook/addon-vitest/internal/setup-file-with-project-annotations', - isCSF4 && previewOrConfigFile, ].filter(Boolean) as string[] ).map((filePath) => fileURLToPath(import.meta.resolve(filePath))); diff --git a/code/addons/vitest/src/vitest-plugin/setup-file.test.ts b/code/addons/vitest/src/vitest-plugin/setup-file.test.ts index 9a2ddfd93ff8..5f3540e5db05 100644 --- a/code/addons/vitest/src/vitest-plugin/setup-file.test.ts +++ b/code/addons/vitest/src/vitest-plugin/setup-file.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { type Task, modifyErrorMessage } from './setup-file'; +import { type Task, modifyErrorMessage } from './setup-file.ts'; describe('modifyErrorMessage', () => { const originalUrl = import.meta.env.__STORYBOOK_URL__; diff --git a/code/addons/vitest/src/vitest-plugin/setup-file.ts b/code/addons/vitest/src/vitest-plugin/setup-file.ts index 99ff85eed5ca..5621698ab1bc 100644 --- a/code/addons/vitest/src/vitest-plugin/setup-file.ts +++ b/code/addons/vitest/src/vitest-plugin/setup-file.ts @@ -3,7 +3,7 @@ import type { RunnerTask } from 'vitest'; import { Channel } from 'storybook/internal/channels'; -import { COMPONENT_TESTING_PANEL_ID } from '../constants'; +import { COMPONENT_TESTING_PANEL_ID } from '../constants.ts'; declare global { // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/code/addons/vitest/src/vitest-plugin/test-utils.ts b/code/addons/vitest/src/vitest-plugin/test-utils.ts index ec39bbc0734f..876ced8f055c 100644 --- a/code/addons/vitest/src/vitest-plugin/test-utils.ts +++ b/code/addons/vitest/src/vitest-plugin/test-utils.ts @@ -6,7 +6,7 @@ import type { ComponentAnnotations, ComposedStoryFn, Renderer } from 'storybook/ import { server } from '@vitest/browser/context'; import { type Report, composeStory, getCsfFactoryAnnotations } from 'storybook/preview-api'; -import { setViewport } from './viewports'; +import { setViewport } from './viewports.ts'; declare module 'vitest/browser' { interface BrowserCommands { diff --git a/code/addons/vitest/src/vitest-plugin/utils.ts b/code/addons/vitest/src/vitest-plugin/utils.ts index dfa3c74cc0e6..20f5a705e4a8 100644 --- a/code/addons/vitest/src/vitest-plugin/utils.ts +++ b/code/addons/vitest/src/vitest-plugin/utils.ts @@ -7,7 +7,7 @@ import { CLI_COLORS, logger } from 'storybook/internal/node-logger'; import { dirname, resolve } from 'pathe'; import { dedent } from 'ts-dedent'; -import type { InternalOptions } from './types'; +import type { InternalOptions } from './types.ts'; let hasLoggedDeprecationWarning = false; @@ -20,8 +20,7 @@ const logBoxOnce = (message: string) => { export async function requiresProjectAnnotations( testConfig: ViteUserConfig['test'] | undefined, - finalOptions: InternalOptions, - isCSF4: boolean + finalOptions: InternalOptions ) { const setupFiles = Array.isArray(testConfig?.setupFiles) ? testConfig.setupFiles @@ -58,8 +57,6 @@ export async function requiresProjectAnnotations( You can safely remove the "setProjectAnnotations" call from your setup file, or remove the file entirely if you don't have custom code there. `); - return false; - } else if (isCSF4) { return false; } diff --git a/code/addons/vitest/src/vitest-plugin/viewports.test.ts b/code/addons/vitest/src/vitest-plugin/viewports.test.ts index 5ab205aa36f8..689b613b29ae 100644 --- a/code/addons/vitest/src/vitest-plugin/viewports.test.ts +++ b/code/addons/vitest/src/vitest-plugin/viewports.test.ts @@ -8,7 +8,7 @@ import { type ViewportsGlobal, type ViewportsParam, setViewport, -} from './viewports'; +} from './viewports.ts'; vi.mock('@vitest/browser/context', () => ({ page: { diff --git a/code/addons/vitest/vitest.config.ts b/code/addons/vitest/vitest.config.ts index 16d2fd08535c..25fae90d1f77 100644 --- a/code/addons/vitest/vitest.config.ts +++ b/code/addons/vitest/vitest.config.ts @@ -1,6 +1,6 @@ import { defineConfig, mergeConfig } from 'vitest/config'; -import { vitestCommonConfig } from '../../vitest.shared'; +import { vitestCommonConfig } from '../../vitest.shared.ts'; export default mergeConfig( vitestCommonConfig, diff --git a/code/builders/builder-vite/build-config.ts b/code/builders/builder-vite/build-config.ts index f6bc2c790c0f..8095981bad8b 100644 --- a/code/builders/builder-vite/build-config.ts +++ b/code/builders/builder-vite/build-config.ts @@ -1,4 +1,4 @@ -import type { BuildEntries } from '../../../scripts/build/utils/entry-utils'; +import type { BuildEntries } from '../../../scripts/build/utils/entry-utils.ts'; const config: BuildEntries = { entries: { diff --git a/code/builders/builder-vite/input/iframe.html b/code/builders/builder-vite/input/iframe.html index 1637f04eb9e8..b161747bb105 100644 --- a/code/builders/builder-vite/input/iframe.html +++ b/code/builders/builder-vite/input/iframe.html @@ -70,10 +70,10 @@ if (hostname !== 'localhost' && globalThis.CONFIG_TYPE === 'DEVELOPMENT') { const message = `Failed to load the Storybook preview file 'vite-app.js': -It looks like you're visiting the Storybook development server on another hostname than localhost: '${hostname}', but you haven't configured the necessary security features to support this. -Please re-run your Storybook development server with the '--host ${hostname}' flag, or manually configure your Vite allowedHosts configuration with viteFinal. + It looks like you're visiting the Storybook development server on another hostname than localhost: '${hostname}', but you haven't configured the necessary security features to support this. + Please re-run your Storybook development server with the '--host ${hostname}' flag, or manually configure your Vite allowedHosts configuration with viteFinal. -See:`; + See:`; const docs = [ 'https://storybook.js.org/docs/api/cli-options#dev', 'https://storybook.js.org/docs/api/main-config/main-config-vite-final', @@ -85,7 +85,7 @@ `

${message.replaceAll( '\n', '
' - )}

    ${docs.map((doc) => `
  • ${doc}
  • `).join('')}

      `; + )}
        ${docs.map((doc) => `
      • ${doc}
      • `).join('')}

      `; return; } } diff --git a/code/builders/builder-vite/package.json b/code/builders/builder-vite/package.json index d62471eab30d..f3e89f0d3942 100644 --- a/code/builders/builder-vite/package.json +++ b/code/builders/builder-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/builder-vite", - "version": "10.4.0-alpha.3", + "version": "10.4.0-alpha.7", "description": "A Storybook builder to dev and build with Vite", "keywords": [ "storybook", diff --git a/code/builders/builder-vite/src/build.ts b/code/builders/builder-vite/src/build.ts index 3a0891fae7aa..3f88b0e48e30 100644 --- a/code/builders/builder-vite/src/build.ts +++ b/code/builders/builder-vite/src/build.ts @@ -4,12 +4,12 @@ import type { Options } from 'storybook/internal/types'; import { dedent } from 'ts-dedent'; import type { InlineConfig } from 'vite'; -import { createViteLogger } from './logger'; -import type { WebpackStatsPlugin } from './plugins'; -import { hasVitePlugins } from './utils/has-vite-plugins'; -import { bundlerOptionsKey } from './utils/vite-features'; -import { withoutVitePlugins } from './utils/without-vite-plugins'; -import { commonConfig } from './vite-config'; +import { createViteLogger } from './logger.ts'; +import type { WebpackStatsPlugin } from './plugins/index.ts'; +import { hasVitePlugins } from './utils/has-vite-plugins.ts'; +import { bundlerOptionsKey } from './utils/vite-features.ts'; +import { withoutVitePlugins } from './utils/without-vite-plugins.ts'; +import { commonConfig } from './vite-config.ts'; function findPlugin(config: InlineConfig, name: string) { return config.plugins?.find((p) => p && 'name' in p && p.name === name); diff --git a/code/builders/builder-vite/src/codegen-importfn-script.test.ts b/code/builders/builder-vite/src/codegen-importfn-script.test.ts index 35edde4662e6..d88880bba10e 100644 --- a/code/builders/builder-vite/src/codegen-importfn-script.test.ts +++ b/code/builders/builder-vite/src/codegen-importfn-script.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest'; import type { StoryIndex } from 'storybook/internal/types'; -import { generateImportFnScriptCode } from './codegen-importfn-script'; +import { generateImportFnScriptCode } from './codegen-importfn-script.ts'; describe('generateImportFnScriptCode', () => { it('should correctly map story paths to import functions for POSIX paths', async () => { diff --git a/code/builders/builder-vite/src/codegen-importfn-script.ts b/code/builders/builder-vite/src/codegen-importfn-script.ts index b1f67fa72221..dbd4d3c040f6 100644 --- a/code/builders/builder-vite/src/codegen-importfn-script.ts +++ b/code/builders/builder-vite/src/codegen-importfn-script.ts @@ -4,7 +4,7 @@ import { genDynamicImport, genObjectFromRawEntries } from 'knitwork'; import { join, normalize, relative } from 'pathe'; import { dedent } from 'ts-dedent'; -import { getUniqueImportPaths } from './utils/unique-import-paths'; +import { getUniqueImportPaths } from './utils/unique-import-paths.ts'; /** * This function takes the story index and creates a mapping between the stories' relative paths to diff --git a/code/builders/builder-vite/src/codegen-modern-iframe-script.test.ts b/code/builders/builder-vite/src/codegen-modern-iframe-script.test.ts index 9b3e1c6c7625..e5161f98a6b3 100644 --- a/code/builders/builder-vite/src/codegen-modern-iframe-script.test.ts +++ b/code/builders/builder-vite/src/codegen-modern-iframe-script.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { generateModernIframeScriptCodeFromPreviews } from './codegen-modern-iframe-script'; -import { generateAddonSetupCode } from './codegen-set-addon-channel'; -import { optimizeViteDeps } from './preset'; +import { generateModernIframeScriptCodeFromPreviews } from './codegen-modern-iframe-script.ts'; +import { generateAddonSetupCode } from './codegen-set-addon-channel.ts'; +import { optimizeViteDeps } from './preset.ts'; describe('generateModernIframeScriptCodeFromPreviews', () => { it('handle one annotation', async () => { diff --git a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts index e8a615db5b59..f756e13445e8 100644 --- a/code/builders/builder-vite/src/codegen-modern-iframe-script.ts +++ b/code/builders/builder-vite/src/codegen-modern-iframe-script.ts @@ -4,8 +4,8 @@ import type { Options } from 'storybook/internal/types'; import { dedent } from 'ts-dedent'; -import { VIRTUAL_ID as PROJECT_ANNOTATIONS_VIRTUAL_ID } from './plugins/storybook-project-annotations-plugin'; -import { SB_VIRTUAL_FILES } from './virtual-file-names'; +import { VIRTUAL_ID as PROJECT_ANNOTATIONS_VIRTUAL_ID } from './plugins/storybook-project-annotations-plugin.ts'; +import { SB_VIRTUAL_FILES } from './virtual-file-names.ts'; export async function generateModernIframeScriptCode(options: Options) { const frameworkName = await getFrameworkName(options); diff --git a/code/builders/builder-vite/src/codegen-project-annotations.ts b/code/builders/builder-vite/src/codegen-project-annotations.ts index 9d13c9390ec9..78dc6499f2fe 100644 --- a/code/builders/builder-vite/src/codegen-project-annotations.ts +++ b/code/builders/builder-vite/src/codegen-project-annotations.ts @@ -6,7 +6,7 @@ import { genArrayFromRaw, genImport, genSafeVariableName } from 'knitwork'; import { filename } from 'pathe/utils'; import { dedent } from 'ts-dedent'; -import { processPreviewAnnotation } from './utils/process-preview-annotation'; +import { processPreviewAnnotation } from './utils/process-preview-annotation.ts'; /** Generates the code for the `PROJECT_ANNOTATIONS_FILE` virtual module. */ export async function generateProjectAnnotationsCode(options: Options, projectRoot: string) { @@ -105,6 +105,11 @@ export function generateProjectAnnotationsCodeFromPreviews(options: { `.trim(); } +/** djb2 hash — http://www.cse.yorku.ca/~oz/hash.html */ function hash(value: string) { - return value.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0); + let acc = 5381; + for (let i = 0; i < value.length; i++) { + acc = ((acc << 5) + acc + value.charCodeAt(i)) >>> 0; + } + return acc; } diff --git a/code/builders/builder-vite/src/index.test.ts b/code/builders/builder-vite/src/index.test.ts new file mode 100644 index 000000000000..cdcccfd3823b --- /dev/null +++ b/code/builders/builder-vite/src/index.test.ts @@ -0,0 +1,402 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { logger } from 'storybook/internal/node-logger'; +import type { ModuleNode as StorybookModuleNode, Options } from 'storybook/internal/types'; +import type { ViteDevServer } from 'vite'; + +import { bail, onModuleGraphChange, start } from './index.ts'; +import { createViteServer } from './vite-server.ts'; + +vi.mock('storybook/internal/node-logger', { spy: true }); +vi.mock('./vite-server', { spy: true }); + +type ViteModuleNodeLike = { + file: string | null; + type: StorybookModuleNode['type']; + importers: Set; + importedModules: Set; +}; + +function createViteModuleNode( + file: string | null, + type: StorybookModuleNode['type'] = 'js' +): ViteModuleNodeLike { + return { + file, + type, + importers: new Set(), + importedModules: new Set(), + }; +} + +function createFileToModulesMap(...entries: Array<[string, Set]>) { + return new Map(entries) as ViteDevServer['moduleGraph']['fileToModulesMap']; +} + +type WatcherHandler = (...args: unknown[]) => void; +type FakeWatcher = { + on: ReturnType; + off: ReturnType; + emit: (event: string, ...args: unknown[]) => void; + listenerCount: (event: string) => number; +}; + +function createFakeViteServer() { + const watcherListeners = new Map>(); + const watcher = {} as FakeWatcher; + + watcher.on = vi.fn((event: string, handler: WatcherHandler) => { + const handlers = watcherListeners.get(event) ?? new Set(); + handlers.add(handler); + watcherListeners.set(event, handlers); + return watcher; + }); + watcher.off = vi.fn((event: string, handler: WatcherHandler) => { + watcherListeners.get(event)?.delete(handler); + return watcher; + }); + watcher.emit = (event: string, ...args: unknown[]) => { + watcherListeners.get(event)?.forEach((handler) => { + handler(...args); + }); + watcherListeners.get('all')?.forEach((handler) => { + handler(event, ...args); + }); + }; + watcher.listenerCount = (event: string) => watcherListeners.get(event)?.size ?? 0; + + return { + watcher, + moduleGraph: { + fileToModulesMap: createFileToModulesMap(), + }, + middlewares: { + handle: vi.fn(), + }, + warmupRequest: vi.fn().mockResolvedValue(undefined), + transformIndexHtml: vi.fn().mockResolvedValue(''), + waitForRequestsIdle: vi.fn().mockResolvedValue(undefined), + close: vi.fn().mockResolvedValue(undefined), + } as unknown as ViteDevServer & { + watcher: typeof watcher; + }; +} + +function createStartArgs(storyImportPaths: string[] = []): Parameters[0] { + const indexGenerator = { + getIndex: vi.fn().mockResolvedValue({ + entries: Object.fromEntries( + storyImportPaths.map((importPath, index) => [`story-${index}`, { importPath }]) + ), + }), + }; + + return { + startTime: process.hrtime(), + options: { + presets: { + apply: vi.fn().mockResolvedValue(indexGenerator), + }, + } as unknown as Options, + router: { + get: vi.fn(), + use: vi.fn(), + } as unknown as Parameters[0]['router'], + server: {} as Parameters[0]['server'], + channel: {} as Parameters[0]['channel'], + }; +} + +describe('onModuleGraphChange', () => { + let fakeViteServer: ReturnType; + + async function startChangeDetection( + fileToModulesMap = createFileToModulesMap([ + '/src/Button.tsx', + new Set([createViteModuleNode('/src/Button.tsx')]), + ]) + ) { + fakeViteServer.moduleGraph.fileToModulesMap = fileToModulesMap; + await start(createStartArgs([...fileToModulesMap.keys()])); + await vi.advanceTimersByTimeAsync(1000); + } + + beforeEach(() => { + vi.useFakeTimers(); + fakeViteServer = createFakeViteServer(); + vi.mocked(createViteServer).mockResolvedValue(fakeViteServer); + vi.mocked(logger.error).mockImplementation(() => undefined); + }); + + afterEach(async () => { + await bail(); + vi.useRealTimers(); + vi.clearAllMocks(); + }); + + it('registers callbacks and unsubscribes them', async () => { + const cb = vi.fn(); + const unsubscribe = onModuleGraphChange(cb); + + expect(unsubscribe).toEqual(expect.any(Function)); + + await startChangeDetection(); + cb.mockClear(); + + fakeViteServer.watcher.emit('change', '/src/Button.tsx'); + + await vi.advanceTimersByTimeAsync(100); + + expect(cb).toHaveBeenCalledTimes(1); + + unsubscribe(); + + fakeViteServer.watcher.emit('change', '/src/Button.tsx'); + await vi.advanceTimersByTimeAsync(100); + + expect(cb).toHaveBeenCalledTimes(1); + }); + + it('passes the module graph payload to listeners', async () => { + const entry = createViteModuleNode('/src/Button.tsx'); + const fileToModulesMap = createFileToModulesMap(['/src/Button.tsx', new Set([entry])]); + + const cb = vi.fn(); + onModuleGraphChange(cb); + + await startChangeDetection(fileToModulesMap); + cb.mockClear(); + + fakeViteServer.watcher.emit('change', '/src/Button.tsx'); + + await vi.advanceTimersByTimeAsync(100); + + const event = cb.mock.calls[0]?.[0]; + const moduleGraph = event?.moduleGraph; + + expect(cb).toHaveBeenCalledWith({ + type: 'moduleGraph', + moduleGraph: expect.any(Map), + }); + expect(moduleGraph?.has('/src/Button.tsx')).toBe(true); + }); + + it('triggers change events after the debounce delay', async () => { + const cb = vi.fn(); + onModuleGraphChange(cb); + + await startChangeDetection(); + cb.mockClear(); + + fakeViteServer.watcher.emit('change', '/src/Button.tsx'); + + await vi.advanceTimersByTimeAsync(50); + expect(cb).not.toHaveBeenCalled(); + + await vi.advanceTimersByTimeAsync(50); + expect(cb).toHaveBeenCalledTimes(1); + }); + + it('triggers add events after the debounce delay', async () => { + const cb = vi.fn(); + onModuleGraphChange(cb); + + await startChangeDetection(); + cb.mockClear(); + + fakeViteServer.watcher.emit('add', '/src/Button.tsx'); + + await vi.advanceTimersByTimeAsync(100); + + expect(cb).toHaveBeenCalledTimes(1); + }); + + it('triggers unlink events after the debounce delay', async () => { + const cb = vi.fn(); + onModuleGraphChange(cb); + + await startChangeDetection(); + cb.mockClear(); + + fakeViteServer.watcher.emit('unlink', '/src/Button.tsx'); + + await vi.advanceTimersByTimeAsync(100); + + expect(cb).toHaveBeenCalledTimes(1); + }); + + it('debounces multiple rapid events into a single callback', async () => { + const cb = vi.fn(); + onModuleGraphChange(cb); + + await startChangeDetection(); + cb.mockClear(); + + fakeViteServer.watcher.emit('change', '/src/Button.tsx'); + fakeViteServer.watcher.emit('add', '/src/Button.tsx'); + fakeViteServer.watcher.emit('change', '/src/Button.tsx'); + + await vi.advanceTimersByTimeAsync(100); + + expect(cb).toHaveBeenCalledTimes(1); + }); + + it('notifies multiple listeners', async () => { + const cb1 = vi.fn(); + const cb2 = vi.fn(); + + onModuleGraphChange(cb1); + onModuleGraphChange(cb2); + + await startChangeDetection(); + cb1.mockClear(); + cb2.mockClear(); + + fakeViteServer.watcher.emit('change', '/src/Button.tsx'); + + await vi.advanceTimersByTimeAsync(100); + + expect(cb1).toHaveBeenCalledTimes(1); + expect(cb2).toHaveBeenCalledTimes(1); + }); + + it('removes the all-event watcher during bail to avoid leaks across restarts', async () => { + const cb = vi.fn(); + onModuleGraphChange(cb); + + await startChangeDetection(); + expect(fakeViteServer.watcher.listenerCount('all')).toBe(1); + + await bail(); + expect(fakeViteServer.watcher.listenerCount('all')).toBe(0); + + await start(createStartArgs(['/src/Button.tsx'])); + await vi.advanceTimersByTimeAsync(1000); + expect(fakeViteServer.watcher.listenerCount('all')).toBe(0); + + fakeViteServer.watcher.emit('change', '/src/Button.tsx'); + await vi.advanceTimersByTimeAsync(100); + + expect(cb).not.toHaveBeenCalled(); + expect(fakeViteServer.watcher.off).toHaveBeenCalledWith('all', expect.any(Function)); + }); + + it('clears the module-graph polling interval during bail', async () => { + onModuleGraphChange(vi.fn()); + + await start(createStartArgs()); + await vi.advanceTimersByTimeAsync(0); + + expect(vi.getTimerCount()).toBe(1); + + await bail(); + + fakeViteServer.moduleGraph.fileToModulesMap = createFileToModulesMap([ + '/src/Button.tsx', + new Set([createViteModuleNode('/src/Button.tsx')]), + ]); + + await vi.advanceTimersByTimeAsync(1000); + + expect(vi.getTimerCount()).toBe(0); + expect(fakeViteServer.waitForRequestsIdle).not.toHaveBeenCalled(); + }); + + it('rejects listeners registered after start', async () => { + await start(createStartArgs()); + + expect(() => onModuleGraphChange(vi.fn())).toThrow( + 'Vite module graph listeners must be registered before the builder starts.' + ); + }); + + it('does not reattach the watcher if bail runs while waiting for idle requests', async () => { + const cb = vi.fn(); + onModuleGraphChange(cb); + + let resolveIdle: (() => void) | undefined; + fakeViteServer.moduleGraph.fileToModulesMap = createFileToModulesMap([ + '/src/Button.tsx', + new Set([createViteModuleNode('/src/Button.tsx')]), + ]); + fakeViteServer.waitForRequestsIdle = vi.fn().mockImplementation( + () => + new Promise((resolve) => { + resolveIdle = resolve; + }) + ); + + await start(createStartArgs(['/src/Button.tsx'])); + await vi.advanceTimersByTimeAsync(1000); + + await bail(); + resolveIdle?.(); + await Promise.resolve(); + await Promise.resolve(); + + expect(fakeViteServer.watcher.listenerCount('all')).toBe(0); + expect(fakeViteServer.watcher.on).not.toHaveBeenCalledWith('all', expect.any(Function)); + expect(cb).not.toHaveBeenCalled(); + }); + + it('logs and swallows rejected startup work', async () => { + const cb = vi.fn(); + onModuleGraphChange(cb); + + const args = createStartArgs(); + vi.mocked(args.options.presets.apply).mockResolvedValue({ + getIndex: vi.fn().mockRejectedValue(new Error('index failed')), + } as never); + + await expect(start(args)).resolves.toBeDefined(); + await Promise.resolve(); + + expect(logger.error).toHaveBeenCalledWith('Failed to initialize Vite change detection'); + expect(logger.error).toHaveBeenCalledWith(expect.objectContaining({ message: 'index failed' })); + expect(cb).toHaveBeenCalledWith({ + type: 'error', + error: expect.objectContaining({ message: 'index failed' }), + }); + expect(vi.getTimerCount()).toBe(0); + }); + + it('logs polling failures and clears the interval', async () => { + const cb = vi.fn(); + onModuleGraphChange(cb); + fakeViteServer.moduleGraph.fileToModulesMap = createFileToModulesMap([ + '/src/Button.tsx', + new Set([createViteModuleNode('/src/Button.tsx')]), + ]); + fakeViteServer.waitForRequestsIdle = vi.fn().mockRejectedValue(new Error('idle failed')); + + await start(createStartArgs(['/src/Button.tsx'])); + await vi.advanceTimersByTimeAsync(1000); + + expect(logger.error).toHaveBeenCalledWith('Failed to complete Vite change detection startup'); + expect(logger.error).toHaveBeenCalledWith(expect.objectContaining({ message: 'idle failed' })); + expect(cb).toHaveBeenCalledWith({ + type: 'error', + error: expect.objectContaining({ message: 'idle failed' }), + }); + expect(vi.getTimerCount()).toBe(0); + expect(fakeViteServer.watcher.listenerCount('all')).toBe(0); + }); + + it('notifies listeners when module graph startup times out', async () => { + const cb = vi.fn(); + onModuleGraphChange(cb); + + await start(createStartArgs(['/src/Button.tsx'])); + await vi.advanceTimersByTimeAsync(31_000); + + expect(logger.error).toHaveBeenCalledWith('Failed to complete Vite change detection startup'); + expect(cb).toHaveBeenCalledWith({ + type: 'unavailable', + reason: 'Timed out while waiting for the Vite module graph to initialize', + error: expect.objectContaining({ + message: 'Timed out while waiting for the Vite module graph to initialize', + }), + }); + expect(vi.getTimerCount()).toBe(0); + }); +}); diff --git a/code/builders/builder-vite/src/index.ts b/code/builders/builder-vite/src/index.ts index f447e76b0419..30f218613ba5 100644 --- a/code/builders/builder-vite/src/index.ts +++ b/code/builders/builder-vite/src/index.ts @@ -2,19 +2,31 @@ import { readFile } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; -import { NoStatsForViteDevError } from 'storybook/internal/server-errors'; -import type { Middleware, Options } from 'storybook/internal/types'; +import { logger } from 'storybook/internal/node-logger'; +import { + NoStatsForViteDevError, + ViteModuleGraphSubscriptionError, +} from 'storybook/internal/server-errors'; +import type { StoryIndexGenerator } from 'storybook/internal/core-server'; +import type { + Builder, + Middleware, + ModuleGraph, + ModuleGraphChangeEvent, + Options, +} from 'storybook/internal/types'; import type { ViteDevServer } from 'vite'; -import { build as viteBuild } from './build'; -import type { ViteBuilder } from './types'; -import { createViteServer } from './vite-server'; +import { build as viteBuild } from './build.ts'; +import type { ViteBuilder } from './types.ts'; +import { createViteServer } from './vite-server.ts'; +import { buildModuleGraph } from './utils/build-module-graph.ts'; -export { withoutVitePlugins } from './utils/without-vite-plugins'; -export { hasVitePlugins } from './utils/has-vite-plugins'; +export { withoutVitePlugins } from './utils/without-vite-plugins.ts'; +export { hasVitePlugins } from './utils/has-vite-plugins.ts'; -export * from './types'; +export * from './types.ts'; function iframeHandler(options: Options, server: ViteDevServer): Middleware { return async (req, res) => { @@ -33,22 +45,150 @@ function iframeHandler(options: Options, server: ViteDevServer): Middleware { } let server: ViteDevServer; +const listeners = new Set<(event: ModuleGraphChangeEvent) => void>(); +let debounce: ReturnType | undefined; +let watcherChangeHandler: (() => void) | undefined; +let waitForModuleGraph: ReturnType | undefined; +let moduleGraphRegistrationClosed = false; + +function clearModuleGraphPolling(): void { + if (waitForModuleGraph) { + clearInterval(waitForModuleGraph); + waitForModuleGraph = undefined; + } +} + +function notifyListeners(moduleGraph: ModuleGraph): void { + listeners.forEach((listener) => { + listener({ type: 'moduleGraph', moduleGraph }); + }); +} + +function notifyListenersOfStartupFailure( + event: Extract +): void { + listeners.forEach((listener) => { + listener(event); + }); +} export async function bail(): Promise { + if (watcherChangeHandler) { + server?.watcher.off('all', watcherChangeHandler); + watcherChangeHandler = undefined; + } + + clearModuleGraphPolling(); + + if (debounce) { + clearTimeout(debounce); + debounce = undefined; + } + + moduleGraphRegistrationClosed = false; + listeners.clear(); return server?.close(); } +export const onModuleGraphChange: NonNullable['onModuleGraphChange']> = (cb) => { + if (moduleGraphRegistrationClosed) { + throw new ViteModuleGraphSubscriptionError(); + } + listeners.add(cb); + + return () => { + listeners.delete(cb); + }; +}; + +const startChangeDetection = async (options: Options) => { + const startTime = process.hrtime(); + const indexGenerator = await options.presets.apply('storyIndexGenerator'); + const storyIndex = await indexGenerator.getIndex(); + const importPaths = new Set(Object.values(storyIndex.entries).map((entry) => entry.importPath)); + + // Warm up the module graph for all story files + await Promise.all(Array.from(importPaths, (importPath) => server.warmupRequest(importPath))); + + // Wait for the module graph to be ready by polling for it to be non-empty + waitForModuleGraph = setInterval(() => { + void (async () => { + try { + if (!watcherChangeHandler) { + clearModuleGraphPolling(); + return; + } + + if (process.hrtime(startTime)[0] > 30) { + clearModuleGraphPolling(); + const error = new Error( + 'Timed out while waiting for the Vite module graph to initialize' + ); + logger.error('Failed to complete Vite change detection startup'); + logger.error(error); + notifyListenersOfStartupFailure({ + type: 'unavailable', + reason: error.message, + error, + }); + return; + } + + if (server.moduleGraph.fileToModulesMap.size > 0) { + clearModuleGraphPolling(); + await server.waitForRequestsIdle(); + if (!watcherChangeHandler) { + return; + } + + server.watcher.on('all', watcherChangeHandler); + watcherChangeHandler(); + } + } catch (error) { + clearModuleGraphPolling(); + logger.error('Failed to complete Vite change detection startup'); + logger.error(error instanceof Error ? error : String(error)); + notifyListenersOfStartupFailure({ + type: 'error', + error: error instanceof Error ? error : new Error(String(error)), + }); + } + })(); + }, 1000); +}; + export const start: ViteBuilder['start'] = async ({ startTime, options, router, server: devServer, }) => { + moduleGraphRegistrationClosed = true; server = await createViteServer(options as Options, devServer); router.get('/iframe.html', iframeHandler(options as Options, server)); router.use(server.middlewares); + if (listeners.size > 0) { + // Debounce handler to prevent multiple callback invocations when multiple files are edited + watcherChangeHandler = () => { + clearTimeout(debounce); + debounce = setTimeout(() => { + notifyListeners(buildModuleGraph(server.moduleGraph.fileToModulesMap)); + }, 100); + }; + // We intentionally don't await this. Cleanup happens in bail(). + void startChangeDetection(options).catch((error) => { + clearModuleGraphPolling(); + logger.error('Failed to initialize Vite change detection'); + logger.error(error instanceof Error ? error : String(error)); + notifyListenersOfStartupFailure({ + type: 'error', + error: error instanceof Error ? error : new Error(String(error)), + }); + }); + } + return { bail, stats: { diff --git a/code/builders/builder-vite/src/plugins/code-generator-plugin.ts b/code/builders/builder-vite/src/plugins/code-generator-plugin.ts index 91afe7da7f0c..78d31bcf3dab 100644 --- a/code/builders/builder-vite/src/plugins/code-generator-plugin.ts +++ b/code/builders/builder-vite/src/plugins/code-generator-plugin.ts @@ -6,17 +6,17 @@ import type { Options } from 'storybook/internal/types'; import type { Plugin } from 'vite'; -import { importMetaResolve } from '../../../../core/src/shared/utils/module'; -import { generateImportFnScriptCode } from '../codegen-importfn-script'; -import { generateModernIframeScriptCode } from '../codegen-modern-iframe-script'; -import { generateAddonSetupCode } from '../codegen-set-addon-channel'; -import { transformIframeHtml } from '../transform-iframe-html'; -import { bundlerOptionsKey, ensureRolldownOptions } from '../utils/vite-features'; +import { importMetaResolve } from '../../../../core/src/shared/utils/module.ts'; +import { generateImportFnScriptCode } from '../codegen-importfn-script.ts'; +import { generateModernIframeScriptCode } from '../codegen-modern-iframe-script.ts'; +import { generateAddonSetupCode } from '../codegen-set-addon-channel.ts'; +import { transformIframeHtml } from '../transform-iframe-html.ts'; +import { bundlerOptionsKey, ensureRolldownOptions } from '../utils/vite-features.ts'; import { SB_VIRTUAL_FILES, SB_VIRTUAL_FILE_IDS, getResolvedVirtualModuleId, -} from '../virtual-file-names'; +} from '../virtual-file-names.ts'; export function codeGeneratorPlugin(options: Options) { const iframePath = fileURLToPath(importMetaResolve('@storybook/builder-vite/input/iframe.html')); diff --git a/code/builders/builder-vite/src/plugins/index.ts b/code/builders/builder-vite/src/plugins/index.ts index 078886a86b4a..1815cfab952e 100644 --- a/code/builders/builder-vite/src/plugins/index.ts +++ b/code/builders/builder-vite/src/plugins/index.ts @@ -1,12 +1,12 @@ // Builder-internal plugins (used by vite-config.ts to assemble the builder's plugin stack) -export { storybookOptimizeDepsPlugin } from './storybook-optimize-deps-plugin'; -export { storybookEntryPlugin } from './storybook-entry-plugin'; -export { pluginWebpackStats } from './webpack-stats-plugin'; -export type { WebpackStatsPlugin } from './webpack-stats-plugin'; +export { storybookOptimizeDepsPlugin } from './storybook-optimize-deps-plugin.ts'; +export { storybookEntryPlugin } from './storybook-entry-plugin.ts'; +export { pluginWebpackStats } from './webpack-stats-plugin.ts'; +export type { WebpackStatsPlugin } from './webpack-stats-plugin.ts'; // Lower-level plugins re-exported for internal use and tests -export { injectExportOrderPlugin } from './inject-export-order-plugin'; -export { stripStoryHMRBoundary } from './strip-story-hmr-boundaries'; -export { codeGeneratorPlugin } from './code-generator-plugin'; -export { csfPlugin } from './csf-plugin'; -export { storybookExternalGlobalsPlugin } from './storybook-external-globals-plugin'; +export { injectExportOrderPlugin } from './inject-export-order-plugin.ts'; +export { stripStoryHMRBoundary } from './strip-story-hmr-boundaries.ts'; +export { codeGeneratorPlugin } from './code-generator-plugin.ts'; +export { csfPlugin } from './csf-plugin.ts'; +export { storybookExternalGlobalsPlugin } from './storybook-external-globals-plugin.ts'; diff --git a/code/builders/builder-vite/src/plugins/inject-export-order-plugin.ts b/code/builders/builder-vite/src/plugins/inject-export-order-plugin.ts index 91091a86811b..9cb2d658c85e 100644 --- a/code/builders/builder-vite/src/plugins/inject-export-order-plugin.ts +++ b/code/builders/builder-vite/src/plugins/inject-export-order-plugin.ts @@ -12,29 +12,32 @@ export async function injectExportOrderPlugin() { name: 'storybook:inject-export-order-plugin', // This should only run after the typescript has been transpiled enforce: 'post', - async transform(code: string, id: string) { - if (!filter(id)) { - return undefined; - } + transform: { + filter: { id: include }, + async handler(code: string, id: string) { + if (!filter(id)) { + return undefined; + } - // TODO: Maybe convert `injectExportOrderPlugin` to function that returns object, - // and run `await init;` once and then call `parse()` without `await`, - // instead of calling `await parse()` every time. - const [, exports] = await parse(code); + // TODO: Maybe convert `injectExportOrderPlugin` to function that returns object, + // and run `await init;` once and then call `parse()` without `await`, + // instead of calling `await parse()` every time. + const [, exports] = await parse(code); - const exportNames = exports.map((e) => code.substring(e.s, e.e)); + const exportNames = exports.map((e) => code.substring(e.s, e.e)); - if (exportNames.includes('__namedExportsOrder')) { - // user has defined named exports already - return undefined; - } - const s = new MagicString(code); - const orderedExports = exportNames.filter((e) => e !== 'default'); - s.append(`;export const __namedExportsOrder = ${JSON.stringify(orderedExports)};`); - return { - code: s.toString(), - map: s.generateMap({ hires: true, source: id }), - }; + if (exportNames.includes('__namedExportsOrder')) { + // user has defined named exports already + return undefined; + } + const s = new MagicString(code); + const orderedExports = exportNames.filter((e) => e !== 'default'); + s.append(`;export const __namedExportsOrder = ${JSON.stringify(orderedExports)};`); + return { + code: s.toString(), + map: s.generateMap({ hires: true, source: id }), + }; + }, }, } satisfies Plugin; } diff --git a/code/builders/builder-vite/src/plugins/storybook-entry-plugin.ts b/code/builders/builder-vite/src/plugins/storybook-entry-plugin.ts index f4c0ba3d38d3..437d3456f888 100644 --- a/code/builders/builder-vite/src/plugins/storybook-entry-plugin.ts +++ b/code/builders/builder-vite/src/plugins/storybook-entry-plugin.ts @@ -2,9 +2,9 @@ import type { Options } from 'storybook/internal/types'; import type { Plugin } from 'vite'; -import { codeGeneratorPlugin } from './code-generator-plugin'; -import { injectExportOrderPlugin } from './inject-export-order-plugin'; -import { stripStoryHMRBoundary } from './strip-story-hmr-boundaries'; +import { codeGeneratorPlugin } from './code-generator-plugin.ts'; +import { injectExportOrderPlugin } from './inject-export-order-plugin.ts'; +import { stripStoryHMRBoundary } from './strip-story-hmr-boundaries.ts'; /** * A composite Vite plugin that manages the generation and injection of virtual entry points for diff --git a/code/builders/builder-vite/src/plugins/storybook-external-globals-plugin.test.ts b/code/builders/builder-vite/src/plugins/storybook-external-globals-plugin.test.ts index 4b5c001f0e3d..ceaae2475df7 100644 --- a/code/builders/builder-vite/src/plugins/storybook-external-globals-plugin.test.ts +++ b/code/builders/builder-vite/src/plugins/storybook-external-globals-plugin.test.ts @@ -1,6 +1,6 @@ import { expect, it } from 'vitest'; -import { rewriteImport } from './storybook-external-globals-plugin'; +import { rewriteImport } from './storybook-external-globals-plugin.ts'; const packageName = '@storybook/package'; const globals = { [packageName]: '_STORYBOOK_PACKAGE_' }; diff --git a/code/builders/builder-vite/src/plugins/storybook-external-globals-plugin.ts b/code/builders/builder-vite/src/plugins/storybook-external-globals-plugin.ts index 54bb15a28c96..ccc15137aa13 100644 --- a/code/builders/builder-vite/src/plugins/storybook-external-globals-plugin.ts +++ b/code/builders/builder-vite/src/plugins/storybook-external-globals-plugin.ts @@ -55,6 +55,9 @@ export async function storybookExternalGlobalsPlugin(options: Options): Promise< await init; const { mergeAlias } = await import('vite'); + const globalsList = Object.keys(externals); + const globalsCodeFilter = new RegExp(globalsList.map(escapeKeys).join('|')); + return { name: 'storybook:external-globals-plugin', enforce: 'post', @@ -88,28 +91,29 @@ export async function storybookExternalGlobalsPlugin(options: Options): Promise< }; }, // Replace imports with variables destructured from global scope - async transform(code: string, id: string) { - const globalsList = Object.keys(externals); - - if (globalsList.every((glob) => !code.includes(glob))) { - return undefined; - } - - const [imports] = parse(code); - const src = new MagicString(code); - imports.forEach(({ n: path, ss: startPosition, se: endPosition }) => { - const packageName = path; - if (packageName && globalsList.includes(packageName)) { - const importStatement = src.slice(startPosition, endPosition); - const transformedImport = rewriteImport(importStatement, externals, packageName); - src.update(startPosition, endPosition, transformedImport); + transform: { + filter: { code: globalsCodeFilter }, + async handler(code: string, id: string) { + if (globalsList.every((glob) => !code.includes(glob))) { + return undefined; } - }); - return { - code: src.toString(), - map: null, - }; + const [imports] = parse(code); + const src = new MagicString(code); + imports.forEach(({ n: path, ss: startPosition, se: endPosition }) => { + const packageName = path; + if (packageName && globalsList.includes(packageName)) { + const importStatement = src.slice(startPosition, endPosition); + const transformedImport = rewriteImport(importStatement, externals, packageName); + src.update(startPosition, endPosition, transformedImport); + } + }); + + return { + code: src.toString(), + map: null, + }; + }, }, } satisfies Plugin; } 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 index b9f1062943ba..48ff1e107f8b 100644 --- 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 @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { escapeGlobPath, getMockRedirectIncludeEntries } from './storybook-optimize-deps-plugin'; +import { escapeGlobPath, getMockRedirectIncludeEntries } from './storybook-optimize-deps-plugin.ts'; describe('escapeGlobPath', () => { it('should not modify a plain path without special characters', () => { 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 cf3e2e8cc3f0..26517acb20e1 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 @@ -6,8 +6,8 @@ import type { Options, PreviewAnnotation, StoryIndex } from 'storybook/internal/ import { resolve } from 'pathe'; import { type Plugin } from 'vite'; -import { processPreviewAnnotation } from '../utils/process-preview-annotation'; -import { getUniqueImportPaths } from '../utils/unique-import-paths'; +import { processPreviewAnnotation } from '../utils/process-preview-annotation.ts'; +import { getUniqueImportPaths } from '../utils/unique-import-paths.ts'; /** * Escapes special glob characters in a file path so Vite's dep optimizer treats it as a literal diff --git a/code/builders/builder-vite/src/plugins/storybook-project-annotations-plugin.ts b/code/builders/builder-vite/src/plugins/storybook-project-annotations-plugin.ts index beb879493f64..31c58d2588b0 100644 --- a/code/builders/builder-vite/src/plugins/storybook-project-annotations-plugin.ts +++ b/code/builders/builder-vite/src/plugins/storybook-project-annotations-plugin.ts @@ -2,8 +2,8 @@ import type { Options } from 'storybook/internal/types'; import type { Plugin } from 'vite'; -import { generateProjectAnnotationsCode } from '../codegen-project-annotations'; -import { getResolvedVirtualModuleId } from '../virtual-file-names'; +import { generateProjectAnnotationsCode } from '../codegen-project-annotations.ts'; +import { getResolvedVirtualModuleId } from '../virtual-file-names.ts'; export const VIRTUAL_ID = 'virtual:/@storybook/builder-vite/project-annotations.js'; const RESOLVED_VIRTUAL_ID = getResolvedVirtualModuleId(VIRTUAL_ID); diff --git a/code/builders/builder-vite/src/plugins/storybook-runtime-plugin.ts b/code/builders/builder-vite/src/plugins/storybook-runtime-plugin.ts index a60d66eace7e..f810f539514f 100644 --- a/code/builders/builder-vite/src/plugins/storybook-runtime-plugin.ts +++ b/code/builders/builder-vite/src/plugins/storybook-runtime-plugin.ts @@ -3,7 +3,7 @@ import type { Options } from 'storybook/internal/types'; import type { Plugin } from 'vite'; -import { stringifyProcessEnvs } from '../envs'; +import { stringifyProcessEnvs } from '../envs.ts'; export interface StorybookRuntimePluginOptions { externals: Record; diff --git a/code/builders/builder-vite/src/plugins/strip-story-hmr-boundaries.ts b/code/builders/builder-vite/src/plugins/strip-story-hmr-boundaries.ts index baa433d1afed..50ab71411071 100644 --- a/code/builders/builder-vite/src/plugins/strip-story-hmr-boundaries.ts +++ b/code/builders/builder-vite/src/plugins/strip-story-hmr-boundaries.ts @@ -9,22 +9,26 @@ import type { Plugin } from 'vite'; export async function stripStoryHMRBoundary() { const { createFilter } = await import('vite'); - const filter = createFilter(/\.stories\.(tsx?|jsx?|svelte|vue)$/); + const include = /\.stories\.(tsx?|jsx?|svelte|vue)$/; + const filter = createFilter(include); return { name: 'storybook:strip-hmr-boundary-plugin', enforce: 'post', - async transform(src, id) { - if (!filter(id)) { - return undefined; - } + transform: { + filter: { id: include }, + async handler(src, id) { + if (!filter(id)) { + return undefined; + } - const s = new MagicString(src); - s.replace(/import\.meta\.hot\.accept\w*/, '(function hmrBoundaryNoop(){})'); + const s = new MagicString(src); + s.replace(/import\.meta\.hot\.accept\w*/, '(function hmrBoundaryNoop(){})'); - return { - code: s.toString(), - map: s.generateMap({ hires: true, source: id }), - }; + return { + code: s.toString(), + map: s.generateMap({ hires: true, source: id }), + }; + }, }, } satisfies Plugin; } diff --git a/code/builders/builder-vite/src/plugins/vite-inject-mocker/plugin.ts b/code/builders/builder-vite/src/plugins/vite-inject-mocker/plugin.ts index 79dfaa64410f..61ff78434487 100644 --- a/code/builders/builder-vite/src/plugins/vite-inject-mocker/plugin.ts +++ b/code/builders/builder-vite/src/plugins/vite-inject-mocker/plugin.ts @@ -41,11 +41,14 @@ export const viteInjectMockerRuntime = (options: { }); } }, - resolveId(source) { - if (source === ENTRY_PATH) { - return mockerRuntimePath; - } - return undefined; + resolveId: { + filter: { id: /\/vite-inject-mocker-entry\.js/ }, + handler(source) { + if (source === ENTRY_PATH) { + return mockerRuntimePath; + } + return undefined; + }, }, transformIndexHtml(html: string) { const headTag = html.match(/]*>/); diff --git a/code/builders/builder-vite/src/plugins/vite-mock/plugin.ts b/code/builders/builder-vite/src/plugins/vite-mock/plugin.ts index 5f60995c05b0..2bfd141e4709 100644 --- a/code/builders/builder-vite/src/plugins/vite-mock/plugin.ts +++ b/code/builders/builder-vite/src/plugins/vite-mock/plugin.ts @@ -14,7 +14,7 @@ import type { CoreConfig } from 'storybook/internal/types'; import { normalize } from 'pathe'; import type { Plugin, ResolvedConfig } from 'vite'; -import { type MockCall, getCleanId, invalidateAllRelatedModules } from './utils'; +import { type MockCall, getCleanId, invalidateAllRelatedModules } from './utils.ts'; export interface MockPluginOptions { /** The absolute path to the preview.tsx file where mocks are defined. */ @@ -162,16 +162,19 @@ export function viteMockPlugin(options: MockPluginOptions): Plugin[] { }, { name: 'storybook:mock-loader-preview', - transform(code, id) { - if (id === normalizedPreviewConfigPath) { - try { - return rewriteSbMockImportCalls(code); - } catch (e) { - logger.debug(`Could not transform sb.mock(import(...)) calls in ${id}: ${e}`); - return null; + transform: { + filter: { id: normalizedPreviewConfigPath }, + handler(code, id) { + if (id === normalizedPreviewConfigPath) { + try { + return rewriteSbMockImportCalls(code); + } catch (e) { + logger.debug(`Could not transform sb.mock(import(...)) calls in ${id}: ${e}`); + return null; + } } - } - return null; + return null; + }, }, }, ]; diff --git a/code/builders/builder-vite/src/plugins/webpack-stats-plugin.ts b/code/builders/builder-vite/src/plugins/webpack-stats-plugin.ts index 0f6c1c441be9..6c0c430feee7 100644 --- a/code/builders/builder-vite/src/plugins/webpack-stats-plugin.ts +++ b/code/builders/builder-vite/src/plugins/webpack-stats-plugin.ts @@ -11,7 +11,7 @@ import { SB_VIRTUAL_FILES, getOriginalVirtualModuleId, getResolvedVirtualModuleId, -} from '../virtual-file-names'; +} from '../virtual-file-names.ts'; /* * Reason, Module are copied from chromatic types diff --git a/code/builders/builder-vite/src/preset.ts b/code/builders/builder-vite/src/preset.ts index cb4a772559aa..caca1a993d99 100644 --- a/code/builders/builder-vite/src/preset.ts +++ b/code/builders/builder-vite/src/preset.ts @@ -3,12 +3,12 @@ import type { Options } from 'storybook/internal/types'; import type { PluginOption } from 'vite'; -import { storybookConfigPlugin } from './plugins/storybook-config-plugin'; -import { storybookOptimizeDepsPlugin } from './plugins/storybook-optimize-deps-plugin'; -import { storybookProjectAnnotationsPlugin } from './plugins/storybook-project-annotations-plugin'; -import { storybookSanitizeEnvs } from './plugins/storybook-runtime-plugin'; -import { viteInjectMockerRuntime } from './plugins/vite-inject-mocker/plugin'; -import { viteMockPlugin } from './plugins/vite-mock/plugin'; +import { storybookConfigPlugin } from './plugins/storybook-config-plugin.ts'; +import { storybookOptimizeDepsPlugin } from './plugins/storybook-optimize-deps-plugin.ts'; +import { storybookProjectAnnotationsPlugin } from './plugins/storybook-project-annotations-plugin.ts'; +import { storybookSanitizeEnvs } from './plugins/storybook-runtime-plugin.ts'; +import { viteInjectMockerRuntime } from './plugins/vite-inject-mocker/plugin.ts'; +import { viteMockPlugin } from './plugins/vite-mock/plugin.ts'; export const optimizeViteDeps: string[] = ['storybook/internal/preview/runtime']; diff --git a/code/builders/builder-vite/src/transform-iframe-html.ts b/code/builders/builder-vite/src/transform-iframe-html.ts index 2e7a6750dca1..697d69da10b8 100644 --- a/code/builders/builder-vite/src/transform-iframe-html.ts +++ b/code/builders/builder-vite/src/transform-iframe-html.ts @@ -1,7 +1,7 @@ import { normalizeStories } from 'storybook/internal/common'; import type { DocsOptions, Options, TagsOptions } from 'storybook/internal/types'; -import { SB_VIRTUAL_FILES } from './virtual-file-names'; +import { SB_VIRTUAL_FILES } from './virtual-file-names.ts'; export type PreviewHtml = string | undefined; diff --git a/code/builders/builder-vite/src/utils/build-module-graph.test.ts b/code/builders/builder-vite/src/utils/build-module-graph.test.ts new file mode 100644 index 000000000000..9545b7b434e8 --- /dev/null +++ b/code/builders/builder-vite/src/utils/build-module-graph.test.ts @@ -0,0 +1,153 @@ +import { describe, expect, it, vi } from 'vitest'; + +import type { ModuleNode as StorybookModuleNode } from 'storybook/internal/types'; +import type { ViteDevServer } from 'vite'; + +import { buildModuleGraph } from './build-module-graph.ts'; + +vi.mock('./vite-server', () => ({ + createViteServer: vi.fn(), +})); + +type ViteModuleNodeLike = { + file: string | null; + type: StorybookModuleNode['type']; + importers: Set; + importedModules: Set; +}; + +function createViteModuleNode( + file: string | null, + type: StorybookModuleNode['type'] = 'js' +): ViteModuleNodeLike { + return { + file, + type, + importers: new Set(), + importedModules: new Set(), + }; +} + +function createFileToModulesMap(...entries: Array<[string, Set]>) { + return new Map(entries) as ViteDevServer['moduleGraph']['fileToModulesMap']; +} + +function getFirstNode( + file: string, + moduleGraph: ReturnType +): StorybookModuleNode { + const moduleNode = moduleGraph.get(file)?.values().next().value; + if (!moduleNode) { + throw new Error(`Expected module node for ${file}`); + } + return moduleNode; +} + +describe('buildModuleGraph', () => { + it('converts vite module nodes into the shared module graph shape', () => { + const entry = createViteModuleNode('/src/entry.ts'); + const component = createViteModuleNode('/src/component.ts'); + const styles = createViteModuleNode('/src/component.css', 'css'); + + entry.importedModules.add(component); + component.importers.add(entry); + component.importedModules.add(styles); + styles.importers.add(component); + + const moduleGraph = buildModuleGraph( + createFileToModulesMap( + ['/src/entry.ts', new Set([entry])], + ['/src/component.ts', new Set([component])], + ['/src/component.css', new Set([styles])] + ) + ); + + const entryNode = getFirstNode('/src/entry.ts', moduleGraph); + const componentNode = getFirstNode('/src/component.ts', moduleGraph); + const styleNode = getFirstNode('/src/component.css', moduleGraph); + + expect(entryNode.file).toBe('/src/entry.ts'); + expect(componentNode.type).toBe('js'); + expect(styleNode.type).toBe('css'); + + expect(entryNode.importedModules).toEqual(new Set([componentNode])); + expect(componentNode.importers).toEqual(new Set([entryNode])); + expect(componentNode.importedModules).toEqual(new Set([styleNode])); + expect(styleNode.importers).toEqual(new Set([componentNode])); + }); + + it('reuses the same converted node identity across relationships', () => { + const shared = createViteModuleNode('/src/shared.ts'); + const importerA = createViteModuleNode('/src/a.ts'); + const importerB = createViteModuleNode('/src/b.ts'); + + importerA.importedModules.add(shared); + importerB.importedModules.add(shared); + shared.importers.add(importerA); + shared.importers.add(importerB); + + const moduleGraph = buildModuleGraph( + createFileToModulesMap( + ['/src/shared.ts', new Set([shared])], + ['/src/a.ts', new Set([importerA])], + ['/src/b.ts', new Set([importerB])] + ) + ); + + const sharedNode = getFirstNode('/src/shared.ts', moduleGraph); + const importerANode = getFirstNode('/src/a.ts', moduleGraph); + const importerBNode = getFirstNode('/src/b.ts', moduleGraph); + + expect(importerANode.importedModules.has(sharedNode)).toBe(true); + expect(importerBNode.importedModules.has(sharedNode)).toBe(true); + expect(sharedNode.importers).toEqual(new Set([importerANode, importerBNode])); + }); + + it('skips related vite module nodes without a file', () => { + const entry = createViteModuleNode('/src/entry.ts'); + const virtualModule = createViteModuleNode(null); + + entry.importedModules.add(virtualModule); + virtualModule.importers.add(entry); + + const moduleGraph = buildModuleGraph( + createFileToModulesMap(['/src/entry.ts', new Set([entry])]) + ); + const entryNode = getFirstNode('/src/entry.ts', moduleGraph); + + expect(moduleGraph.size).toBe(1); + expect(entryNode.importedModules.size).toBe(0); + }); + + it('preserves edges for related vite module nodes discovered before their file path is known', () => { + const entry = createViteModuleNode('/src/entry.ts'); + const component = createViteModuleNode(null); + + entry.importedModules.add(component); + component.importers.add(entry); + + const moduleGraph = buildModuleGraph( + createFileToModulesMap( + ['/src/entry.ts', new Set([entry])], + ['/src/component.ts', new Set([component])] + ) + ); + + const entryNode = getFirstNode('/src/entry.ts', moduleGraph); + const componentNode = getFirstNode('/src/component.ts', moduleGraph); + + expect(entryNode.importedModules).toEqual(new Set([componentNode])); + expect(componentNode.importers).toEqual(new Set([entryNode])); + }); + + it('keeps multiple module identities for the same file', () => { + const clientModule = createViteModuleNode('/src/shared.ts'); + const ssrModule = createViteModuleNode('/src/shared.ts'); + + const moduleGraph = buildModuleGraph( + createFileToModulesMap(['/src/shared.ts', new Set([clientModule, ssrModule])]) + ); + + expect(moduleGraph.get('/src/shared.ts')?.size).toBe(2); + }); +}); diff --git a/code/builders/builder-vite/src/utils/build-module-graph.ts b/code/builders/builder-vite/src/utils/build-module-graph.ts new file mode 100644 index 000000000000..4d14235bd88e --- /dev/null +++ b/code/builders/builder-vite/src/utils/build-module-graph.ts @@ -0,0 +1,82 @@ +import type { ModuleGraph, ModuleNode } from 'storybook/internal/types'; + +import type { ViteDevServer, ModuleNode as ViteModuleNode } from 'vite'; + +export function buildModuleGraph( + fileToModulesMap: ViteDevServer['moduleGraph']['fileToModulesMap'] +): ModuleGraph { + const moduleGraph: ModuleGraph = new Map(); + const moduleNodeMap = new WeakMap(); + const getModuleFileFromMap = (viteModuleNode: { + file: string | null; + type: ViteModuleNode['type']; + importers: Set; + importedModules: Set; + }): string | undefined => { + for (const [filePath, viteModuleSet] of fileToModulesMap.entries()) { + if (viteModuleSet.has(viteModuleNode as ViteModuleNode)) { + return filePath; + } + } + }; + + const getOrCreateModuleNode = ( + viteModuleNode: { + file: string | null; + type: ViteModuleNode['type']; + importers: Set; + importedModules: Set; + }, + fallbackFile?: string + ): ModuleNode | undefined => { + const file = viteModuleNode.file ?? fallbackFile; + if (!file) { + return undefined; + } + + const existingNode = moduleNodeMap.get(viteModuleNode); + if (existingNode) { + return existingNode; + } + + const moduleNode: ModuleNode = { + file, + type: viteModuleNode.type, + importers: new Set(), + importedModules: new Set(), + }; + moduleNodeMap.set(viteModuleNode, moduleNode); + + const moduleSet = moduleGraph.get(file) ?? new Set(); + moduleSet.add(moduleNode); + moduleGraph.set(file, moduleSet); + + return moduleNode; + }; + + fileToModulesMap.forEach((viteModuleSet, filePath) => { + viteModuleSet.forEach((viteModuleNode) => { + const moduleNode = getOrCreateModuleNode(viteModuleNode, filePath); + if (moduleNode) { + viteModuleNode.importers.forEach((importer) => { + const importerNode = + getOrCreateModuleNode(importer) ?? + getOrCreateModuleNode(importer, getModuleFileFromMap(importer)); + if (importerNode) { + moduleNode.importers.add(importerNode); + } + }); + viteModuleNode.importedModules.forEach((importedModule) => { + const importedModuleNode = + getOrCreateModuleNode(importedModule) ?? + getOrCreateModuleNode(importedModule, getModuleFileFromMap(importedModule)); + if (importedModuleNode) { + moduleNode.importedModules.add(importedModuleNode); + } + }); + } + }); + }); + + return moduleGraph; +} diff --git a/code/builders/builder-vite/src/utils/has-vite-plugins.test.ts b/code/builders/builder-vite/src/utils/has-vite-plugins.test.ts index b8d5a3e44617..fb581d9778be 100644 --- a/code/builders/builder-vite/src/utils/has-vite-plugins.test.ts +++ b/code/builders/builder-vite/src/utils/has-vite-plugins.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { hasVitePlugins } from './has-vite-plugins'; +import { hasVitePlugins } from './has-vite-plugins.ts'; describe('hasVitePlugins', () => { describe('should return true for', () => { diff --git a/code/builders/builder-vite/src/utils/process-preview-annotation.test.ts b/code/builders/builder-vite/src/utils/process-preview-annotation.test.ts index f4f243ebc439..e4946644152e 100644 --- a/code/builders/builder-vite/src/utils/process-preview-annotation.test.ts +++ b/code/builders/builder-vite/src/utils/process-preview-annotation.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; -import { onlyWindows, skipWindows } from '../../../../vitest.helpers'; -import { processPreviewAnnotation } from './process-preview-annotation'; +import { onlyWindows, skipWindows } from '../../../../vitest.helpers.ts'; +import { processPreviewAnnotation } from './process-preview-annotation.ts'; describe('processPreviewAnnotation()', () => { it('should pull the `absolute` value from an object', () => { diff --git a/code/builders/builder-vite/src/utils/without-vite-plugins.test.ts b/code/builders/builder-vite/src/utils/without-vite-plugins.test.ts index e0efee13036b..a15d45de0040 100644 --- a/code/builders/builder-vite/src/utils/without-vite-plugins.test.ts +++ b/code/builders/builder-vite/src/utils/without-vite-plugins.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { withoutVitePlugins } from './without-vite-plugins'; +import { withoutVitePlugins } from './without-vite-plugins.ts'; describe('withoutVitePlugins', () => { describe('should remove', () => { diff --git a/code/builders/builder-vite/src/vite-config.test.ts b/code/builders/builder-vite/src/vite-config.test.ts index 9bb9a0b6913d..1fc7cfdd4e8d 100644 --- a/code/builders/builder-vite/src/vite-config.test.ts +++ b/code/builders/builder-vite/src/vite-config.test.ts @@ -5,8 +5,8 @@ import type { Options, Presets } from 'storybook/internal/types'; import { loadConfigFromFile } from 'vite'; -import { storybookConfigPlugin } from './plugins/storybook-config-plugin'; -import { commonConfig } from './vite-config'; +import { storybookConfigPlugin } from './plugins/storybook-config-plugin.ts'; +import { commonConfig } from './vite-config.ts'; vi.mock('vite', async (importOriginal) => ({ ...(await importOriginal()), diff --git a/code/builders/builder-vite/src/vite-config.ts b/code/builders/builder-vite/src/vite-config.ts index 807f50152219..27347853e650 100644 --- a/code/builders/builder-vite/src/vite-config.ts +++ b/code/builders/builder-vite/src/vite-config.ts @@ -16,9 +16,9 @@ import { pluginWebpackStats, storybookEntryPlugin, storybookExternalGlobalsPlugin, -} from './plugins'; -import { viteCorePlugins as corePlugins } from './preset'; -import type { BuilderOptions } from './types'; +} from './plugins/index.ts'; +import { viteCorePlugins as corePlugins } from './preset.ts'; +import type { BuilderOptions } from './types.ts'; export type PluginConfigType = 'build' | 'development'; diff --git a/code/builders/builder-vite/src/vite-server.ts b/code/builders/builder-vite/src/vite-server.ts index e8022b796202..b770666dec8c 100644 --- a/code/builders/builder-vite/src/vite-server.ts +++ b/code/builders/builder-vite/src/vite-server.ts @@ -3,8 +3,8 @@ import type { Options } from 'storybook/internal/types'; import type { Server } from 'http'; import type { InlineConfig, ServerOptions } from 'vite'; -import { createViteLogger } from './logger'; -import { commonConfig } from './vite-config'; +import { createViteLogger } from './logger.ts'; +import { commonConfig } from './vite-config.ts'; export async function createViteServer(options: Options, devServer: Server) { const { presets } = options; diff --git a/code/builders/builder-vite/vitest.config.ts b/code/builders/builder-vite/vitest.config.ts index 16d2fd08535c..25fae90d1f77 100644 --- a/code/builders/builder-vite/vitest.config.ts +++ b/code/builders/builder-vite/vitest.config.ts @@ -1,6 +1,6 @@ import { defineConfig, mergeConfig } from 'vitest/config'; -import { vitestCommonConfig } from '../../vitest.shared'; +import { vitestCommonConfig } from '../../vitest.shared.ts'; export default mergeConfig( vitestCommonConfig, diff --git a/code/builders/builder-webpack5/build-config.ts b/code/builders/builder-webpack5/build-config.ts index 64d45a5ebec4..67b848b3b6fc 100644 --- a/code/builders/builder-webpack5/build-config.ts +++ b/code/builders/builder-webpack5/build-config.ts @@ -1,4 +1,4 @@ -import type { BuildEntries } from '../../../scripts/build/utils/entry-utils'; +import type { BuildEntries } from '../../../scripts/build/utils/entry-utils.ts'; const config: BuildEntries = { entries: { diff --git a/code/builders/builder-webpack5/package.json b/code/builders/builder-webpack5/package.json index 7538f88dcd67..125e13162e1c 100644 --- a/code/builders/builder-webpack5/package.json +++ b/code/builders/builder-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/builder-webpack5", - "version": "10.4.0-alpha.3", + "version": "10.4.0-alpha.7", "description": "A Storybook builder to dev and build with Webpack", "keywords": [ "storybook", diff --git a/code/builders/builder-webpack5/src/index.ts b/code/builders/builder-webpack5/src/index.ts index 37b9ef963038..f4312b4c0939 100644 --- a/code/builders/builder-webpack5/src/index.ts +++ b/code/builders/builder-webpack5/src/index.ts @@ -20,8 +20,8 @@ import webpackModule from 'webpack'; import webpackDevMiddleware from 'webpack-dev-middleware'; import webpackHotMiddleware from 'webpack-hot-middleware'; -export * from './types'; -export * from './preview/virtual-module-mapping'; +export * from './types.ts'; +export * from './preview/virtual-module-mapping.ts'; const { DefinePlugin, IgnorePlugin, ProgressPlugin } = webpackModule; diff --git a/code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts b/code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts index 20947f59e5a9..95143303dc66 100644 --- a/code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts +++ b/code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts @@ -9,9 +9,9 @@ import { loadCustomWebpackConfig } from '@storybook/core-webpack'; import webpackModule from 'webpack'; import type { Configuration } from 'webpack'; -import { WebpackInjectMockerRuntimePlugin } from '../plugins/webpack-inject-mocker-runtime-plugin'; -import { WebpackMockPlugin } from '../plugins/webpack-mock-plugin'; -import { createDefaultWebpackConfig } from '../preview/base-webpack.config'; +import { WebpackInjectMockerRuntimePlugin } from '../plugins/webpack-inject-mocker-runtime-plugin.ts'; +import { WebpackMockPlugin } from '../plugins/webpack-mock-plugin.ts'; +import { createDefaultWebpackConfig } from '../preview/base-webpack.config.ts'; export const swc: PresetProperty<'swc'> = (config: Record): Record => { return { diff --git a/code/builders/builder-webpack5/src/presets/preview-preset.ts b/code/builders/builder-webpack5/src/presets/preview-preset.ts index fe570d5d2034..30385be38ef5 100644 --- a/code/builders/builder-webpack5/src/presets/preview-preset.ts +++ b/code/builders/builder-webpack5/src/presets/preview-preset.ts @@ -1,6 +1,6 @@ import { fileURLToPath } from 'node:url'; -import webpackConfig from '../preview/iframe-webpack.config'; +import webpackConfig from '../preview/iframe-webpack.config.ts'; export const webpack = async (_: unknown, options: any) => webpackConfig(options); diff --git a/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts b/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts index 7b5644297a8f..4bb377386c53 100644 --- a/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts +++ b/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts @@ -23,8 +23,8 @@ import webpackModule from 'webpack'; import type { Configuration } from 'webpack'; import VirtualModulePlugin from 'webpack-virtual-modules'; -import type { TypescriptOptions } from '../types'; -import { getVirtualModules } from './virtual-module-mapping'; +import type { TypescriptOptions } from '../types.ts'; +import { getVirtualModules } from './virtual-module-mapping.ts'; const { DefinePlugin, HotModuleReplacementPlugin, ProgressPlugin } = webpackModule; diff --git a/code/builders/builder-webpack5/src/preview/virtual-module-mapping.ts b/code/builders/builder-webpack5/src/preview/virtual-module-mapping.ts index 8ae8e872006a..cedb275a5914 100644 --- a/code/builders/builder-webpack5/src/preview/virtual-module-mapping.ts +++ b/code/builders/builder-webpack5/src/preview/virtual-module-mapping.ts @@ -14,7 +14,7 @@ import { toImportFn } from '@storybook/core-webpack'; // eslint-disable-next-line depend/ban-dependencies import slash from 'slash'; -import type { BuilderOptions } from '../types'; +import type { BuilderOptions } from '../types.ts'; export const getVirtualModules = async (options: Options) => { const virtualModules: Record = {}; diff --git a/code/builders/builder-webpack5/vitest.config.ts b/code/builders/builder-webpack5/vitest.config.ts index 16d2fd08535c..25fae90d1f77 100644 --- a/code/builders/builder-webpack5/vitest.config.ts +++ b/code/builders/builder-webpack5/vitest.config.ts @@ -1,6 +1,6 @@ import { defineConfig, mergeConfig } from 'vitest/config'; -import { vitestCommonConfig } from '../../vitest.shared'; +import { vitestCommonConfig } from '../../vitest.shared.ts'; export default mergeConfig( vitestCommonConfig, diff --git a/code/core/build-config.ts b/code/core/build-config.ts index 3c72209d7694..e12d7c7ceba0 100644 --- a/code/core/build-config.ts +++ b/code/core/build-config.ts @@ -2,7 +2,7 @@ import path from 'node:path'; import { x as exec } from 'tinyexec'; -import type { BuildEntries } from '../../scripts/build/utils/entry-utils'; +import type { BuildEntries } from '../../scripts/build/utils/entry-utils.ts'; const config: BuildEntries = { prebuild: async (cwd) => { diff --git a/code/core/package.json b/code/core/package.json index b2bc8dc4df60..e7b6b07b1750 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -1,6 +1,6 @@ { "name": "storybook", - "version": "10.4.0-alpha.3", + "version": "10.4.0-alpha.7", "description": "Storybook: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/core/scripts/generate-source-files.ts b/code/core/scripts/generate-source-files.ts index 89fe327ea060..b920c2509f3c 100644 --- a/code/core/scripts/generate-source-files.ts +++ b/code/core/scripts/generate-source-files.ts @@ -11,8 +11,11 @@ import * as esbuild from 'esbuild'; import { format } from 'oxfmt'; import { dedent } from 'ts-dedent'; -import { getWorkspace } from '../../../scripts/utils/tools'; -import { BROWSER_TARGETS, SUPPORTED_FEATURES } from '../src/shared/constants/environments-support'; +import { getWorkspace } from '../../../scripts/utils/tools.ts'; +import { + BROWSER_TARGETS, + SUPPORTED_FEATURES, +} from '../src/shared/constants/environments-support.ts'; GlobalRegistrator.register({ url: 'http://localhost:3000', width: 1920, height: 1080 }); diff --git a/code/core/src/__tests/preview-errors.test.ts b/code/core/src/__tests/preview-errors.test.ts index 6c780167947f..3e4f14ab3fd5 100644 --- a/code/core/src/__tests/preview-errors.test.ts +++ b/code/core/src/__tests/preview-errors.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { UnknownArgTypesError } from '../preview-errors'; +import { UnknownArgTypesError } from '../preview-errors.ts'; describe('UnknownFlowArgTypesError', () => { it('should correctly handle error with convertSig', () => { diff --git a/code/core/src/__tests/server-errors.test.ts b/code/core/src/__tests/server-errors.test.ts index e268a8b43e55..a347afd1250e 100644 --- a/code/core/src/__tests/server-errors.test.ts +++ b/code/core/src/__tests/server-errors.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { WebpackCompilationError } from '../server-errors'; +import { WebpackCompilationError } from '../server-errors.ts'; describe('WebpackCompilationError', () => { it('should correctly handle error with stats.compilation.errors', () => { diff --git a/code/core/src/__tests/storybook-error.test.ts b/code/core/src/__tests/storybook-error.test.ts index 62399ffd81f7..982464a7136a 100644 --- a/code/core/src/__tests/storybook-error.test.ts +++ b/code/core/src/__tests/storybook-error.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { StorybookError, appendErrorRef } from '../storybook-error'; +import { StorybookError, appendErrorRef } from '../storybook-error.ts'; describe('StorybookError', () => { class TestError extends StorybookError { diff --git a/code/core/src/actions/addArgs.ts b/code/core/src/actions/addArgs.ts index f0ee718ed28a..444393333fb3 100644 --- a/code/core/src/actions/addArgs.ts +++ b/code/core/src/actions/addArgs.ts @@ -1,6 +1,6 @@ import type { ArgsEnhancer } from 'storybook/internal/types'; -import { addActionsFromArgTypes, inferActionsFromArgTypesRegex } from './addArgsHelpers'; +import { addActionsFromArgTypes, inferActionsFromArgTypesRegex } from './addArgsHelpers.ts'; export const argsEnhancers: ArgsEnhancer[] = [ addActionsFromArgTypes, diff --git a/code/core/src/actions/addArgsHelpers.test.ts b/code/core/src/actions/addArgsHelpers.test.ts index 99b8da579089..6d875f5af14c 100644 --- a/code/core/src/actions/addArgsHelpers.test.ts +++ b/code/core/src/actions/addArgsHelpers.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'; import type { StoryContext } from 'storybook/internal/types'; -import { addActionsFromArgTypes, inferActionsFromArgTypesRegex } from './addArgsHelpers'; +import { addActionsFromArgTypes, inferActionsFromArgTypesRegex } from './addArgsHelpers.ts'; describe('actions parameter enhancers', () => { describe('actions.argTypesRegex parameter', () => { diff --git a/code/core/src/actions/addArgsHelpers.ts b/code/core/src/actions/addArgsHelpers.ts index 8e81aebb934e..88a46f0360e3 100644 --- a/code/core/src/actions/addArgsHelpers.ts +++ b/code/core/src/actions/addArgsHelpers.ts @@ -1,6 +1,6 @@ import type { Args, ArgsEnhancer, Renderer } from 'storybook/internal/types'; -import { action } from './runtime/action'; +import { action } from './runtime/action.ts'; // interface ActionsParameter { // disable?: boolean; diff --git a/code/core/src/actions/components/ActionLogger/index.tsx b/code/core/src/actions/components/ActionLogger/index.tsx index 4ab90a5b9130..14b935742b8d 100644 --- a/code/core/src/actions/components/ActionLogger/index.tsx +++ b/code/core/src/actions/components/ActionLogger/index.tsx @@ -7,8 +7,8 @@ import { Inspector } from 'react-inspector'; import type { Theme } from 'storybook/theming'; import { styled, withTheme } from 'storybook/theming'; -import type { ActionDisplay } from '../../models'; -import { Action, Counter, InspectorContainer } from './style'; +import type { ActionDisplay } from '../../models/index.ts'; +import { Action, Counter, InspectorContainer } from './style.tsx'; const UnstyledWrapped = forwardRef( ({ children, className }, ref) => ( diff --git a/code/core/src/actions/components/Title.tsx b/code/core/src/actions/components/Title.tsx index dee4b5c7be0a..4dade331bf7e 100644 --- a/code/core/src/actions/components/Title.tsx +++ b/code/core/src/actions/components/Title.tsx @@ -5,7 +5,7 @@ import { STORY_CHANGED } from 'storybook/internal/core-events'; import { useAddonState, useChannel, useStorybookApi } from 'storybook/manager-api'; -import { ADDON_ID, CLEAR_ID, EVENT_ID, PANEL_ID } from '../constants'; +import { ADDON_ID, CLEAR_ID, EVENT_ID, PANEL_ID } from '../constants.ts'; export function Title() { const api = useStorybookApi(); diff --git a/code/core/src/actions/containers/ActionLogger/index.tsx b/code/core/src/actions/containers/ActionLogger/index.tsx index b56398b11b24..af1d05b083b2 100644 --- a/code/core/src/actions/containers/ActionLogger/index.tsx +++ b/code/core/src/actions/containers/ActionLogger/index.tsx @@ -6,10 +6,10 @@ import { dequal as deepEqual } from 'dequal'; import type { API } from 'storybook/manager-api'; import { useParameter } from 'storybook/manager-api'; -import { ActionLogger as ActionLoggerComponent } from '../../components/ActionLogger'; -import { CLEAR_ID, EVENT_ID, PARAM_KEY } from '../../constants'; -import type { ActionDisplay } from '../../models'; -import type { ActionsParameters } from '../../types'; +import { ActionLogger as ActionLoggerComponent } from '../../components/ActionLogger/index.tsx'; +import { CLEAR_ID, EVENT_ID, PARAM_KEY } from '../../constants.ts'; +import type { ActionDisplay } from '../../models/index.ts'; +import type { ActionsParameters } from '../../types.ts'; interface ActionLoggerProps { active: boolean; diff --git a/code/core/src/actions/decorator.ts b/code/core/src/actions/decorator.ts index 5ca7e98ffe34..da83ae4cad41 100644 --- a/code/core/src/actions/decorator.ts +++ b/code/core/src/actions/decorator.ts @@ -2,8 +2,8 @@ import type { PartialStoryFn, Renderer } from 'storybook/internal/types'; import { makeDecorator, useEffect } from 'storybook/preview-api'; -import { PARAM_KEY } from './constants'; -import { actions } from './runtime/actions'; +import { PARAM_KEY } from './constants.ts'; +import { actions } from './runtime/actions.ts'; const delegateEventSplitter = /^(\S+)\s*(.*)$/; diff --git a/code/core/src/actions/index.ts b/code/core/src/actions/index.ts index d2d3261dc960..56dab5244351 100644 --- a/code/core/src/actions/index.ts +++ b/code/core/src/actions/index.ts @@ -1,3 +1,3 @@ -export * from './constants'; -export * from './models'; -export * from './runtime'; +export * from './constants.ts'; +export * from './models/index.ts'; +export * from './runtime/index.ts'; diff --git a/code/core/src/actions/loaders.ts b/code/core/src/actions/loaders.ts index 01512af34094..4b179ba891f6 100644 --- a/code/core/src/actions/loaders.ts +++ b/code/core/src/actions/loaders.ts @@ -2,7 +2,7 @@ import type { LoaderFunction } from 'storybook/internal/types'; import { onMockCall } from 'storybook/test'; -import { action } from './runtime'; +import { action } from './runtime/index.ts'; let subscribed = false; diff --git a/code/core/src/actions/manager.tsx b/code/core/src/actions/manager.tsx index a8c1607c6e07..2d86f597d69c 100644 --- a/code/core/src/actions/manager.tsx +++ b/code/core/src/actions/manager.tsx @@ -2,9 +2,9 @@ import React from 'react'; import { addons, types } from 'storybook/manager-api'; -import { Title } from './components/Title'; -import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants'; -import ActionLogger from './containers/ActionLogger'; +import { Title } from './components/Title.tsx'; +import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants.ts'; +import ActionLogger from './containers/ActionLogger/index.tsx'; export default addons.register(ADDON_ID, (api) => { if (globalThis?.FEATURES?.actions) { diff --git a/code/core/src/actions/models/ActionDisplay.ts b/code/core/src/actions/models/ActionDisplay.ts index 356b2127c3b6..f6512010ff88 100644 --- a/code/core/src/actions/models/ActionDisplay.ts +++ b/code/core/src/actions/models/ActionDisplay.ts @@ -1,4 +1,4 @@ -import type { ActionOptions } from './ActionOptions'; +import type { ActionOptions } from './ActionOptions.ts'; export interface ActionDisplay { id: string; diff --git a/code/core/src/actions/models/ActionsFunction.ts b/code/core/src/actions/models/ActionsFunction.ts index 476c8d91c3fa..4e49857f13e4 100644 --- a/code/core/src/actions/models/ActionsFunction.ts +++ b/code/core/src/actions/models/ActionsFunction.ts @@ -1,5 +1,5 @@ -import type { ActionOptions } from './ActionOptions'; -import type { ActionsMap } from './ActionsMap'; +import type { ActionOptions } from './ActionOptions.ts'; +import type { ActionsMap } from './ActionsMap.ts'; export interface ActionsFunction { (handlerMap: Record, options?: ActionOptions): ActionsMap; diff --git a/code/core/src/actions/models/ActionsMap.ts b/code/core/src/actions/models/ActionsMap.ts index 962f3bb01104..af8c42afc3a1 100644 --- a/code/core/src/actions/models/ActionsMap.ts +++ b/code/core/src/actions/models/ActionsMap.ts @@ -1,3 +1,3 @@ -import type { HandlerFunction } from './HandlerFunction'; +import type { HandlerFunction } from './HandlerFunction.ts'; export type ActionsMap = Record; diff --git a/code/core/src/actions/models/HandlerFunction.test-d.ts b/code/core/src/actions/models/HandlerFunction.test-d.ts index 3527de749450..455eb8a43e3c 100644 --- a/code/core/src/actions/models/HandlerFunction.test-d.ts +++ b/code/core/src/actions/models/HandlerFunction.test-d.ts @@ -1,6 +1,6 @@ import { expectTypeOf } from 'vitest'; -import type { HandlerFunction } from './HandlerFunction'; +import type { HandlerFunction } from './HandlerFunction.ts'; // Should be assignable to async callback props (the fixed case) expectTypeOf().toExtend<() => Promise>(); diff --git a/code/core/src/actions/models/index.ts b/code/core/src/actions/models/index.ts index d8c7de6271d0..997a75d4f896 100644 --- a/code/core/src/actions/models/index.ts +++ b/code/core/src/actions/models/index.ts @@ -1,6 +1,6 @@ -export * from './ActionDisplay'; -export * from './ActionsFunction'; -export * from './ActionOptions'; -export * from './ActionsMap'; -export * from './DecoratorFunction'; -export * from './HandlerFunction'; +export * from './ActionDisplay.ts'; +export * from './ActionsFunction.ts'; +export * from './ActionOptions.ts'; +export * from './ActionsMap.ts'; +export * from './DecoratorFunction.ts'; +export * from './HandlerFunction.ts'; diff --git a/code/core/src/actions/preview.ts b/code/core/src/actions/preview.ts index e9966fc4c050..bec69c4554cc 100644 --- a/code/core/src/actions/preview.ts +++ b/code/core/src/actions/preview.ts @@ -1,8 +1,8 @@ import { definePreviewAddon } from 'storybook/internal/csf'; -import * as addArgs from './addArgs'; -import * as loaders from './loaders'; -import type { ActionsTypes } from './types'; +import * as addArgs from './addArgs.ts'; +import * as loaders from './loaders.ts'; +import type { ActionsTypes } from './types.ts'; export type { ActionsTypes }; diff --git a/code/core/src/actions/runtime/action.ts b/code/core/src/actions/runtime/action.ts index 6e9c6c65a519..8eb2fb303ab7 100644 --- a/code/core/src/actions/runtime/action.ts +++ b/code/core/src/actions/runtime/action.ts @@ -6,9 +6,9 @@ import { global } from '@storybook/global'; import type { PreviewWeb } from 'storybook/preview-api'; import { addons } from 'storybook/preview-api'; -import { EVENT_ID } from '../constants'; -import type { ActionDisplay, ActionOptions, HandlerFunction } from '../models'; -import { config } from './configureActions'; +import { EVENT_ID } from '../constants.ts'; +import type { ActionDisplay, ActionOptions, HandlerFunction } from '../models/index.ts'; +import { config } from './configureActions.ts'; type SyntheticEvent = any; // import('react').SyntheticEvent; const findProto = (obj: unknown, callback: (proto: any) => boolean): Function | null => { diff --git a/code/core/src/actions/runtime/actions.ts b/code/core/src/actions/runtime/actions.ts index 9cbb57c4198b..5285738c4288 100644 --- a/code/core/src/actions/runtime/actions.ts +++ b/code/core/src/actions/runtime/actions.ts @@ -1,6 +1,6 @@ -import type { ActionOptions, ActionsFunction, ActionsMap } from '../models'; -import { action } from './action'; -import { config } from './configureActions'; +import type { ActionOptions, ActionsFunction, ActionsMap } from '../models/index.ts'; +import { action } from './action.ts'; +import { config } from './configureActions.ts'; export const actions: ActionsFunction = (...args: any[]) => { let options: ActionOptions = config; diff --git a/code/core/src/actions/runtime/configureActions.ts b/code/core/src/actions/runtime/configureActions.ts index 60bcc01baef5..75588e21b686 100644 --- a/code/core/src/actions/runtime/configureActions.ts +++ b/code/core/src/actions/runtime/configureActions.ts @@ -1,4 +1,4 @@ -import type { ActionOptions } from '../models'; +import type { ActionOptions } from '../models/index.ts'; export const config: ActionOptions = { depth: 10, diff --git a/code/core/src/actions/runtime/index.ts b/code/core/src/actions/runtime/index.ts index 2edbd87475e9..9ea52094c0a1 100644 --- a/code/core/src/actions/runtime/index.ts +++ b/code/core/src/actions/runtime/index.ts @@ -1,3 +1,3 @@ -export * from './action'; -export * from './actions'; -export * from './configureActions'; +export * from './action.ts'; +export * from './actions.ts'; +export * from './configureActions.ts'; diff --git a/code/core/src/babel/expression-resolver.test.ts b/code/core/src/babel/expression-resolver.test.ts index e1d5c93b681f..ff870880b539 100644 --- a/code/core/src/babel/expression-resolver.test.ts +++ b/code/core/src/babel/expression-resolver.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'; import * as parser from '@babel/parser'; -import { resolveExpression, unwrapTSExpression } from './expression-resolver'; +import { resolveExpression, unwrapTSExpression } from './expression-resolver.ts'; const parse = (code: string) => parser.parse(code, { diff --git a/code/core/src/babel/index.ts b/code/core/src/babel/index.ts index 018113ec0c97..59650a0f070f 100644 --- a/code/core/src/babel/index.ts +++ b/code/core/src/babel/index.ts @@ -13,8 +13,8 @@ import bt from '@babel/traverse'; import * as types from '@babel/types'; import * as recast from 'recast'; -export * from './babelParse'; -export { unwrapTSExpression, resolveExpression } from './expression-resolver'; +export * from './babelParse.ts'; +export { unwrapTSExpression, resolveExpression } from './expression-resolver.ts'; export { isImportedDefineConfigLikeIdentifier, isDefineConfigLike, @@ -23,7 +23,7 @@ export { getTargetConfigObject, canUpdateVitestConfigFile, canUpdateVitestWorkspaceFile, -} from './vitest-config-helpers'; +} from './vitest-config-helpers.ts'; // @ts-expect-error (needed due to it's use of `exports.default`) const traverse = (bt.default || bt) as typeof bt; diff --git a/code/core/src/babel/vitest-config-helpers.test.ts b/code/core/src/babel/vitest-config-helpers.test.ts index 99c21cd10cef..8bbfdd30b9cc 100644 --- a/code/core/src/babel/vitest-config-helpers.test.ts +++ b/code/core/src/babel/vitest-config-helpers.test.ts @@ -1,6 +1,9 @@ import { describe, expect, it } from 'vitest'; -import { canUpdateVitestConfigFile, canUpdateVitestWorkspaceFile } from './vitest-config-helpers'; +import { + canUpdateVitestConfigFile, + canUpdateVitestWorkspaceFile, +} from './vitest-config-helpers.ts'; describe('canUpdateVitestConfigFile', () => { it('returns true for plain export default object literal', () => { diff --git a/code/core/src/babel/vitest-config-helpers.ts b/code/core/src/babel/vitest-config-helpers.ts index b1e106a34cb9..f8f04067dfb4 100644 --- a/code/core/src/babel/vitest-config-helpers.ts +++ b/code/core/src/babel/vitest-config-helpers.ts @@ -6,8 +6,8 @@ */ import type * as t from '@babel/types'; -import { babelParse } from './babelParse'; -import { resolveExpression } from './expression-resolver'; +import { babelParse } from './babelParse.ts'; +import { resolveExpression } from './expression-resolver.ts'; type AST = t.File; diff --git a/code/core/src/backgrounds/components/Tool.tsx b/code/core/src/backgrounds/components/Tool.tsx index 2b5716993582..0ffdea401dd3 100644 --- a/code/core/src/backgrounds/components/Tool.tsx +++ b/code/core/src/backgrounds/components/Tool.tsx @@ -6,9 +6,14 @@ import { CircleIcon, GridIcon, PhotoIcon } from '@storybook/icons'; import { useGlobals, useParameter } from 'storybook/manager-api'; -import { PARAM_KEY as KEY } from '../constants'; -import { DEFAULT_BACKGROUNDS } from '../defaults'; -import type { Background, BackgroundMap, BackgroundsParameters, GlobalStateUpdate } from '../types'; +import { PARAM_KEY as KEY } from '../constants.ts'; +import { DEFAULT_BACKGROUNDS } from '../defaults.ts'; +import type { + Background, + BackgroundMap, + BackgroundsParameters, + GlobalStateUpdate, +} from '../types.ts'; export const BackgroundTool = memo(function BackgroundSelector() { const config = useParameter(KEY); diff --git a/code/core/src/backgrounds/decorator.ts b/code/core/src/backgrounds/decorator.ts index a58e7e14cfb0..6342fca442b2 100644 --- a/code/core/src/backgrounds/decorator.ts +++ b/code/core/src/backgrounds/decorator.ts @@ -2,10 +2,10 @@ import type { DecoratorFunction } from 'storybook/internal/types'; import { useEffect } from 'storybook/preview-api'; -import { PARAM_KEY } from './constants'; -import { DEFAULT_BACKGROUNDS } from './defaults'; -import type { BackgroundsParameters, GridConfig } from './types'; -import { addBackgroundStyle, addGridStyle, clearStyles, isReduceMotionEnabled } from './utils'; +import { PARAM_KEY } from './constants.ts'; +import { DEFAULT_BACKGROUNDS } from './defaults.ts'; +import type { BackgroundsParameters, GridConfig } from './types.ts'; +import { addBackgroundStyle, addGridStyle, clearStyles, isReduceMotionEnabled } from './utils.ts'; const defaultGrid: GridConfig = { cellSize: 100, diff --git a/code/core/src/backgrounds/defaults.ts b/code/core/src/backgrounds/defaults.ts index 6b9329d15458..b18713b22829 100644 --- a/code/core/src/backgrounds/defaults.ts +++ b/code/core/src/backgrounds/defaults.ts @@ -1,4 +1,4 @@ -import type { BackgroundMap } from './types'; +import type { BackgroundMap } from './types.ts'; export const DEFAULT_BACKGROUNDS: BackgroundMap = { light: { name: 'light', value: '#F8F8F8' }, diff --git a/code/core/src/backgrounds/manager.tsx b/code/core/src/backgrounds/manager.tsx index cfd3078f667c..e11c02cbac59 100644 --- a/code/core/src/backgrounds/manager.tsx +++ b/code/core/src/backgrounds/manager.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { addons, types } from 'storybook/manager-api'; -import { BackgroundTool } from './components/Tool'; -import { ADDON_ID } from './constants'; +import { BackgroundTool } from './components/Tool.tsx'; +import { ADDON_ID } from './constants.ts'; export default addons.register(ADDON_ID, () => { if (globalThis?.FEATURES?.backgrounds) { diff --git a/code/core/src/backgrounds/preview.ts b/code/core/src/backgrounds/preview.ts index 448d73c2ec1d..23662615c692 100644 --- a/code/core/src/backgrounds/preview.ts +++ b/code/core/src/backgrounds/preview.ts @@ -1,13 +1,13 @@ import { definePreviewAddon } from 'storybook/internal/csf'; -import { PARAM_KEY } from './constants'; -import { withBackgroundAndGrid } from './decorator'; +import { PARAM_KEY } from './constants.ts'; +import { withBackgroundAndGrid } from './decorator.ts'; import type { BackgroundTypes, BackgroundsGlobals, BackgroundsParameters, GlobalState, -} from './types'; +} from './types.ts'; const decorators = globalThis.FEATURES?.backgrounds ? [withBackgroundAndGrid] : []; diff --git a/code/core/src/backgrounds/types.ts b/code/core/src/backgrounds/types.ts index 0abe07747304..ee88e549bd94 100644 --- a/code/core/src/backgrounds/types.ts +++ b/code/core/src/backgrounds/types.ts @@ -1,4 +1,4 @@ -import type { PARAM_KEY } from './constants'; +import type { PARAM_KEY } from './constants.ts'; export interface Background { name: string; diff --git a/code/core/src/bin/core.ts b/code/core/src/bin/core.ts index e673405eac44..9ce0413d9bcb 100644 --- a/code/core/src/bin/core.ts +++ b/code/core/src/bin/core.ts @@ -7,10 +7,10 @@ import leven from 'leven'; import picocolors from 'picocolors'; import { version } from '../../package.json'; -import { build } from '../cli/build'; -import { buildIndex as index } from '../cli/buildIndex'; -import { dev } from '../cli/dev'; -import { globalSettings } from '../cli/globalSettings'; +import { build } from '../cli/build.ts'; +import { buildIndex as index } from '../cli/buildIndex.ts'; +import { dev } from '../cli/dev.ts'; +import { globalSettings } from '../cli/globalSettings.ts'; addToGlobalContext('cliVersion', version); process.env.STORYBOOK = 'true'; diff --git a/code/core/src/bin/dispatcher.ts b/code/core/src/bin/dispatcher.ts index 9dd3787480df..ef544b871c75 100644 --- a/code/core/src/bin/dispatcher.ts +++ b/code/core/src/bin/dispatcher.ts @@ -7,8 +7,8 @@ import { logger } from 'storybook/internal/node-logger'; import { join } from 'pathe'; import { dedent } from 'ts-dedent'; -import versions from '../common/versions'; -import { resolvePackageDir } from '../shared/utils/module'; +import versions from '../common/versions.ts'; +import { resolvePackageDir } from '../shared/utils/module.ts'; /** * Dispatches Storybook CLI commands to the appropriate handler. diff --git a/code/core/src/bin/loader.test.ts b/code/core/src/bin/loader.test.ts index 140dec5807f2..b0738b3e9ae2 100644 --- a/code/core/src/bin/loader.test.ts +++ b/code/core/src/bin/loader.test.ts @@ -8,7 +8,7 @@ import { addExtensionsToRelativeImports, clearDirectoryCache, resolveWithExtension, -} from './loader'; +} from './loader.ts'; // Mock dependencies vi.mock('node:fs'); diff --git a/code/core/src/bin/loader.ts b/code/core/src/bin/loader.ts index 97651122f397..a9f44613c6a1 100644 --- a/code/core/src/bin/loader.ts +++ b/code/core/src/bin/loader.ts @@ -14,7 +14,7 @@ import { deprecate } from 'storybook/internal/node-logger'; import { transform } from 'esbuild'; import { dedent } from 'ts-dedent'; -import { NODE_TARGET } from '../shared/constants/environments-support'; +import { NODE_TARGET } from '../shared/constants/environments-support.ts'; export const supportedExtensions = [ '.js', diff --git a/code/core/src/builder-manager/index.ts b/code/core/src/builder-manager/index.ts index 265b37e102d8..de984736d4db 100644 --- a/code/core/src/builder-manager/index.ts +++ b/code/core/src/builder-manager/index.ts @@ -8,9 +8,9 @@ import { resolveModulePath } from 'exsolve'; import { join, parse } from 'pathe'; import sirv from 'sirv'; -import { globalsModuleInfoMap } from '../manager/globals/globals-module-info'; -import { BROWSER_TARGETS, SUPPORTED_FEATURES } from '../shared/constants/environments-support'; -import { resolvePackageDir } from '../shared/utils/module'; +import { globalsModuleInfoMap } from '../manager/globals/globals-module-info.ts'; +import { BROWSER_TARGETS, SUPPORTED_FEATURES } from '../shared/constants/environments-support.ts'; +import { resolvePackageDir } from '../shared/utils/module.ts'; import type { BuilderBuildResult, BuilderFunction, @@ -18,14 +18,14 @@ import type { Compilation, ManagerBuilder, StarterFunction, -} from './types'; -import { getData } from './utils/data'; -import { readOrderedFiles } from './utils/files'; -import { buildFrameworkGlobalsFromOptions } from './utils/framework'; -import { wrapManagerEntries } from './utils/managerEntries'; -import { getTemplatePath, renderHTML } from './utils/template'; - -export { BROWSER_TARGETS, NODE_TARGET } from '../shared/constants/environments-support'; +} from './types.ts'; +import { getData } from './utils/data.ts'; +import { readOrderedFiles } from './utils/files.ts'; +import { buildFrameworkGlobalsFromOptions } from './utils/framework.ts'; +import { wrapManagerEntries } from './utils/managerEntries.ts'; +import { getTemplatePath, renderHTML } from './utils/template.ts'; + +export { BROWSER_TARGETS, NODE_TARGET } from '../shared/constants/environments-support.ts'; const CORE_DIR_ORIGIN = join(resolvePackageDir('storybook'), 'dist/manager'); diff --git a/code/core/src/builder-manager/utils/data.ts b/code/core/src/builder-manager/utils/data.ts index 7defc2da7739..786f7518f840 100644 --- a/code/core/src/builder-manager/utils/data.ts +++ b/code/core/src/builder-manager/utils/data.ts @@ -3,8 +3,8 @@ import { basename } from 'node:path'; import { getRefs } from 'storybook/internal/common'; import type { Options } from 'storybook/internal/types'; -import { executor, getConfig } from '../index'; -import { readTemplate } from './template'; +import { executor, getConfig } from '../index.ts'; +import { readTemplate } from './template.ts'; export const getData = async (options: Options) => { const refs = getRefs(options); diff --git a/code/core/src/builder-manager/utils/files.test.ts b/code/core/src/builder-manager/utils/files.test.ts index d01a9f739109..2b90e89d8b4b 100644 --- a/code/core/src/builder-manager/utils/files.test.ts +++ b/code/core/src/builder-manager/utils/files.test.ts @@ -4,7 +4,7 @@ import { expect, it } from 'vitest'; import type { OutputFile } from 'esbuild'; -import { sanitizePath } from './files'; +import { sanitizePath } from './files.ts'; const os = platform(); const isWindows = os === 'win32'; diff --git a/code/core/src/builder-manager/utils/files.ts b/code/core/src/builder-manager/utils/files.ts index 1e8005aae591..883be52886cb 100644 --- a/code/core/src/builder-manager/utils/files.ts +++ b/code/core/src/builder-manager/utils/files.ts @@ -6,7 +6,7 @@ import type { OutputFile } from 'esbuild'; // eslint-disable-next-line depend/ban-dependencies import slash from 'slash'; -import type { Compilation } from '../types'; +import type { Compilation } from '../types.ts'; export async function readOrderedFiles( addonsDir: string, diff --git a/code/core/src/builder-manager/utils/template.ts b/code/core/src/builder-manager/utils/template.ts index c31088c39d41..43ea18d2856e 100644 --- a/code/core/src/builder-manager/utils/template.ts +++ b/code/core/src/builder-manager/utils/template.ts @@ -5,7 +5,7 @@ import type { DocsOptions, Options, Ref, TagsOptions } from 'storybook/internal/ import { render } from 'ejs'; import { join } from 'pathe'; -import { resolvePackageDir } from '../../shared/utils/module'; +import { resolvePackageDir } from '../../shared/utils/module.ts'; export const getTemplatePath = (template: string) => { return join(resolvePackageDir('storybook'), 'assets/server', template); diff --git a/code/core/src/channels/index.test.ts b/code/core/src/channels/index.test.ts index a55f537971c2..22fab1c3906c 100644 --- a/code/core/src/channels/index.test.ts +++ b/code/core/src/channels/index.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import type { ChannelTransport, Listener } from '.'; -import { Channel, WebsocketTransport } from '.'; +import type { ChannelTransport, Listener } from './index.ts'; +import { Channel, WebsocketTransport } from './index.ts'; const MockedWebsocket = vi.hoisted(() => { const ref = { current: undefined as unknown as InstanceType }; diff --git a/code/core/src/channels/index.ts b/code/core/src/channels/index.ts index f7a36226ee3a..b4a32cd30047 100644 --- a/code/core/src/channels/index.ts +++ b/code/core/src/channels/index.ts @@ -1,20 +1,24 @@ /// import { global } from '@storybook/global'; -import { UniversalStore } from '../shared/universal-store'; -import { Channel } from './main'; -import { PostMessageTransport } from './postmessage'; -import type { ChannelTransport, Config } from './types'; -import { WebsocketTransport } from './websocket'; +import { UniversalStore } from '../shared/universal-store/index.ts'; +import { Channel } from './main.ts'; +import { PostMessageTransport } from './postmessage/index.ts'; +import type { ChannelTransport, Config } from './types.ts'; +import { WebsocketTransport } from './websocket/index.ts'; const { CHANNEL_OPTIONS, CONFIG_TYPE } = global; -export * from './main'; +export * from './main.ts'; export default Channel; -export { PostMessageTransport } from './postmessage'; -export { WebsocketTransport, HEARTBEAT_INTERVAL, HEARTBEAT_MAX_LATENCY } from './websocket'; +export { PostMessageTransport } from './postmessage/index.ts'; +export { + WebsocketTransport, + HEARTBEAT_INTERVAL, + HEARTBEAT_MAX_LATENCY, +} from './websocket/index.ts'; type Options = Config & { extraTransports?: ChannelTransport[]; @@ -56,4 +60,4 @@ export type { ChannelTransport, ChannelHandler, ChannelLike, -} from './types'; +} from './types.ts'; diff --git a/code/core/src/channels/main.ts b/code/core/src/channels/main.ts index ec4119b3c7db..2688d2553043 100644 --- a/code/core/src/channels/main.ts +++ b/code/core/src/channels/main.ts @@ -7,7 +7,7 @@ import type { ChannelTransport, EventsKeyValue, Listener, -} from './types'; +} from './types.ts'; const isMulti = (args: ChannelArgs): args is ChannelArgsMulti => { // @ts-expect-error (we guard against this right here) diff --git a/code/core/src/channels/postmessage/index.ts b/code/core/src/channels/postmessage/index.ts index 9498e31a28b1..3a8b4d8feda0 100644 --- a/code/core/src/channels/postmessage/index.ts +++ b/code/core/src/channels/postmessage/index.ts @@ -13,8 +13,8 @@ import type { ChannelHandler, ChannelTransport, Config, -} from '../types'; -import { getEventSourceUrl } from './getEventSourceUrl'; +} from '../types.ts'; +import { getEventSourceUrl } from './getEventSourceUrl.ts'; const { document, location } = global; diff --git a/code/core/src/channels/websocket/index.ts b/code/core/src/channels/websocket/index.ts index a1a06c9f6131..0c9a6ba5827c 100644 --- a/code/core/src/channels/websocket/index.ts +++ b/code/core/src/channels/websocket/index.ts @@ -6,7 +6,7 @@ import { global } from '@storybook/global'; import { isJSON, parse, stringify } from 'telejson'; import invariant from 'tiny-invariant'; -import type { ChannelHandler, ChannelTransport, Config } from '../types'; +import type { ChannelHandler, ChannelTransport, Config } from '../types.ts'; const { WebSocket } = global; diff --git a/code/core/src/cli/AddonVitestService.constants.ts b/code/core/src/cli/AddonVitestService.constants.ts index 9e5accff34f2..e96d6833813a 100644 --- a/code/core/src/cli/AddonVitestService.constants.ts +++ b/code/core/src/cli/AddonVitestService.constants.ts @@ -1,4 +1,4 @@ -import { SupportedFramework } from '../types'; +import { SupportedFramework } from '../types/index.ts'; export const SUPPORTED_FRAMEWORKS: readonly SupportedFramework[] = [ SupportedFramework.HTML_VITE, diff --git a/code/core/src/cli/AddonVitestService.test.ts b/code/core/src/cli/AddonVitestService.test.ts index f16a224c06b3..9b432fdd74d5 100644 --- a/code/core/src/cli/AddonVitestService.test.ts +++ b/code/core/src/cli/AddonVitestService.test.ts @@ -11,8 +11,8 @@ import * as find from 'empathic/find'; // eslint-disable-next-line depend/ban-dependencies import type { ResultPromise } from 'execa'; -import { SupportedBuilder, SupportedFramework } from '../types'; -import { AddonVitestService } from './AddonVitestService'; +import { SupportedBuilder, SupportedFramework } from '../types/index.ts'; +import { AddonVitestService } from './AddonVitestService.ts'; vi.mock('node:fs/promises', { spy: true }); vi.mock('node:os', { spy: true }); diff --git a/code/core/src/cli/AddonVitestService.ts b/code/core/src/cli/AddonVitestService.ts index 9a49129e48d2..d39dd0a0bf72 100644 --- a/code/core/src/cli/AddonVitestService.ts +++ b/code/core/src/cli/AddonVitestService.ts @@ -12,8 +12,8 @@ import * as find from 'empathic/find'; import { coerce, minVersion, satisfies, validRange } from 'semver'; import { dedent } from 'ts-dedent'; -import { SupportedBuilder, type SupportedFramework } from '../types'; -import { SUPPORTED_FRAMEWORKS } from './AddonVitestService.constants'; +import { SupportedBuilder, type SupportedFramework } from '../types/index.ts'; +import { SUPPORTED_FRAMEWORKS } from './AddonVitestService.constants.ts'; type Result = { compatible: boolean; diff --git a/code/core/src/cli/NpmOptions.ts b/code/core/src/cli/NpmOptions.ts index 1a93a9832caf..296bf720f940 100644 --- a/code/core/src/cli/NpmOptions.ts +++ b/code/core/src/cli/NpmOptions.ts @@ -1,3 +1,3 @@ -import type { JsPackageManager } from '../common/js-package-manager/JsPackageManager'; +import type { JsPackageManager } from '../common/js-package-manager/JsPackageManager.ts'; export type NpmOptions = Parameters[0]; diff --git a/code/core/src/cli/dirs.ts b/code/core/src/cli/dirs.ts index 80fb96fedcd4..92670181aa3f 100644 --- a/code/core/src/cli/dirs.ts +++ b/code/core/src/cli/dirs.ts @@ -18,7 +18,7 @@ import getNpmTarballUrlDefault from 'get-npm-tarball-url'; import { unpackTar } from 'modern-tar/fs'; import invariant from 'tiny-invariant'; -import { resolvePackageDir } from '../shared/utils/module'; +import { resolvePackageDir } from '../shared/utils/module.ts'; const resolveUsingBranchInstall = async (packageManager: JsPackageManager, request: string) => { const tempDirectory = await temporaryDirectory(); diff --git a/code/core/src/cli/eslintPlugin.test.ts b/code/core/src/cli/eslintPlugin.test.ts index e6fda6380e07..882c4cdcf50e 100644 --- a/code/core/src/cli/eslintPlugin.test.ts +++ b/code/core/src/cli/eslintPlugin.test.ts @@ -5,14 +5,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as find from 'empathic/find'; import { dedent } from 'ts-dedent'; -import type { PackageJsonWithDepsAndDevDeps } from '../common'; -import type { JsPackageManager } from '../common/js-package-manager/JsPackageManager'; +import type { PackageJsonWithDepsAndDevDeps } from '../common/index.ts'; +import type { JsPackageManager } from '../common/js-package-manager/JsPackageManager.ts'; import { configureEslintPlugin, extractEslintInfo, findEslintFile, normalizeExtends, -} from './eslintPlugin'; +} from './eslintPlugin.ts'; vi.mock('empathic/find', () => ({ up: vi.fn(), diff --git a/code/core/src/cli/eslintPlugin.ts b/code/core/src/cli/eslintPlugin.ts index 4ff6561c541a..95163eb619cb 100644 --- a/code/core/src/cli/eslintPlugin.ts +++ b/code/core/src/cli/eslintPlugin.ts @@ -10,7 +10,7 @@ import * as find from 'empathic/find'; import picocolors from 'picocolors'; import { dedent } from 'ts-dedent'; -import { babelParse, recast, types as t, traverse } from '../babel'; +import { babelParse, recast, types as t, traverse } from '../babel/index.ts'; export const SUPPORTED_ESLINT_EXTENSIONS = ['ts', 'mts', 'cts', 'mjs', 'js', 'cjs', 'json']; const UNSUPPORTED_ESLINT_EXTENSIONS = ['yaml', 'yml']; diff --git a/code/core/src/cli/globalSettings.test.ts b/code/core/src/cli/globalSettings.test.ts index 95060338d0b4..d6d7548055cb 100644 --- a/code/core/src/cli/globalSettings.test.ts +++ b/code/core/src/cli/globalSettings.test.ts @@ -4,7 +4,7 @@ import { afterEach } from 'node:test'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { type Settings, _clearGlobalSettings, globalSettings } from './globalSettings'; +import { type Settings, _clearGlobalSettings, globalSettings } from './globalSettings.ts'; vi.mock('node:fs'); vi.mock('node:fs/promises'); diff --git a/code/core/src/cli/globalSettings.ts b/code/core/src/cli/globalSettings.ts index 58406c2f8b5d..ee20cf140d35 100644 --- a/code/core/src/cli/globalSettings.ts +++ b/code/core/src/cli/globalSettings.ts @@ -5,7 +5,7 @@ import { dirname, join } from 'node:path'; import { dedent } from 'ts-dedent'; import { z } from 'zod'; -import { invariant } from '../common/utils/utils'; +import { invariant } from '../common/utils/utils.ts'; const DEFAULT_SETTINGS_PATH = join(homedir(), '.storybook', 'settings.json'); diff --git a/code/core/src/cli/helpers.test.ts b/code/core/src/cli/helpers.test.ts index ffccebb4f8d3..90bb8765edde 100644 --- a/code/core/src/cli/helpers.test.ts +++ b/code/core/src/cli/helpers.test.ts @@ -8,8 +8,8 @@ import { Feature, SupportedLanguage, SupportedRenderer } from 'storybook/interna import { sep } from 'path'; -import { IS_WINDOWS } from '../../../vitest.helpers'; -import * as helpers from './helpers'; +import { IS_WINDOWS } from '../../../vitest.helpers.ts'; +import * as helpers from './helpers.ts'; const normalizePath = (path: string) => (IS_WINDOWS ? path.replace(/\//g, sep) : path); diff --git a/code/core/src/cli/helpers.ts b/code/core/src/cli/helpers.ts index aecedea634f4..53aa9753102f 100644 --- a/code/core/src/cli/helpers.ts +++ b/code/core/src/cli/helpers.ts @@ -20,7 +20,7 @@ import { coerce, satisfies } from 'semver'; import stripJsonComments from 'strip-json-comments'; import invariant from 'tiny-invariant'; -import { getRendererDir } from './dirs'; +import { getRendererDir } from './dirs.ts'; export function readFileAsJson(jsonPath: string, allowComments?: boolean) { const filePath = resolve(jsonPath); diff --git a/code/core/src/cli/index.ts b/code/core/src/cli/index.ts index 617abb5c60d7..a7054c44fce5 100644 --- a/code/core/src/cli/index.ts +++ b/code/core/src/cli/index.ts @@ -1,9 +1,9 @@ -export * from './detect'; -export * from './helpers'; -export * from './angular/helpers'; -export * from './dirs'; -export * from './projectTypes'; -export * from './NpmOptions'; -export * from './eslintPlugin'; -export * from './globalSettings'; -export * from './AddonVitestService'; +export * from './detect.ts'; +export * from './helpers.ts'; +export * from './angular/helpers.ts'; +export * from './dirs.ts'; +export * from './projectTypes.ts'; +export * from './NpmOptions.ts'; +export * from './eslintPlugin.ts'; +export * from './globalSettings.ts'; +export * from './AddonVitestService.ts'; diff --git a/code/core/src/client-logger/index.test.ts b/code/core/src/client-logger/index.test.ts index c46f70f2fd00..5de47bdaf056 100644 --- a/code/core/src/client-logger/index.test.ts +++ b/code/core/src/client-logger/index.test.ts @@ -1,6 +1,6 @@ import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'; -import { logger } from '.'; +import { logger } from './index.ts'; vi.mock('@storybook/global', () => ({ global: { ...global, LOGLEVEL: 'debug' } })); diff --git a/code/core/src/common/config.test.ts b/code/core/src/common/config.test.ts index de70dd7fe9a4..65db5ea5dd6c 100644 --- a/code/core/src/common/config.test.ts +++ b/code/core/src/common/config.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { filterPresetsConfig } from './presets'; +import { filterPresetsConfig } from './presets.ts'; describe('filterPresetsConfig', () => { it('string config', () => { diff --git a/code/core/src/common/index.ts b/code/core/src/common/index.ts index 928d44c89e5e..e2146b95da09 100644 --- a/code/core/src/common/index.ts +++ b/code/core/src/common/index.ts @@ -1,54 +1,53 @@ -import versions from './versions'; +import versions from './versions.ts'; /// -export * from './presets'; - -export * from './utils/cache'; -export * from './utils/cli'; -export * from './utils/check-addon-order'; -export * from './utils/envs'; -export * from './utils/common-glob-options'; -export * from './utils/framework'; -export * from './utils/get-builder-options'; -export * from './utils/get-framework-name'; -export * from './utils/get-renderer-name'; -export * from './utils/get-storybook-configuration'; -export * from './utils/get-storybook-info'; -export * from './utils/get-storybook-refs'; -export * from './utils/glob-to-regexp'; -export * from './utils/HandledError'; -export * from './utils/interpolate'; -export * from './utils/interpret-files'; -export * from './utils/interpret-require'; -export * from './utils/load-main-config'; -export * from './utils/load-manager-or-addons-file'; -export * from './utils/load-preview-or-config-file'; -export * from './utils/log-config'; -export * from './utils/normalize-stories'; -export * from './utils/paths'; -export * from './utils/readTemplate'; -export * from './utils/remove'; -export * from './utils/resolve-path-in-sb-cache'; -export * from './utils/symlinks'; -export * from './utils/template'; -export * from './utils/validate-config'; -export * from './utils/validate-configuration-files'; -export * from './utils/satisfies'; -export * from './utils/formatter'; -export * from './utils/get-story-id'; -export * from './utils/posix'; -export * from './utils/sync-main-preview-addons'; -export * from './utils/setup-addon-in-config'; -export * from './utils/wrap-getAbsolutePath-utils'; -export * from './js-package-manager'; -export * from './utils/scan-and-transform-files'; -export * from './utils/transform-imports'; -export * from '../shared/utils/module'; -export * from './utils/get-addon-names'; -export * from './utils/utils'; -export * from './utils/command'; +export * from './presets.ts'; +export * from './utils/cache.ts'; +export * from './utils/cli.ts'; +export * from './utils/check-addon-order.ts'; +export * from './utils/envs.ts'; +export * from './utils/common-glob-options.ts'; +export * from './utils/framework.ts'; +export * from './utils/get-builder-options.ts'; +export * from './utils/get-framework-name.ts'; +export * from './utils/get-renderer-name.ts'; +export * from './utils/get-storybook-configuration.ts'; +export * from './utils/get-storybook-info.ts'; +export * from './utils/get-storybook-refs.ts'; +export * from './utils/glob-to-regexp.ts'; +export * from './utils/HandledError.ts'; +export * from './utils/interpolate.ts'; +export * from './utils/interpret-files.ts'; +export * from './utils/interpret-require.ts'; +export * from './utils/load-main-config.ts'; +export * from './utils/load-manager-or-addons-file.ts'; +export * from './utils/load-preview-or-config-file.ts'; +export * from './utils/log-config.ts'; +export * from './utils/normalize-stories.ts'; +export * from './utils/paths.ts'; +export * from './utils/readTemplate.ts'; +export * from './utils/remove.ts'; +export * from './utils/resolve-path-in-sb-cache.ts'; +export * from './utils/symlinks.ts'; +export * from './utils/template.ts'; +export * from './utils/validate-config.ts'; +export * from './utils/validate-configuration-files.ts'; +export * from './utils/satisfies.ts'; +export * from './utils/formatter.ts'; +export * from './utils/get-story-id.ts'; +export * from './utils/posix.ts'; +export * from './utils/sync-main-preview-addons.ts'; +export * from './utils/setup-addon-in-config.ts'; +export * from './utils/wrap-getAbsolutePath-utils.ts'; +export * from './js-package-manager/index.ts'; +export * from './utils/scan-and-transform-files.ts'; +export * from './utils/transform-imports.ts'; +export * from '../shared/utils/module.ts'; +export * from './utils/get-addon-names.ts'; +export * from './utils/utils.ts'; +export * from './utils/command.ts'; export { versions }; -export { createFileSystemCache, FileSystemCache } from './utils/file-cache'; +export { createFileSystemCache, FileSystemCache } from './utils/file-cache.ts'; diff --git a/code/core/src/common/js-package-manager/BUNProxy.ts b/code/core/src/common/js-package-manager/BUNProxy.ts index b5a39f16d7a0..47b8ffeab333 100644 --- a/code/core/src/common/js-package-manager/BUNProxy.ts +++ b/code/core/src/common/js-package-manager/BUNProxy.ts @@ -9,12 +9,12 @@ import * as find from 'empathic/find'; import type { ResultPromise } from 'execa'; import sort from 'semver/functions/sort.js'; -import type { ExecuteCommandOptions } from '../utils/command'; -import { executeCommand } from '../utils/command'; -import { getProjectRoot } from '../utils/paths'; -import { JsPackageManager, PackageManagerName } from './JsPackageManager'; -import type { PackageJson } from './PackageJson'; -import type { InstallationMetadata, PackageMetadata } from './types'; +import type { ExecuteCommandOptions } from '../utils/command.ts'; +import { executeCommand } from '../utils/command.ts'; +import { getProjectRoot } from '../utils/paths.ts'; +import { JsPackageManager, PackageManagerName } from './JsPackageManager.ts'; +import type { PackageJson } from './PackageJson.ts'; +import type { InstallationMetadata, PackageMetadata } from './types.ts'; type NpmDependency = { version: string; diff --git a/code/core/src/common/js-package-manager/JsPackageManager.test.ts b/code/core/src/common/js-package-manager/JsPackageManager.test.ts index 8ca7d8ec4a8a..63353cc26828 100644 --- a/code/core/src/common/js-package-manager/JsPackageManager.test.ts +++ b/code/core/src/common/js-package-manager/JsPackageManager.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { JsPackageManager } from './JsPackageManager'; +import { JsPackageManager } from './JsPackageManager.ts'; const mockVersions = vi.hoisted(() => ({ '@storybook/react': '8.3.0', diff --git a/code/core/src/common/js-package-manager/JsPackageManager.ts b/code/core/src/common/js-package-manager/JsPackageManager.ts index 53be2316dbca..48bf28df02b2 100644 --- a/code/core/src/common/js-package-manager/JsPackageManager.ts +++ b/code/core/src/common/js-package-manager/JsPackageManager.ts @@ -13,12 +13,12 @@ import picocolors from 'picocolors'; import { coerce, gt, satisfies } from 'semver'; import invariant from 'tiny-invariant'; -import { HandledError } from '../utils/HandledError'; -import type { ExecuteCommandOptions } from '../utils/command'; -import { findFilesUp, getProjectRoot } from '../utils/paths'; -import storybookPackagesVersions from '../versions'; -import type { PackageJson, PackageJsonWithDepsAndDevDeps } from './PackageJson'; -import type { InstallationMetadata } from './types'; +import { HandledError } from '../utils/HandledError.ts'; +import type { ExecuteCommandOptions } from '../utils/command.ts'; +import { findFilesUp, getProjectRoot } from '../utils/paths.ts'; +import storybookPackagesVersions from '../versions.ts'; +import type { PackageJson, PackageJsonWithDepsAndDevDeps } from './PackageJson.ts'; +import type { InstallationMetadata } from './types.ts'; export enum PackageManagerName { NPM = 'npm', diff --git a/code/core/src/common/js-package-manager/JsPackageManagerFactory.test.ts b/code/core/src/common/js-package-manager/JsPackageManagerFactory.test.ts index 50070bfbfc0e..59302b4fbcda 100644 --- a/code/core/src/common/js-package-manager/JsPackageManagerFactory.test.ts +++ b/code/core/src/common/js-package-manager/JsPackageManagerFactory.test.ts @@ -4,14 +4,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as find from 'empathic/find'; -import { PackageManagerName } from '.'; -import { executeCommandSync } from '../utils/command'; -import { BUNProxy } from './BUNProxy'; -import { JsPackageManagerFactory } from './JsPackageManagerFactory'; -import { NPMProxy } from './NPMProxy'; -import { PNPMProxy } from './PNPMProxy'; -import { Yarn1Proxy } from './Yarn1Proxy'; -import { Yarn2Proxy } from './Yarn2Proxy'; +import { PackageManagerName } from './index.ts'; +import { executeCommandSync } from '../utils/command.ts'; +import { BUNProxy } from './BUNProxy.ts'; +import { JsPackageManagerFactory } from './JsPackageManagerFactory.ts'; +import { NPMProxy } from './NPMProxy.ts'; +import { PNPMProxy } from './PNPMProxy.ts'; +import { Yarn1Proxy } from './Yarn1Proxy.ts'; +import { Yarn2Proxy } from './Yarn2Proxy.ts'; vi.mock('../utils/command', { spy: true }); const executeCommandSyncMock = vi.mocked(executeCommandSync); diff --git a/code/core/src/common/js-package-manager/JsPackageManagerFactory.ts b/code/core/src/common/js-package-manager/JsPackageManagerFactory.ts index a7dc0523f538..853fdf05db87 100644 --- a/code/core/src/common/js-package-manager/JsPackageManagerFactory.ts +++ b/code/core/src/common/js-package-manager/JsPackageManagerFactory.ts @@ -2,22 +2,22 @@ import { basename, parse, relative } from 'node:path'; import * as find from 'empathic/find'; -import { executeCommandSync } from '../utils/command'; -import { getProjectRoot } from '../utils/paths'; -import { BUNProxy } from './BUNProxy'; -import type { JsPackageManager } from './JsPackageManager'; -import { PackageManagerName } from './JsPackageManager'; -import { NPMProxy } from './NPMProxy'; -import { PNPMProxy } from './PNPMProxy'; -import { Yarn1Proxy } from './Yarn1Proxy'; -import { Yarn2Proxy } from './Yarn2Proxy'; +import { executeCommandSync } from '../utils/command.ts'; +import { getProjectRoot } from '../utils/paths.ts'; +import { BUNProxy } from './BUNProxy.ts'; +import type { JsPackageManager } from './JsPackageManager.ts'; +import { PackageManagerName } from './JsPackageManager.ts'; +import { NPMProxy } from './NPMProxy.ts'; +import { PNPMProxy } from './PNPMProxy.ts'; +import { Yarn1Proxy } from './Yarn1Proxy.ts'; +import { Yarn2Proxy } from './Yarn2Proxy.ts'; import { BUN_LOCKFILE, BUN_LOCKFILE_BINARY, NPM_LOCKFILE, PNPM_LOCKFILE, YARN_LOCKFILE, -} from './constants'; +} from './constants.ts'; type PackageManagerProxy = | typeof NPMProxy diff --git a/code/core/src/common/js-package-manager/NPMProxy.test.ts b/code/core/src/common/js-package-manager/NPMProxy.test.ts index c5394a9ecb5a..42bedeb9cbfa 100644 --- a/code/core/src/common/js-package-manager/NPMProxy.test.ts +++ b/code/core/src/common/js-package-manager/NPMProxy.test.ts @@ -2,9 +2,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { prompt } from 'storybook/internal/node-logger'; -import { executeCommand } from '../utils/command'; -import { JsPackageManager } from './JsPackageManager'; -import { NPMProxy } from './NPMProxy'; +import { executeCommand } from '../utils/command.ts'; +import { JsPackageManager } from './JsPackageManager.ts'; +import { NPMProxy } from './NPMProxy.ts'; vi.mock('storybook/internal/node-logger', () => ({ prompt: { @@ -18,7 +18,7 @@ vi.mock('storybook/internal/node-logger', () => ({ }, })); -vi.mock(import('../utils/command'), { spy: true }); +vi.mock(import('../utils/command.ts'), { spy: true }); const mockedExecuteCommand = vi.mocked(executeCommand); @@ -222,7 +222,9 @@ describe('NPM Proxy', () => { describe('getVersion', () => { it('with a Storybook package listed in versions.json it returns the version', async () => { - const storybookAngularVersion = (await import('../versions')).default['@storybook/angular']; + const storybookAngularVersion = (await import('../versions.ts')).default[ + '@storybook/angular' + ]; const executeCommandSpy = mockedExecuteCommand.mockResolvedValue({ stdout: '5.3.19' } as any); const version = await npmProxy.getVersion('@storybook/angular'); diff --git a/code/core/src/common/js-package-manager/NPMProxy.ts b/code/core/src/common/js-package-manager/NPMProxy.ts index c1d56243e705..8aee0ee4408e 100644 --- a/code/core/src/common/js-package-manager/NPMProxy.ts +++ b/code/core/src/common/js-package-manager/NPMProxy.ts @@ -10,12 +10,12 @@ import * as find from 'empathic/find'; import type { ResultPromise } from 'execa'; import sort from 'semver/functions/sort.js'; -import type { ExecuteCommandOptions } from '../utils/command'; -import { executeCommand } from '../utils/command'; -import { getProjectRoot } from '../utils/paths'; -import { JsPackageManager, PackageManagerName } from './JsPackageManager'; -import type { PackageJson } from './PackageJson'; -import type { InstallationMetadata, PackageMetadata } from './types'; +import type { ExecuteCommandOptions } from '../utils/command.ts'; +import { executeCommand } from '../utils/command.ts'; +import { getProjectRoot } from '../utils/paths.ts'; +import { JsPackageManager, PackageManagerName } from './JsPackageManager.ts'; +import type { PackageJson } from './PackageJson.ts'; +import type { InstallationMetadata, PackageMetadata } from './types.ts'; type NpmDependency = { version: string; diff --git a/code/core/src/common/js-package-manager/PNPMProxy.test.ts b/code/core/src/common/js-package-manager/PNPMProxy.test.ts index 6926096dffaf..3e80c045178a 100644 --- a/code/core/src/common/js-package-manager/PNPMProxy.test.ts +++ b/code/core/src/common/js-package-manager/PNPMProxy.test.ts @@ -2,9 +2,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { prompt } from 'storybook/internal/node-logger'; -import { executeCommand } from '../utils/command'; -import { JsPackageManager } from './JsPackageManager'; -import { PNPMProxy } from './PNPMProxy'; +import { executeCommand } from '../utils/command.ts'; +import { JsPackageManager } from './JsPackageManager.ts'; +import { PNPMProxy } from './PNPMProxy.ts'; vi.mock('storybook/internal/node-logger', () => ({ prompt: { @@ -18,7 +18,7 @@ vi.mock('storybook/internal/node-logger', () => ({ }, })); -vi.mock(import('../utils/command'), { spy: true }); +vi.mock(import('../utils/command.ts'), { spy: true }); const mockedExecuteCommand = vi.mocked(executeCommand); describe('PNPM Proxy', () => { @@ -150,7 +150,9 @@ describe('PNPM Proxy', () => { describe('getVersion', () => { it('with a Storybook package listed in versions.json it returns the version', async () => { - const storybookAngularVersion = (await import('../versions')).default['@storybook/angular']; + const storybookAngularVersion = (await import('../versions.ts')).default[ + '@storybook/angular' + ]; const executeCommandSpy = mockedExecuteCommand.mockResolvedValue({ stdout: '5.3.19' } as any); const version = await pnpmProxy.getVersion('@storybook/angular'); diff --git a/code/core/src/common/js-package-manager/PNPMProxy.ts b/code/core/src/common/js-package-manager/PNPMProxy.ts index 1a6bb6839124..db0ce442bc38 100644 --- a/code/core/src/common/js-package-manager/PNPMProxy.ts +++ b/code/core/src/common/js-package-manager/PNPMProxy.ts @@ -9,12 +9,12 @@ import * as find from 'empathic/find'; // eslint-disable-next-line depend/ban-dependencies import type { ResultPromise } from 'execa'; -import type { ExecuteCommandOptions } from '../utils/command'; -import { executeCommand } from '../utils/command'; -import { getProjectRoot } from '../utils/paths'; -import { JsPackageManager, PackageManagerName } from './JsPackageManager'; -import type { PackageJson } from './PackageJson'; -import type { InstallationMetadata, PackageMetadata } from './types'; +import type { ExecuteCommandOptions } from '../utils/command.ts'; +import { executeCommand } from '../utils/command.ts'; +import { getProjectRoot } from '../utils/paths.ts'; +import { JsPackageManager, PackageManagerName } from './JsPackageManager.ts'; +import type { PackageJson } from './PackageJson.ts'; +import type { InstallationMetadata, PackageMetadata } from './types.ts'; type PnpmDependency = { from: string; diff --git a/code/core/src/common/js-package-manager/Yarn1Proxy.test.ts b/code/core/src/common/js-package-manager/Yarn1Proxy.test.ts index cdce47483712..7d21c4719e54 100644 --- a/code/core/src/common/js-package-manager/Yarn1Proxy.test.ts +++ b/code/core/src/common/js-package-manager/Yarn1Proxy.test.ts @@ -4,9 +4,9 @@ import { prompt } from 'storybook/internal/node-logger'; import { dedent } from 'ts-dedent'; -import { executeCommand } from '../utils/command'; -import { JsPackageManager, PackageManagerName } from './JsPackageManager'; -import { Yarn1Proxy } from './Yarn1Proxy'; +import { executeCommand } from '../utils/command.ts'; +import { JsPackageManager, PackageManagerName } from './JsPackageManager.ts'; +import { Yarn1Proxy } from './Yarn1Proxy.ts'; vi.mock('storybook/internal/node-logger', () => ({ prompt: { @@ -20,7 +20,7 @@ vi.mock('storybook/internal/node-logger', () => ({ }, })); -vi.mock(import('../utils/command'), { spy: true }); +vi.mock(import('../utils/command.ts'), { spy: true }); const mockedExecuteCommand = vi.mocked(executeCommand); vi.mock('node:process', async (importOriginal) => { diff --git a/code/core/src/common/js-package-manager/Yarn1Proxy.ts b/code/core/src/common/js-package-manager/Yarn1Proxy.ts index 77eafe80b635..ef078ded5ec9 100644 --- a/code/core/src/common/js-package-manager/Yarn1Proxy.ts +++ b/code/core/src/common/js-package-manager/Yarn1Proxy.ts @@ -9,13 +9,13 @@ import * as find from 'empathic/find'; // eslint-disable-next-line depend/ban-dependencies import type { ResultPromise } from 'execa'; -import type { ExecuteCommandOptions } from '../utils/command'; -import { executeCommand } from '../utils/command'; -import { getProjectRoot } from '../utils/paths'; -import { JsPackageManager, PackageManagerName } from './JsPackageManager'; -import type { PackageJson } from './PackageJson'; -import type { InstallationMetadata, PackageMetadata } from './types'; -import { parsePackageData } from './util'; +import type { ExecuteCommandOptions } from '../utils/command.ts'; +import { executeCommand } from '../utils/command.ts'; +import { getProjectRoot } from '../utils/paths.ts'; +import { JsPackageManager, PackageManagerName } from './JsPackageManager.ts'; +import type { PackageJson } from './PackageJson.ts'; +import type { InstallationMetadata, PackageMetadata } from './types.ts'; +import { parsePackageData } from './util.ts'; type Yarn1ListItem = { name: string; diff --git a/code/core/src/common/js-package-manager/Yarn2Proxy.test.ts b/code/core/src/common/js-package-manager/Yarn2Proxy.test.ts index 529b131c69cb..5e9ba83bf574 100644 --- a/code/core/src/common/js-package-manager/Yarn2Proxy.test.ts +++ b/code/core/src/common/js-package-manager/Yarn2Proxy.test.ts @@ -2,9 +2,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { prompt } from 'storybook/internal/node-logger'; -import { executeCommand } from '../utils/command'; -import { JsPackageManager } from './JsPackageManager'; -import { Yarn2Proxy } from './Yarn2Proxy'; +import { executeCommand } from '../utils/command.ts'; +import { JsPackageManager } from './JsPackageManager.ts'; +import { Yarn2Proxy } from './Yarn2Proxy.ts'; vi.mock('storybook/internal/node-logger', () => ({ prompt: { diff --git a/code/core/src/common/js-package-manager/Yarn2Proxy.ts b/code/core/src/common/js-package-manager/Yarn2Proxy.ts index 024435d25d58..ba4f25bd1a67 100644 --- a/code/core/src/common/js-package-manager/Yarn2Proxy.ts +++ b/code/core/src/common/js-package-manager/Yarn2Proxy.ts @@ -11,14 +11,14 @@ import * as find from 'empathic/find'; // eslint-disable-next-line depend/ban-dependencies import type { ResultPromise } from 'execa'; -import { logger } from '../../node-logger'; -import type { ExecuteCommandOptions } from '../utils/command'; -import { executeCommand } from '../utils/command'; -import { getProjectRoot } from '../utils/paths'; -import { JsPackageManager, PackageManagerName } from './JsPackageManager'; -import type { PackageJson } from './PackageJson'; -import type { InstallationMetadata, PackageMetadata } from './types'; -import { parsePackageData } from './util'; +import { logger } from '../../node-logger/index.ts'; +import type { ExecuteCommandOptions } from '../utils/command.ts'; +import { executeCommand } from '../utils/command.ts'; +import { getProjectRoot } from '../utils/paths.ts'; +import { JsPackageManager, PackageManagerName } from './JsPackageManager.ts'; +import type { PackageJson } from './PackageJson.ts'; +import type { InstallationMetadata, PackageMetadata } from './types.ts'; +import { parsePackageData } from './util.ts'; // more info at https://yarnpkg.com/advanced/error-codes const CRITICAL_YARN2_ERROR_CODES = { diff --git a/code/core/src/common/js-package-manager/index.ts b/code/core/src/common/js-package-manager/index.ts index 58cfad6420a7..039b07bd906f 100644 --- a/code/core/src/common/js-package-manager/index.ts +++ b/code/core/src/common/js-package-manager/index.ts @@ -1,4 +1,4 @@ -export * from './JsPackageManagerFactory'; -export * from './JsPackageManager'; -export * from './PackageJson'; -export * from './types'; +export * from './JsPackageManagerFactory.ts'; +export * from './JsPackageManager.ts'; +export * from './PackageJson.ts'; +export * from './types.ts'; diff --git a/code/core/src/common/presets.test.ts b/code/core/src/common/presets.test.ts index 3233734e1516..059cd55db65f 100644 --- a/code/core/src/common/presets.test.ts +++ b/code/core/src/common/presets.test.ts @@ -5,8 +5,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { logger } from 'storybook/internal/node-logger'; -import * as resolveUtils from '../shared/utils/module'; -import { getPresets, loadPreset, resolveAddonName } from './presets'; +import * as resolveUtils from '../shared/utils/module.ts'; +import { getPresets, loadPreset, resolveAddonName } from './presets.ts'; function wrapPreset(basePresets: any): { babel: Function; webpack: Function } { return { diff --git a/code/core/src/common/presets.ts b/code/core/src/common/presets.ts index 38298c4b22a3..113d64298527 100644 --- a/code/core/src/common/presets.ts +++ b/code/core/src/common/presets.ts @@ -15,11 +15,11 @@ import type { import { join, parse, resolve } from 'pathe'; import { dedent } from 'ts-dedent'; -import type { ChannelLike } from '../channels'; -import { importModule, safeResolveModule } from '../shared/utils/module'; -import { getInterpretedFile } from './utils/interpret-files'; -import { stripAbsNodeModulesPath } from './utils/strip-abs-node-modules-path'; -import { validateConfigurationFiles } from './utils/validate-configuration-files'; +import type { ChannelLike } from '../channels/index.ts'; +import { importModule, safeResolveModule } from '../shared/utils/module.ts'; +import { getInterpretedFile } from './utils/interpret-files.ts'; +import { stripAbsNodeModulesPath } from './utils/strip-abs-node-modules-path.ts'; +import { validateConfigurationFiles } from './utils/validate-configuration-files.ts'; export type InterPresetOptions = Omit< CLIOptions & diff --git a/code/core/src/common/utils/__tests__/interpret-files.test.ts b/code/core/src/common/utils/__tests__/interpret-files.test.ts index 7d898af3ab04..8de2e17647fc 100644 --- a/code/core/src/common/utils/__tests__/interpret-files.test.ts +++ b/code/core/src/common/utils/__tests__/interpret-files.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { vol } from 'memfs'; -import { getInterpretedFile } from '../interpret-files'; +import { getInterpretedFile } from '../interpret-files.ts'; vi.mock('fs', async () => { const memfs = await vi.importActual('memfs'); diff --git a/code/core/src/common/utils/__tests__/normalize-stories.test.ts b/code/core/src/common/utils/__tests__/normalize-stories.test.ts index 1c78c2737873..fdcef0954468 100644 --- a/code/core/src/common/utils/__tests__/normalize-stories.test.ts +++ b/code/core/src/common/utils/__tests__/normalize-stories.test.ts @@ -9,7 +9,7 @@ import { getDirectoryFromWorkingDir, normalizeStories, normalizeStoriesEntry, -} from '../normalize-stories'; +} from '../normalize-stories.ts'; expect.addSnapshotSerializer({ print: (val: any) => JSON.stringify(val, null, 2), diff --git a/code/core/src/common/utils/__tests__/paths.test.ts b/code/core/src/common/utils/__tests__/paths.test.ts index 8f019dea10c8..bd9b9b8cf7be 100644 --- a/code/core/src/common/utils/__tests__/paths.test.ts +++ b/code/core/src/common/utils/__tests__/paths.test.ts @@ -6,7 +6,7 @@ import * as find from 'empathic/find'; // eslint-disable-next-line depend/ban-dependencies import slash from 'slash'; -import { getProjectRoot, normalizeStoryPath } from '../paths'; +import { getProjectRoot, normalizeStoryPath } from '../paths.ts'; vi.mock('empathic/find'); diff --git a/code/core/src/common/utils/__tests__/template.test.ts b/code/core/src/common/utils/__tests__/template.test.ts index f91e8857617e..5bf5747778c7 100644 --- a/code/core/src/common/utils/__tests__/template.test.ts +++ b/code/core/src/common/utils/__tests__/template.test.ts @@ -4,7 +4,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { vol } from 'memfs'; -import { getPreviewBodyTemplate, getPreviewHeadTemplate } from '../template'; +import { getPreviewBodyTemplate, getPreviewHeadTemplate } from '../template.ts'; vi.mock('fs', async () => { const memfs = await vi.importActual('memfs'); diff --git a/code/core/src/common/utils/cache.ts b/code/core/src/common/utils/cache.ts index eddec3d6242c..6f3b7c6d439d 100644 --- a/code/core/src/common/utils/cache.ts +++ b/code/core/src/common/utils/cache.ts @@ -1,5 +1,5 @@ -import { createFileSystemCache } from './file-cache'; -import { resolvePathInStorybookCache } from './resolve-path-in-sb-cache'; +import { createFileSystemCache } from './file-cache.ts'; +import { resolvePathInStorybookCache } from './resolve-path-in-sb-cache.ts'; export const cache = createFileSystemCache({ basePath: resolvePathInStorybookCache('dev-server'), diff --git a/code/core/src/common/utils/cli.test.ts b/code/core/src/common/utils/cli.test.ts index 07ce1ca93f23..77fe6b29cb55 100644 --- a/code/core/src/common/utils/cli.test.ts +++ b/code/core/src/common/utils/cli.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { isCorePackage } from './cli'; +import { isCorePackage } from './cli.ts'; describe('UTILS', () => { describe.each([ diff --git a/code/core/src/common/utils/cli.ts b/code/core/src/common/utils/cli.ts index 9b216fbee848..046a266a64c8 100644 --- a/code/core/src/common/utils/cli.ts +++ b/code/core/src/common/utils/cli.ts @@ -7,9 +7,9 @@ import { join } from 'node:path'; import { type MergeExclusive } from 'type-fest'; import uniqueString from 'unique-string'; -import type { JsPackageManager } from '../js-package-manager'; -import satelliteAddons from '../satellite-addons'; -import storybookPackagesVersions from '../versions'; +import type { JsPackageManager } from '../js-package-manager/index.ts'; +import satelliteAddons from '../satellite-addons.ts'; +import storybookPackagesVersions from '../versions.ts'; const tempDir = () => realpath(os.tmpdir()); diff --git a/code/core/src/common/utils/envs.ts b/code/core/src/common/utils/envs.ts index c87005101d5c..415c04d9975e 100644 --- a/code/core/src/common/utils/envs.ts +++ b/code/core/src/common/utils/envs.ts @@ -2,7 +2,7 @@ export { isWebContainer } from '@webcontainer/env'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - Needed for Angular sandbox running without --no-link option. Do NOT convert to @ts-expect-error! -import { nodePathsToArray } from './paths'; +import { nodePathsToArray } from './paths.ts'; // Load environment variables starts with STORYBOOK_ to the client side. diff --git a/code/core/src/common/utils/file-cache.ts b/code/core/src/common/utils/file-cache.ts index 35b201b53e32..c400323000a6 100644 --- a/code/core/src/common/utils/file-cache.ts +++ b/code/core/src/common/utils/file-cache.ts @@ -5,7 +5,7 @@ import { mkdir, readFile, readdir, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { writeFileWithRetry } from './write-file-with-retry'; +import { writeFileWithRetry } from './write-file-with-retry.ts'; interface FileSystemCacheOptions { ns?: string; diff --git a/code/core/src/common/utils/formatter.test.ts b/code/core/src/common/utils/formatter.test.ts index c60ecb09b84f..4d03f2800631 100644 --- a/code/core/src/common/utils/formatter.test.ts +++ b/code/core/src/common/utils/formatter.test.ts @@ -2,7 +2,7 @@ import { resolve } from 'node:path'; import { describe, expect, it, vi } from 'vitest'; -import { formatFileContent } from './formatter'; +import { formatFileContent } from './formatter.ts'; const mockPrettier = vi.hoisted(() => ({ resolveConfig: vi.fn(), diff --git a/code/core/src/common/utils/get-addon-annotations.test.ts b/code/core/src/common/utils/get-addon-annotations.test.ts index b60f9f512947..75d61923fced 100644 --- a/code/core/src/common/utils/get-addon-annotations.test.ts +++ b/code/core/src/common/utils/get-addon-annotations.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { getAnnotationsName } from './get-addon-annotations'; +import { getAnnotationsName } from './get-addon-annotations.ts'; describe('getAnnotationsName', () => { it('should handle @storybook namespace and camel case conversion', () => { diff --git a/code/core/src/common/utils/get-addon-annotations.ts b/code/core/src/common/utils/get-addon-annotations.ts index 6586ae3d4e78..04e7f34c761e 100644 --- a/code/core/src/common/utils/get-addon-annotations.ts +++ b/code/core/src/common/utils/get-addon-annotations.ts @@ -1,6 +1,6 @@ import { createRequire } from 'node:module'; -import { isCorePackage } from './cli'; +import { isCorePackage } from './cli.ts'; /** * Get the name of the annotations object for a given addon. diff --git a/code/core/src/common/utils/get-addon-names.test.ts b/code/core/src/common/utils/get-addon-names.test.ts index 182426948dfe..d2e692bdc2eb 100644 --- a/code/core/src/common/utils/get-addon-names.test.ts +++ b/code/core/src/common/utils/get-addon-names.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { getAddonNames } from './get-addon-names'; +import { getAddonNames } from './get-addon-names.ts'; describe('getAddonNames', () => { it('should extract addon names from simple strings', () => { diff --git a/code/core/src/common/utils/get-addon-names.ts b/code/core/src/common/utils/get-addon-names.ts index 98d0f3b9ee1a..9c24ba4fcaee 100644 --- a/code/core/src/common/utils/get-addon-names.ts +++ b/code/core/src/common/utils/get-addon-names.ts @@ -1,6 +1,6 @@ import type { StorybookConfig } from 'storybook/internal/types'; -import { normalizePath } from './normalize-path'; +import { normalizePath } from './normalize-path.ts'; export const getAddonNames = (mainConfig: StorybookConfig): string[] => { const addons = mainConfig.addons || []; diff --git a/code/core/src/common/utils/get-framework-name.test.ts b/code/core/src/common/utils/get-framework-name.test.ts index 4d610d0f7ff7..cfb7d0ea1201 100644 --- a/code/core/src/common/utils/get-framework-name.test.ts +++ b/code/core/src/common/utils/get-framework-name.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { extractFrameworkPackageName } from './get-framework-name'; +import { extractFrameworkPackageName } from './get-framework-name.ts'; describe('get-framework-name', () => { describe('extractProperFrameworkName', () => { diff --git a/code/core/src/common/utils/get-framework-name.ts b/code/core/src/common/utils/get-framework-name.ts index 9c5af3a4a660..005975eff337 100644 --- a/code/core/src/common/utils/get-framework-name.ts +++ b/code/core/src/common/utils/get-framework-name.ts @@ -2,8 +2,8 @@ import type { Options } from 'storybook/internal/types'; import { dedent } from 'ts-dedent'; -import { frameworkPackages } from './get-storybook-info'; -import { normalizePath } from './normalize-path'; +import { frameworkPackages } from './get-storybook-info.ts'; +import { normalizePath } from './normalize-path.ts'; /** Framework can be a string or an object. This utility always returns the string name. */ export async function getFrameworkName(options: Options) { diff --git a/code/core/src/common/utils/get-renderer-name.test.ts b/code/core/src/common/utils/get-renderer-name.test.ts index 78b1f35b799e..64c31ed464ff 100644 --- a/code/core/src/common/utils/get-renderer-name.test.ts +++ b/code/core/src/common/utils/get-renderer-name.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest'; -import { extractRenderer } from './get-renderer-name'; +import { extractRenderer } from './get-renderer-name.ts'; describe('get-renderer-name', () => { describe('extractProperRendererNameFromFramework', () => { diff --git a/code/core/src/common/utils/get-renderer-name.ts b/code/core/src/common/utils/get-renderer-name.ts index af039d9d5abc..d2c4da6d9237 100644 --- a/code/core/src/common/utils/get-renderer-name.ts +++ b/code/core/src/common/utils/get-renderer-name.ts @@ -1,8 +1,8 @@ import type { Options } from 'storybook/internal/types'; -import { frameworkToRenderer } from './framework'; -import { extractFrameworkPackageName, getFrameworkName } from './get-framework-name'; -import { frameworkPackages } from './get-storybook-info'; +import { frameworkToRenderer } from './framework.ts'; +import { extractFrameworkPackageName, getFrameworkName } from './get-framework-name.ts'; +import { frameworkPackages } from './get-storybook-info.ts'; /** * Render is set as a string on core. It must be set by the framework It falls back to the framework diff --git a/code/core/src/common/utils/get-story-id.test.ts b/code/core/src/common/utils/get-story-id.test.ts index e2056119f155..fbd83bb436b6 100644 --- a/code/core/src/common/utils/get-story-id.test.ts +++ b/code/core/src/common/utils/get-story-id.test.ts @@ -2,7 +2,7 @@ import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; -import { getStoryId } from './get-story-id'; +import { getStoryId } from './get-story-id.ts'; describe('getStoryId', () => { it('should return the storyId', async () => { diff --git a/code/core/src/common/utils/get-story-id.ts b/code/core/src/common/utils/get-story-id.ts index cf8ac14944bf..2e0ae505d073 100644 --- a/code/core/src/common/utils/get-story-id.ts +++ b/code/core/src/common/utils/get-story-id.ts @@ -6,8 +6,8 @@ import type { Options, StoriesEntry } from 'storybook/internal/types'; import { dedent } from 'ts-dedent'; -import { userOrAutoTitleFromSpecifier } from '../../preview-api/modules/store/autoTitle'; -import { posix } from './posix'; +import { userOrAutoTitleFromSpecifier } from '../../preview-api/modules/store/autoTitle.ts'; +import { posix } from './posix.ts'; interface StoryIdData { storyFilePath: string; diff --git a/code/core/src/common/utils/get-storybook-configuration.test.ts b/code/core/src/common/utils/get-storybook-configuration.test.ts index 1bb377f79d94..72bc9b2b3a9d 100644 --- a/code/core/src/common/utils/get-storybook-configuration.test.ts +++ b/code/core/src/common/utils/get-storybook-configuration.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { getStorybookConfiguration } from './get-storybook-configuration'; +import { getStorybookConfiguration } from './get-storybook-configuration.ts'; describe('getStorybookConfiguration', () => { it('handles short names', () => { diff --git a/code/core/src/common/utils/get-storybook-info.ts b/code/core/src/common/utils/get-storybook-info.ts index 4fe99186987a..7b5c44c23189 100644 --- a/code/core/src/common/utils/get-storybook-info.ts +++ b/code/core/src/common/utils/get-storybook-info.ts @@ -11,13 +11,13 @@ import { SupportedBuilder, SupportedRenderer } from 'storybook/internal/types'; import invariant from 'tiny-invariant'; -import { JsPackageManager } from '../js-package-manager/JsPackageManager'; -import { frameworkToBuilder } from './framework'; -import { getAddonNames } from './get-addon-names'; -import { extractFrameworkPackageName } from './get-framework-name'; -import { extractRenderer } from './get-renderer-name'; -import { getStorybookConfiguration } from './get-storybook-configuration'; -import { loadMainConfig } from './load-main-config'; +import { JsPackageManager } from '../js-package-manager/JsPackageManager.ts'; +import { frameworkToBuilder } from './framework.ts'; +import { getAddonNames } from './get-addon-names.ts'; +import { extractFrameworkPackageName } from './get-framework-name.ts'; +import { extractRenderer } from './get-renderer-name.ts'; +import { getStorybookConfiguration } from './get-storybook-configuration.ts'; +import { loadMainConfig } from './load-main-config.ts'; export const rendererPackages: Record = { '@storybook/react': SupportedRenderer.REACT, diff --git a/code/core/src/common/utils/get-storybook-refs.test.ts b/code/core/src/common/utils/get-storybook-refs.test.ts index 9c1c4f540945..394d042f9df5 100644 --- a/code/core/src/common/utils/get-storybook-refs.test.ts +++ b/code/core/src/common/utils/get-storybook-refs.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { checkRef } from './get-storybook-refs'; +import { checkRef } from './get-storybook-refs.ts'; describe('checkRef', () => { afterEach(() => vi.restoreAllMocks()); diff --git a/code/core/src/common/utils/get-storybook-refs.ts b/code/core/src/common/utils/get-storybook-refs.ts index c6e1d10d1427..b12f8aabbd4b 100644 --- a/code/core/src/common/utils/get-storybook-refs.ts +++ b/code/core/src/common/utils/get-storybook-refs.ts @@ -7,7 +7,7 @@ import type { Options, Ref } from 'storybook/internal/types'; import * as pkg from 'empathic/package'; import * as resolve from 'empathic/resolve'; -import { getProjectRoot } from './paths'; +import { getProjectRoot } from './paths.ts'; export const getAutoRefs = async (options: Options): Promise> => { const location = pkg.up({ cwd: options.configDir, last: getProjectRoot() }); diff --git a/code/core/src/common/utils/interpret-require.ts b/code/core/src/common/utils/interpret-require.ts index 939486cdbb54..16aea2a55908 100644 --- a/code/core/src/common/utils/interpret-require.ts +++ b/code/core/src/common/utils/interpret-require.ts @@ -1,5 +1,5 @@ -import { importModule } from '../../shared/utils/module'; -import { getInterpretedFile } from './interpret-files'; +import { importModule } from '../../shared/utils/module.ts'; +import { getInterpretedFile } from './interpret-files.ts'; function getCandidate(paths: string[]) { for (let i = 0; i < paths.length; i += 1) { diff --git a/code/core/src/common/utils/load-main-config.ts b/code/core/src/common/utils/load-main-config.ts index ad77f991d146..c3e5c515dcab 100644 --- a/code/core/src/common/utils/load-main-config.ts +++ b/code/core/src/common/utils/load-main-config.ts @@ -7,9 +7,9 @@ import type { StorybookConfig } from 'storybook/internal/types'; import { dedent } from 'ts-dedent'; -import { importModule } from '../../shared/utils/module'; -import { getInterpretedFile } from './interpret-files'; -import { validateConfigurationFiles } from './validate-configuration-files'; +import { importModule } from '../../shared/utils/module.ts'; +import { getInterpretedFile } from './interpret-files.ts'; +import { validateConfigurationFiles } from './validate-configuration-files.ts'; export async function loadMainConfig({ configDir = '.storybook', diff --git a/code/core/src/common/utils/load-manager-or-addons-file.ts b/code/core/src/common/utils/load-manager-or-addons-file.ts index 0a53025e56f8..a7cd0084b227 100644 --- a/code/core/src/common/utils/load-manager-or-addons-file.ts +++ b/code/core/src/common/utils/load-manager-or-addons-file.ts @@ -4,7 +4,7 @@ import { logger } from 'storybook/internal/node-logger'; import { dedent } from 'ts-dedent'; -import { getInterpretedFile } from './interpret-files'; +import { getInterpretedFile } from './interpret-files.ts'; export function loadManagerOrAddonsFile({ configDir }: { configDir: string }) { const storybookCustomAddonsPath = getInterpretedFile(resolve(configDir, 'addons')); diff --git a/code/core/src/common/utils/load-preview-or-config-file.ts b/code/core/src/common/utils/load-preview-or-config-file.ts index bc6cadcfbb30..e475a1b2d409 100644 --- a/code/core/src/common/utils/load-preview-or-config-file.ts +++ b/code/core/src/common/utils/load-preview-or-config-file.ts @@ -2,7 +2,7 @@ import { resolve } from 'node:path'; import { dedent } from 'ts-dedent'; -import { getInterpretedFile } from './interpret-files'; +import { getInterpretedFile } from './interpret-files.ts'; export function loadPreviewOrConfigFile({ configDir }: { configDir: string }) { const storybookConfigPath = getInterpretedFile(resolve(configDir, 'config')); diff --git a/code/core/src/common/utils/normalize-path.test.ts b/code/core/src/common/utils/normalize-path.test.ts index 41c5b1dbbf34..aef82555916a 100644 --- a/code/core/src/common/utils/normalize-path.test.ts +++ b/code/core/src/common/utils/normalize-path.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { normalizePath } from './normalize-path'; +import { normalizePath } from './normalize-path.ts'; describe('normalize-path', () => { it('should normalize paths', () => { diff --git a/code/core/src/common/utils/normalize-stories.ts b/code/core/src/common/utils/normalize-stories.ts index 44db1e4ba9e4..2f8f6d9dcb73 100644 --- a/code/core/src/common/utils/normalize-stories.ts +++ b/code/core/src/common/utils/normalize-stories.ts @@ -8,8 +8,8 @@ import * as pico from 'picomatch'; // eslint-disable-next-line depend/ban-dependencies import slash from 'slash'; -import { globToRegexp } from './glob-to-regexp'; -import { normalizeStoryPath } from './paths'; +import { globToRegexp } from './glob-to-regexp.ts'; +import { normalizeStoryPath } from './paths.ts'; const DEFAULT_TITLE_PREFIX = ''; export const DEFAULT_FILES_PATTERN = '**/*.@(mdx|stories.@(js|jsx|mjs|ts|tsx))'; diff --git a/code/core/src/common/utils/paths.ts b/code/core/src/common/utils/paths.ts index d35ee86ceb2d..cc29853abff7 100644 --- a/code/core/src/common/utils/paths.ts +++ b/code/core/src/common/utils/paths.ts @@ -4,7 +4,7 @@ import * as find from 'empathic/find'; import * as walk from 'empathic/walk'; import { globSync } from 'tinyglobby'; -import { LOCK_FILES } from '../js-package-manager/constants'; +import { LOCK_FILES } from '../js-package-manager/constants.ts'; let projectRoot: string | undefined; diff --git a/code/core/src/common/utils/posix.test.ts b/code/core/src/common/utils/posix.test.ts index 960d799e30b1..b9c04220accf 100644 --- a/code/core/src/common/utils/posix.test.ts +++ b/code/core/src/common/utils/posix.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { posix } from './posix'; +import { posix } from './posix.ts'; describe('posix', () => { it('should replace backslashes with forward slashes', () => { diff --git a/code/core/src/common/utils/remove.ts b/code/core/src/common/utils/remove.ts index ea91f5bb7dd6..a402ee6334e0 100644 --- a/code/core/src/common/utils/remove.ts +++ b/code/core/src/common/utils/remove.ts @@ -3,8 +3,8 @@ import { logger } from 'storybook/internal/node-logger'; import { dedent } from 'ts-dedent'; -import type { JsPackageManager } from '../js-package-manager'; -import { getConfigInfo } from './get-storybook-info'; +import type { JsPackageManager } from '../js-package-manager/index.ts'; +import { getConfigInfo } from './get-storybook-info.ts'; export type RemoveAddonOptions = { packageManager: JsPackageManager; diff --git a/code/core/src/common/utils/resolve-path-in-sb-cache.test.ts b/code/core/src/common/utils/resolve-path-in-sb-cache.test.ts index d559817f50e1..06d2e8020fac 100644 --- a/code/core/src/common/utils/resolve-path-in-sb-cache.test.ts +++ b/code/core/src/common/utils/resolve-path-in-sb-cache.test.ts @@ -4,14 +4,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as pkg from 'empathic/package'; -import versions from '../versions'; -import { resolvePathInStorybookCache } from './resolve-path-in-sb-cache'; +import versions from '../versions.ts'; +import { resolvePathInStorybookCache } from './resolve-path-in-sb-cache.ts'; vi.mock('empathic/package', () => ({ cache: vi.fn(), })); -vi.mock('../versions', () => ({ +vi.mock('../versions.ts', () => ({ default: { storybook: '10.3.0-alpha.1', }, diff --git a/code/core/src/common/utils/resolve-path-in-sb-cache.ts b/code/core/src/common/utils/resolve-path-in-sb-cache.ts index 1a4fdfe51307..084a292c1b81 100644 --- a/code/core/src/common/utils/resolve-path-in-sb-cache.ts +++ b/code/core/src/common/utils/resolve-path-in-sb-cache.ts @@ -2,7 +2,7 @@ import { join } from 'node:path'; import * as pkg from 'empathic/package'; -import versions from '../versions'; +import versions from '../versions.ts'; /** * Get the path of the file or directory with input name inside the Storybook cache directory: diff --git a/code/core/src/common/utils/scan-and-transform-files.test.ts b/code/core/src/common/utils/scan-and-transform-files.test.ts index 7ce57f9a57fd..ba8afaef3ce1 100644 --- a/code/core/src/common/utils/scan-and-transform-files.test.ts +++ b/code/core/src/common/utils/scan-and-transform-files.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import * as paths from './paths'; -import { scanAndTransformFiles } from './scan-and-transform-files'; +import * as paths from './paths.ts'; +import { scanAndTransformFiles } from './scan-and-transform-files.ts'; // Mock dependencies const mocks = vi.hoisted(() => { diff --git a/code/core/src/common/utils/scan-and-transform-files.ts b/code/core/src/common/utils/scan-and-transform-files.ts index 2d9568dd3b12..7daa3c99c9bc 100644 --- a/code/core/src/common/utils/scan-and-transform-files.ts +++ b/code/core/src/common/utils/scan-and-transform-files.ts @@ -1,7 +1,7 @@ import { logger, prompt } from 'storybook/internal/node-logger'; -import { commonGlobOptions } from './common-glob-options'; -import { getProjectRoot } from './paths'; +import { commonGlobOptions } from './common-glob-options.ts'; +import { getProjectRoot } from './paths.ts'; /** * Helper function to scan for files matching a glob pattern and transform them diff --git a/code/core/src/common/utils/setup-addon-in-config.test.ts b/code/core/src/common/utils/setup-addon-in-config.test.ts index 484994425caf..eb99550c0b69 100644 --- a/code/core/src/common/utils/setup-addon-in-config.test.ts +++ b/code/core/src/common/utils/setup-addon-in-config.test.ts @@ -4,10 +4,10 @@ import type { ConfigFile } from 'storybook/internal/csf-tools'; import * as csfTools from 'storybook/internal/csf-tools'; import type { StorybookConfigRaw } from 'storybook/internal/types'; -import * as loadMainConfigModule from './load-main-config'; -import { setupAddonInConfig } from './setup-addon-in-config'; -import * as syncModule from './sync-main-preview-addons'; -import * as wrapUtils from './wrap-getAbsolutePath-utils'; +import * as loadMainConfigModule from './load-main-config.ts'; +import { setupAddonInConfig } from './setup-addon-in-config.ts'; +import * as syncModule from './sync-main-preview-addons.ts'; +import * as wrapUtils from './wrap-getAbsolutePath-utils.ts'; vi.mock('storybook/internal/csf-tools', { spy: true }); vi.mock('./sync-main-preview-addons', { spy: true }); diff --git a/code/core/src/common/utils/setup-addon-in-config.ts b/code/core/src/common/utils/setup-addon-in-config.ts index e234806c1055..5f74b6180c38 100644 --- a/code/core/src/common/utils/setup-addon-in-config.ts +++ b/code/core/src/common/utils/setup-addon-in-config.ts @@ -1,12 +1,12 @@ import type { ConfigFile } from 'storybook/internal/csf-tools'; import { writeConfig } from 'storybook/internal/csf-tools'; -import { loadMainConfig } from './load-main-config'; -import { syncStorybookAddons } from './sync-main-preview-addons'; +import { loadMainConfig } from './load-main-config.ts'; +import { syncStorybookAddons } from './sync-main-preview-addons.ts'; import { getAbsolutePathWrapperName, wrapValueWithGetAbsolutePathWrapper, -} from './wrap-getAbsolutePath-utils'; +} from './wrap-getAbsolutePath-utils.ts'; export interface SetupAddonInConfigOptions { addonName: string; diff --git a/code/core/src/common/utils/sync-main-preview-addons.test.ts b/code/core/src/common/utils/sync-main-preview-addons.test.ts index 0d8651c7f6bd..2dfbc3682772 100644 --- a/code/core/src/common/utils/sync-main-preview-addons.test.ts +++ b/code/core/src/common/utils/sync-main-preview-addons.test.ts @@ -6,8 +6,8 @@ import type { StorybookConfigRaw } from 'storybook/internal/types'; import { dedent } from 'ts-dedent'; -import { getAddonAnnotations } from './get-addon-annotations'; -import { syncPreviewAddonsWithMainConfig } from './sync-main-preview-addons'; +import { getAddonAnnotations } from './get-addon-annotations.ts'; +import { syncPreviewAddonsWithMainConfig } from './sync-main-preview-addons.ts'; vi.mock('./get-addon-annotations'); diff --git a/code/core/src/common/utils/sync-main-preview-addons.ts b/code/core/src/common/utils/sync-main-preview-addons.ts index a06ab24251c0..bf8cbb641292 100644 --- a/code/core/src/common/utils/sync-main-preview-addons.ts +++ b/code/core/src/common/utils/sync-main-preview-addons.ts @@ -10,8 +10,8 @@ import type { StorybookConfig } from 'storybook/internal/types'; import picocolors from 'picocolors'; -import { getAddonAnnotations } from './get-addon-annotations'; -import { getAddonNames } from './get-addon-names'; +import { getAddonAnnotations } from './get-addon-annotations.ts'; +import { getAddonNames } from './get-addon-names.ts'; export async function syncStorybookAddons( mainConfig: StorybookConfig, diff --git a/code/core/src/common/utils/template.ts b/code/core/src/common/utils/template.ts index ecd5f23027b2..73117871a5ea 100644 --- a/code/core/src/common/utils/template.ts +++ b/code/core/src/common/utils/template.ts @@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'node:fs'; import { join, resolve } from 'pathe'; -import { resolvePackageDir } from '../../shared/utils/module'; +import { resolvePackageDir } from '../../shared/utils/module.ts'; const interpolate = (string: string, data: Record = {}) => Object.entries(data).reduce((acc, [k, v]) => acc.replace(new RegExp(`%${k}%`, 'g'), v), string); diff --git a/code/core/src/common/utils/transform-imports.test.ts b/code/core/src/common/utils/transform-imports.test.ts index e57aab7ea0a4..5e1dd9c2cd3f 100644 --- a/code/core/src/common/utils/transform-imports.test.ts +++ b/code/core/src/common/utils/transform-imports.test.ts @@ -5,7 +5,7 @@ import { describe, expect, it, vi } from 'vitest'; import { dedent } from 'ts-dedent'; -import { transformImportFiles } from './transform-imports'; +import { transformImportFiles } from './transform-imports.ts'; const consolidatedPackages = { '@storybook/core-common': 'storybook/internal/common', diff --git a/code/core/src/common/utils/utils.test.ts b/code/core/src/common/utils/utils.test.ts new file mode 100644 index 000000000000..09833eb4bcb6 --- /dev/null +++ b/code/core/src/common/utils/utils.test.ts @@ -0,0 +1,225 @@ +import { describe, expect, it } from 'vitest'; + +import { groupBy, invariant } from './utils.ts'; + +describe('utils', () => { + describe('groupBy', () => { + it('should group items by a key selector', () => { + const items = [ + { type: 'fruit', name: 'apple' }, + { type: 'vegetable', name: 'carrot' }, + { type: 'fruit', name: 'banana' }, + { type: 'vegetable', name: 'broccoli' }, + ]; + + const result = groupBy(items, (item) => item.type); + + expect(result).toEqual({ + fruit: [ + { type: 'fruit', name: 'apple' }, + { type: 'fruit', name: 'banana' }, + ], + vegetable: [ + { type: 'vegetable', name: 'carrot' }, + { type: 'vegetable', name: 'broccoli' }, + ], + }); + }); + + it('should handle empty arrays', () => { + const result = groupBy([], (item) => item); + + expect(result).toEqual({}); + }); + + it('should handle single item', () => { + const items = [{ type: 'fruit', name: 'apple' }]; + + const result = groupBy(items, (item) => item.type); + + expect(result).toEqual({ + fruit: [{ type: 'fruit', name: 'apple' }], + }); + }); + + it('should pass index to key selector', () => { + const items = ['a', 'b', 'c', 'd']; + const indices: number[] = []; + + groupBy(items, (_item, index) => { + indices.push(index); + return index % 2 === 0 ? 'even' : 'odd'; + }); + + expect(indices).toEqual([0, 1, 2, 3]); + }); + + it('should group by index when key selector uses index', () => { + const items = ['a', 'b', 'c', 'd']; + + const result = groupBy(items, (_item, index) => (index % 2 === 0 ? 'even' : 'odd')); + + expect(result).toEqual({ + even: ['a', 'c'], + odd: ['b', 'd'], + }); + }); + + it('should handle numeric keys', () => { + const items = [{ id: 1 }, { id: 2 }, { id: 1 }]; + + const result = groupBy(items, (item) => item.id); + + expect(result).toEqual({ + 1: [{ id: 1 }, { id: 1 }], + 2: [{ id: 2 }], + }); + }); + + it('should handle symbol keys', () => { + const key1 = Symbol('key1'); + const key2 = Symbol('key2'); + const items = [ + { key: key1, value: 'a' }, + { key: key2, value: 'b' }, + { key: key1, value: 'c' }, + ]; + + const result = groupBy(items, (item) => item.key); + + expect(result[key1]).toEqual([ + { key: key1, value: 'a' }, + { key: key1, value: 'c' }, + ]); + expect(result[key2]).toEqual([{ key: key2, value: 'b' }]); + }); + + it('should handle all items with same key', () => { + const items = ['a', 'b', 'c']; + + const result = groupBy(items, () => 'same'); + + expect(result).toEqual({ + same: ['a', 'b', 'c'], + }); + }); + }); + + describe('invariant', () => { + it('should not throw when condition is truthy', () => { + expect(() => { + invariant(true, 'Error message'); + }).not.toThrow(); + + expect(() => { + invariant(1, 'Error message'); + }).not.toThrow(); + + expect(() => { + invariant('non-empty', 'Error message'); + }).not.toThrow(); + + expect(() => { + invariant({}, 'Error message'); + }).not.toThrow(); + + expect(() => { + invariant([], 'Error message'); + }).not.toThrow(); + }); + + it('should throw when condition is falsy', () => { + expect(() => { + invariant(false, 'Error message'); + }).toThrow('Error message'); + + expect(() => { + invariant(0, 'Zero is falsy'); + }).toThrow('Zero is falsy'); + + expect(() => { + invariant('', 'Empty string is falsy'); + }).toThrow('Empty string is falsy'); + + expect(() => { + invariant(null, 'Null is falsy'); + }).toThrow('Null is falsy'); + + expect(() => { + invariant(undefined, 'Undefined is falsy'); + }).toThrow('Undefined is falsy'); + }); + + it('should throw with default message when no message provided', () => { + expect(() => { + invariant(false); + }).toThrow('Invariant failed'); + }); + + it('should support lazy message evaluation with function', () => { + let messageEvaluated = false; + + // Should not evaluate message when condition is true + invariant(true, () => { + messageEvaluated = true; + return 'This should not be evaluated'; + }); + + expect(messageEvaluated).toBe(false); + }); + + it('should evaluate lazy message when condition is false', () => { + let messageEvaluated = false; + + expect(() => { + invariant(false, () => { + messageEvaluated = true; + return 'Lazy evaluated message'; + }); + }).toThrow('Lazy evaluated message'); + + expect(messageEvaluated).toBe(true); + }); + + it('should allow expensive message computation only when needed', () => { + const expensiveComputation = () => { + return Array.from({ length: 1000 }) + .map((_, i) => `Item ${i}`) + .join(', '); + }; + + // This should be fast because the message is not computed + expect(() => { + invariant(true, expensiveComputation); + }).not.toThrow(); + + // This should compute the message + expect(() => { + invariant(false, expensiveComputation); + }).toThrow(/Item 0/); + }); + + it('should handle complex conditions', () => { + const obj = { value: 42 }; + + expect(() => { + invariant(obj.value > 0, 'Value must be positive'); + }).not.toThrow(); + + expect(() => { + invariant(obj.value < 0, 'Value must be negative'); + }).toThrow('Value must be negative'); + }); + + it('should narrow types with type assertion', () => { + function processValue(value: string | null): string { + invariant(value !== null, 'Value must not be null'); + // TypeScript should now know that value is string, not string | null + return value.toUpperCase(); + } + + expect(processValue('test')).toBe('TEST'); + expect(() => processValue(null)).toThrow('Value must not be null'); + }); + }); +}); diff --git a/code/core/src/common/utils/validate-config.test.ts b/code/core/src/common/utils/validate-config.test.ts index e478c2db0afc..60be637b998c 100644 --- a/code/core/src/common/utils/validate-config.test.ts +++ b/code/core/src/common/utils/validate-config.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { resolveModulePath } from 'exsolve'; -import { validateFrameworkName } from './validate-config'; +import { validateFrameworkName } from './validate-config.ts'; // mock exsolve to spy vi.mock('exsolve', { spy: true }); diff --git a/code/core/src/common/utils/validate-config.ts b/code/core/src/common/utils/validate-config.ts index a0b401bf1133..2aabdda604fd 100644 --- a/code/core/src/common/utils/validate-config.ts +++ b/code/core/src/common/utils/validate-config.ts @@ -6,8 +6,8 @@ import { import { resolveModulePath } from 'exsolve'; -import { extractFrameworkPackageName } from '..'; -import { frameworkPackages } from './get-storybook-info'; +import { extractFrameworkPackageName } from '../index.ts'; +import { frameworkPackages } from './get-storybook-info.ts'; const renderers = ['html', 'preact', 'react', 'server', 'svelte', 'vue', 'vue3', 'web-components']; diff --git a/code/core/src/common/utils/validate-configuration-files.ts b/code/core/src/common/utils/validate-configuration-files.ts index 2d7b58aada10..1d1707326a99 100644 --- a/code/core/src/common/utils/validate-configuration-files.ts +++ b/code/core/src/common/utils/validate-configuration-files.ts @@ -9,7 +9,7 @@ import { glob } from 'glob'; import slash from 'slash'; import { dedent } from 'ts-dedent'; -import { supportedExtensions } from './interpret-files'; +import { supportedExtensions } from './interpret-files.ts'; export async function validateConfigurationFiles(configDir: string, cwd?: string) { const extensionsPattern = `{${Array.from(supportedExtensions).join(',')}}`; diff --git a/code/core/src/common/utils/write-file-with-retry.test.ts b/code/core/src/common/utils/write-file-with-retry.test.ts index 0fabcde2764c..eb81e3bb9960 100644 --- a/code/core/src/common/utils/write-file-with-retry.test.ts +++ b/code/core/src/common/utils/write-file-with-retry.test.ts @@ -2,7 +2,7 @@ import fs from 'node:fs/promises'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { writeFileWithRetry } from './write-file-with-retry'; +import { writeFileWithRetry } from './write-file-with-retry.ts'; vi.mock('node:fs/promises'); diff --git a/code/core/src/common/versions.ts b/code/core/src/common/versions.ts index f68e363561f1..462c852dbaa0 100644 --- a/code/core/src/common/versions.ts +++ b/code/core/src/common/versions.ts @@ -1,45 +1,45 @@ // auto generated file, do not edit export default { - '@storybook/addon-a11y': '10.4.0-alpha.3', - '@storybook/addon-docs': '10.4.0-alpha.3', - '@storybook/addon-links': '10.4.0-alpha.3', - '@storybook/addon-onboarding': '10.4.0-alpha.3', - 'storybook-addon-pseudo-states': '10.4.0-alpha.3', - '@storybook/addon-themes': '10.4.0-alpha.3', - '@storybook/addon-vitest': '10.4.0-alpha.3', - '@storybook/builder-vite': '10.4.0-alpha.3', - '@storybook/builder-webpack5': '10.4.0-alpha.3', - storybook: '10.4.0-alpha.3', - '@storybook/angular': '10.4.0-alpha.3', - '@storybook/ember': '10.4.0-alpha.3', - '@storybook/html-vite': '10.4.0-alpha.3', - '@storybook/nextjs': '10.4.0-alpha.3', - '@storybook/nextjs-vite': '10.4.0-alpha.3', - '@storybook/preact-vite': '10.4.0-alpha.3', - '@storybook/react-native-web-vite': '10.4.0-alpha.3', - '@storybook/react-vite': '10.4.0-alpha.3', - '@storybook/react-webpack5': '10.4.0-alpha.3', - '@storybook/server-webpack5': '10.4.0-alpha.3', - '@storybook/svelte-vite': '10.4.0-alpha.3', - '@storybook/sveltekit': '10.4.0-alpha.3', - '@storybook/vue3-vite': '10.4.0-alpha.3', - '@storybook/web-components-vite': '10.4.0-alpha.3', - sb: '10.4.0-alpha.3', - '@storybook/cli': '10.4.0-alpha.3', - '@storybook/codemod': '10.4.0-alpha.3', - '@storybook/core-webpack': '10.4.0-alpha.3', - 'create-storybook': '10.4.0-alpha.3', - '@storybook/csf-plugin': '10.4.0-alpha.3', - 'eslint-plugin-storybook': '10.4.0-alpha.3', - '@storybook/react-dom-shim': '10.4.0-alpha.3', - '@storybook/preset-create-react-app': '10.4.0-alpha.3', - '@storybook/preset-react-webpack': '10.4.0-alpha.3', - '@storybook/preset-server-webpack': '10.4.0-alpha.3', - '@storybook/html': '10.4.0-alpha.3', - '@storybook/preact': '10.4.0-alpha.3', - '@storybook/react': '10.4.0-alpha.3', - '@storybook/server': '10.4.0-alpha.3', - '@storybook/svelte': '10.4.0-alpha.3', - '@storybook/vue3': '10.4.0-alpha.3', - '@storybook/web-components': '10.4.0-alpha.3', + '@storybook/addon-a11y': '10.4.0-alpha.7', + '@storybook/addon-docs': '10.4.0-alpha.7', + '@storybook/addon-links': '10.4.0-alpha.7', + '@storybook/addon-onboarding': '10.4.0-alpha.7', + 'storybook-addon-pseudo-states': '10.4.0-alpha.7', + '@storybook/addon-themes': '10.4.0-alpha.7', + '@storybook/addon-vitest': '10.4.0-alpha.7', + '@storybook/builder-vite': '10.4.0-alpha.7', + '@storybook/builder-webpack5': '10.4.0-alpha.7', + storybook: '10.4.0-alpha.7', + '@storybook/angular': '10.4.0-alpha.7', + '@storybook/ember': '10.4.0-alpha.7', + '@storybook/html-vite': '10.4.0-alpha.7', + '@storybook/nextjs': '10.4.0-alpha.7', + '@storybook/nextjs-vite': '10.4.0-alpha.7', + '@storybook/preact-vite': '10.4.0-alpha.7', + '@storybook/react-native-web-vite': '10.4.0-alpha.7', + '@storybook/react-vite': '10.4.0-alpha.7', + '@storybook/react-webpack5': '10.4.0-alpha.7', + '@storybook/server-webpack5': '10.4.0-alpha.7', + '@storybook/svelte-vite': '10.4.0-alpha.7', + '@storybook/sveltekit': '10.4.0-alpha.7', + '@storybook/vue3-vite': '10.4.0-alpha.7', + '@storybook/web-components-vite': '10.4.0-alpha.7', + sb: '10.4.0-alpha.7', + '@storybook/cli': '10.4.0-alpha.7', + '@storybook/codemod': '10.4.0-alpha.7', + '@storybook/core-webpack': '10.4.0-alpha.7', + 'create-storybook': '10.4.0-alpha.7', + '@storybook/csf-plugin': '10.4.0-alpha.7', + 'eslint-plugin-storybook': '10.4.0-alpha.7', + '@storybook/react-dom-shim': '10.4.0-alpha.7', + '@storybook/preset-create-react-app': '10.4.0-alpha.7', + '@storybook/preset-react-webpack': '10.4.0-alpha.7', + '@storybook/preset-server-webpack': '10.4.0-alpha.7', + '@storybook/html': '10.4.0-alpha.7', + '@storybook/preact': '10.4.0-alpha.7', + '@storybook/react': '10.4.0-alpha.7', + '@storybook/server': '10.4.0-alpha.7', + '@storybook/svelte': '10.4.0-alpha.7', + '@storybook/vue3': '10.4.0-alpha.7', + '@storybook/web-components': '10.4.0-alpha.7', }; diff --git a/code/core/src/component-testing/components/DetachedDebuggerMessage.tsx b/code/core/src/component-testing/components/DetachedDebuggerMessage.tsx index 98fb813c674f..6f63702f1056 100644 --- a/code/core/src/component-testing/components/DetachedDebuggerMessage.tsx +++ b/code/core/src/component-testing/components/DetachedDebuggerMessage.tsx @@ -4,7 +4,7 @@ import { Link } from 'storybook/internal/components'; import { styled } from 'storybook/theming'; -import { PANEL_ID } from '../constants'; +import { PANEL_ID } from '../constants.ts'; const Wrapper = styled.div(({ theme: { color, typography, background } }) => ({ textAlign: 'start', diff --git a/code/core/src/component-testing/components/EmptyState.tsx b/code/core/src/component-testing/components/EmptyState.tsx index 65402c9288d4..e4bba8df5175 100644 --- a/code/core/src/component-testing/components/EmptyState.tsx +++ b/code/core/src/component-testing/components/EmptyState.tsx @@ -7,7 +7,7 @@ import { DocumentIcon } from '@storybook/icons'; import { useStorybookApi } from 'storybook/manager-api'; import { styled } from 'storybook/theming'; -import { DOCUMENTATION_PLAY_FUNCTION_LINK } from '../constants'; +import { DOCUMENTATION_PLAY_FUNCTION_LINK } from '../constants.ts'; const Links = styled.div(({ theme }) => ({ display: 'flex', diff --git a/code/core/src/component-testing/components/Interaction.stories.tsx b/code/core/src/component-testing/components/Interaction.stories.tsx index 985acaa97a8e..32894427c162 100644 --- a/code/core/src/component-testing/components/Interaction.stories.tsx +++ b/code/core/src/component-testing/components/Interaction.stories.tsx @@ -2,10 +2,10 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, userEvent, within } from 'storybook/test'; -import { CallStates } from '../../instrumenter/types'; -import { getCalls } from '../mocks'; -import { Interaction } from './Interaction'; -import ToolbarStories from './Toolbar.stories'; +import { CallStates } from '../../instrumenter/types.ts'; +import { getCalls } from '../mocks/index.ts'; +import { Interaction } from './Interaction.tsx'; +import ToolbarStories from './Toolbar.stories.tsx'; type Story = StoryObj; diff --git a/code/core/src/component-testing/components/Interaction.tsx b/code/core/src/component-testing/components/Interaction.tsx index 34514a6bf9eb..36b1bf91634b 100644 --- a/code/core/src/component-testing/components/Interaction.tsx +++ b/code/core/src/component-testing/components/Interaction.tsx @@ -7,13 +7,13 @@ import { ChevronDownIcon, ChevronUpIcon } from '@storybook/icons'; import { transparentize } from 'polished'; import { styled, typography } from 'storybook/theming'; -import { type Call, CallStates, type ControlStates } from '../../instrumenter/types'; -import { INTERNAL_RENDER_CALL_ID } from '../constants'; -import { isChaiError, isJestError, useAnsiToHtmlFilter } from '../utils'; -import type { Controls } from './InteractionsPanel'; -import { MatcherResult } from './MatcherResult'; -import { MethodCall } from './MethodCall'; -import { StatusIcon } from './StatusIcon'; +import { type Call, CallStates, type ControlStates } from '../../instrumenter/types.ts'; +import { INTERNAL_RENDER_CALL_ID } from '../constants.ts'; +import { isChaiError, isJestError, useAnsiToHtmlFilter } from '../utils.ts'; +import type { Controls } from './InteractionsPanel.tsx'; +import { MatcherResult } from './MatcherResult.tsx'; +import { MethodCall } from './MethodCall.tsx'; +import { StatusIcon } from './StatusIcon.tsx'; const MethodCallWrapper = styled.div({ fontFamily: typography.fonts.mono, diff --git a/code/core/src/component-testing/components/InteractionsPanel.stories.tsx b/code/core/src/component-testing/components/InteractionsPanel.stories.tsx index f779fd5b3030..c91899488e18 100644 --- a/code/core/src/component-testing/components/InteractionsPanel.stories.tsx +++ b/code/core/src/component-testing/components/InteractionsPanel.stories.tsx @@ -6,11 +6,11 @@ import { ManagerContext } from 'storybook/manager-api'; import { expect, fn, userEvent, waitFor, within } from 'storybook/test'; import { styled } from 'storybook/theming'; -import { isChromatic } from '../../../../.storybook/isChromatic'; -import { CallStates } from '../../instrumenter/types'; -import { getCalls, getInteractions } from '../mocks'; -import { InteractionsPanel } from './InteractionsPanel'; -import ToolbarStories from './Toolbar.stories'; +import { isChromatic } from '../../../../.storybook/isChromatic.ts'; +import { CallStates } from '../../instrumenter/types.ts'; +import { getCalls, getInteractions } from '../mocks/index.ts'; +import { InteractionsPanel } from './InteractionsPanel.tsx'; +import ToolbarStories from './Toolbar.stories.tsx'; const StyledWrapper = styled.div(({ theme }) => ({ backgroundColor: theme.background.content, diff --git a/code/core/src/component-testing/components/InteractionsPanel.tsx b/code/core/src/component-testing/components/InteractionsPanel.tsx index d9baa4ff2013..6042aaeea567 100644 --- a/code/core/src/component-testing/components/InteractionsPanel.tsx +++ b/code/core/src/component-testing/components/InteractionsPanel.tsx @@ -4,15 +4,15 @@ import { transparentize } from 'polished'; import type { API } from 'storybook/manager-api'; import { styled } from 'storybook/theming'; -import { type Call, type CallStates, type ControlStates } from '../../instrumenter/types'; -import { INTERNAL_RENDER_CALL_ID } from '../constants'; -import { isTestAssertionError, useAnsiToHtmlFilter } from '../utils'; -import { DetachedDebuggerMessage } from './DetachedDebuggerMessage'; -import { Empty } from './EmptyState'; -import { Interaction } from './Interaction'; -import type { PlayStatus } from './StatusBadge'; -import { TestDiscrepancyMessage } from './TestDiscrepancyMessage'; -import { Toolbar } from './Toolbar'; +import { type Call, type CallStates, type ControlStates } from '../../instrumenter/types.ts'; +import { INTERNAL_RENDER_CALL_ID } from '../constants.ts'; +import { isTestAssertionError, useAnsiToHtmlFilter } from '../utils.ts'; +import { DetachedDebuggerMessage } from './DetachedDebuggerMessage.tsx'; +import { Empty } from './EmptyState.tsx'; +import { Interaction } from './Interaction.tsx'; +import type { PlayStatus } from './StatusBadge.tsx'; +import { TestDiscrepancyMessage } from './TestDiscrepancyMessage.tsx'; +import { Toolbar } from './Toolbar.tsx'; export interface Controls { start: (args?: any) => void; diff --git a/code/core/src/component-testing/components/MatcherResult.stories.tsx b/code/core/src/component-testing/components/MatcherResult.stories.tsx index f8ec78e970aa..d19350728112 100644 --- a/code/core/src/component-testing/components/MatcherResult.stories.tsx +++ b/code/core/src/component-testing/components/MatcherResult.stories.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { styled } from 'storybook/theming'; import { dedent } from 'ts-dedent'; -import { MatcherResult } from './MatcherResult'; +import { MatcherResult } from './MatcherResult.tsx'; const StyledWrapper = styled.div(({ theme }) => ({ backgroundColor: theme.background.content, diff --git a/code/core/src/component-testing/components/MatcherResult.tsx b/code/core/src/component-testing/components/MatcherResult.tsx index d2888448eb77..204cbc78630e 100644 --- a/code/core/src/component-testing/components/MatcherResult.tsx +++ b/code/core/src/component-testing/components/MatcherResult.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { styled, typography } from 'storybook/theming'; -import { useAnsiToHtmlFilter } from '../utils'; -import { Node } from './MethodCall'; +import { useAnsiToHtmlFilter } from '../utils.ts'; +import { Node } from './MethodCall.tsx'; const getParams = (line: string, fromIndex = 0): string => { for (let i = fromIndex, depth = 1; i < line.length; i += 1) { diff --git a/code/core/src/component-testing/components/MethodCall.stories.tsx b/code/core/src/component-testing/components/MethodCall.stories.tsx index ea9b78d3735a..680649e7d337 100644 --- a/code/core/src/component-testing/components/MethodCall.stories.tsx +++ b/code/core/src/component-testing/components/MethodCall.stories.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { styled, typography } from 'storybook/theming'; -import type { Call } from '../../instrumenter/types'; -import { MethodCall, Node } from './MethodCall'; +import type { Call } from '../../instrumenter/types.ts'; +import { MethodCall, Node } from './MethodCall.tsx'; const StyledWrapper = styled.div(({ theme }) => ({ backgroundColor: theme.background.content, diff --git a/code/core/src/component-testing/components/MethodCall.tsx b/code/core/src/component-testing/components/MethodCall.tsx index cb4fc43a87c2..46a194c6e520 100644 --- a/code/core/src/component-testing/components/MethodCall.tsx +++ b/code/core/src/component-testing/components/MethodCall.tsx @@ -6,7 +6,7 @@ import { logger } from 'storybook/internal/client-logger'; import { ObjectInspector } from '@devtools-ds/object-inspector'; import { useTheme } from 'storybook/theming'; -import type { Call, CallRef, ElementRef } from '../../instrumenter/types'; +import type { Call, CallRef, ElementRef } from '../../instrumenter/types.ts'; const colorsLight = { base: '#444', diff --git a/code/core/src/component-testing/components/Panel.test.ts b/code/core/src/component-testing/components/Panel.test.ts index bcf177b349ab..9c98da0cde8f 100644 --- a/code/core/src/component-testing/components/Panel.test.ts +++ b/code/core/src/component-testing/components/Panel.test.ts @@ -1,9 +1,9 @@ // @vitest-environment happy-dom import { describe, expect, it } from 'vitest'; -import { type Call, CallStates, type LogItem } from '../../instrumenter/types'; -import { INTERNAL_RENDER_CALL_ID } from '../constants'; -import { type PanelState, getInteractions, getPanelState } from './Panel'; +import { type Call, CallStates, type LogItem } from '../../instrumenter/types.ts'; +import { INTERNAL_RENDER_CALL_ID } from '../constants.ts'; +import { type PanelState, getInteractions, getPanelState } from './Panel.tsx'; describe('Panel', () => { const log: LogItem[] = [ @@ -259,6 +259,101 @@ describe('Panel', () => { ]); }); + it('does not hide waitFor after a collapsed step', () => { + const stepLog: LogItem[] = [ + { callId: 'story--id [0] step', status: CallStates.DONE, ancestors: [] }, + { + callId: 'story--id [0] step [0] click', + status: CallStates.DONE, + ancestors: ['story--id [0] step'], + }, + { callId: 'story--id [1] waitFor', status: CallStates.DONE, ancestors: [] }, + { + callId: 'story--id [1] waitFor [0] toHaveBeenCalledWith', + status: CallStates.DONE, + ancestors: ['story--id [1] waitFor'], + }, + ]; + const stepCalls = new Map( + [ + { + id: 'story--id [0] step', + storyId: 'story--id', + ancestors: [], + cursor: 0, + path: [], + method: 'step', + args: ['Fill and submit form'], + interceptable: true, + retain: false, + }, + { + id: 'story--id [0] step [0] click', + storyId: 'story--id', + ancestors: ['story--id [0] step'], + cursor: 0, + path: ['userEvent'], + method: 'click', + args: [], + interceptable: true, + retain: false, + }, + { + id: 'story--id [1] waitFor', + storyId: 'story--id', + ancestors: [], + cursor: 1, + path: [], + method: 'waitFor', + args: [], + interceptable: true, + retain: false, + }, + { + id: 'story--id [1] waitFor [0] toHaveBeenCalledWith', + storyId: 'story--id', + ancestors: ['story--id [1] waitFor'], + cursor: 0, + path: [], + method: 'toHaveBeenCalledWith', + args: [], + interceptable: true, + retain: false, + }, + ].map((v) => [v.id, v]) + ); + + const stepCollapsed = new Set(['story--id [0] step']); + + const result = getInteractions({ + log: stepLog, + calls: stepCalls, + collapsed: stepCollapsed, + setCollapsed, + }); + + // Step's child should be hidden + expect(result).toEqual([ + expect.objectContaining({ id: 'story--id [0] step', isHidden: false, isCollapsed: true }), + expect.objectContaining({ + id: 'story--id [0] step [0] click', + isHidden: true, + isCollapsed: false, + }), + // waitFor and its children should NOT be hidden by the collapsed step + expect.objectContaining({ + id: 'story--id [1] waitFor', + isHidden: false, + isCollapsed: false, + }), + expect.objectContaining({ + id: 'story--id [1] waitFor [0] toHaveBeenCalledWith', + isHidden: false, + isCollapsed: false, + }), + ]); + }); + it('uses status from log', () => { const withError = log.slice(0, 4).concat({ ...log[4], status: CallStates.ERROR }); diff --git a/code/core/src/component-testing/components/Panel.tsx b/code/core/src/component-testing/components/Panel.tsx index ced73de7b8f3..583e49c0bfbc 100644 --- a/code/core/src/component-testing/components/Panel.tsx +++ b/code/core/src/component-testing/components/Panel.tsx @@ -24,18 +24,18 @@ import { import { STATUS_TYPE_ID_COMPONENT_TEST, STORYBOOK_ADDON_TEST_CHANNEL, -} from '../../../../addons/vitest/src/constants'; -import { EVENTS } from '../../instrumenter/EVENTS'; +} from '../../../../addons/vitest/src/constants.ts'; +import { EVENTS } from '../../instrumenter/EVENTS.ts'; import { type Call, CallStates, type ControlStates, type LogItem, type RenderPhase, -} from '../../instrumenter/types'; -import { ADDON_ID, INTERNAL_RENDER_CALL_ID } from '../constants'; -import { InteractionsPanel, type SerializedError } from './InteractionsPanel'; -import type { PlayStatus } from './StatusBadge'; +} from '../../instrumenter/types.ts'; +import { ADDON_ID, INTERNAL_RENDER_CALL_ID } from '../constants.ts'; +import { InteractionsPanel, type SerializedError } from './InteractionsPanel.tsx'; +import type { PlayStatus } from './StatusBadge.tsx'; export interface PanelState { status: PlayStatus; diff --git a/code/core/src/component-testing/components/PanelTitle.tsx b/code/core/src/component-testing/components/PanelTitle.tsx index ac035d3a3032..4be41f4a6372 100644 --- a/code/core/src/component-testing/components/PanelTitle.tsx +++ b/code/core/src/component-testing/components/PanelTitle.tsx @@ -4,10 +4,10 @@ import { Badge } from 'storybook/internal/components'; import { useAddonState, useStorybookApi } from 'storybook/manager-api'; -import { CallStates } from '../../instrumenter/types'; -import { ADDON_ID, PANEL_ID } from '../constants'; -import type { PanelState } from './Panel'; -import { StatusIcon } from './StatusIcon'; +import { CallStates } from '../../instrumenter/types.ts'; +import { ADDON_ID, PANEL_ID } from '../constants.ts'; +import type { PanelState } from './Panel.tsx'; +import { StatusIcon } from './StatusIcon.tsx'; export function PanelTitle() { const api = useStorybookApi(); diff --git a/code/core/src/component-testing/components/StatusBadge.stories.tsx b/code/core/src/component-testing/components/StatusBadge.stories.tsx index b52a0b49a31b..7954cb6c8453 100644 --- a/code/core/src/component-testing/components/StatusBadge.stories.tsx +++ b/code/core/src/component-testing/components/StatusBadge.stories.tsx @@ -1,4 +1,4 @@ -import { StatusBadge } from './StatusBadge'; +import { StatusBadge } from './StatusBadge.tsx'; export default { title: 'StatusBadge', diff --git a/code/core/src/component-testing/components/StatusIcon.stories.tsx b/code/core/src/component-testing/components/StatusIcon.stories.tsx index 3e3aa0468903..c72056d8fc2e 100644 --- a/code/core/src/component-testing/components/StatusIcon.stories.tsx +++ b/code/core/src/component-testing/components/StatusIcon.stories.tsx @@ -1,5 +1,5 @@ -import { CallStates } from '../../instrumenter/types'; -import { StatusIcon } from './StatusIcon'; +import { CallStates } from '../../instrumenter/types.ts'; +import { StatusIcon } from './StatusIcon.tsx'; export default { title: 'StatusIcon', diff --git a/code/core/src/component-testing/components/StatusIcon.tsx b/code/core/src/component-testing/components/StatusIcon.tsx index 4ff02db96a67..6c86da09d354 100644 --- a/code/core/src/component-testing/components/StatusIcon.tsx +++ b/code/core/src/component-testing/components/StatusIcon.tsx @@ -5,7 +5,7 @@ import { CheckIcon, CircleIcon, PlayIcon, StopAltIcon } from '@storybook/icons'; import { transparentize } from 'polished'; import { styled, useTheme } from 'storybook/theming'; -import { type Call, CallStates } from '../../instrumenter/types'; +import { type Call, CallStates } from '../../instrumenter/types.ts'; export interface StatusIconProps { status: Call['status']; diff --git a/code/core/src/component-testing/components/TestDiscrepancyMessage.stories.tsx b/code/core/src/component-testing/components/TestDiscrepancyMessage.stories.tsx index 4baabce599ca..34b02a5e34e9 100644 --- a/code/core/src/component-testing/components/TestDiscrepancyMessage.stories.tsx +++ b/code/core/src/component-testing/components/TestDiscrepancyMessage.stories.tsx @@ -5,8 +5,8 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { ManagerContext } from 'storybook/manager-api'; import { fn } from 'storybook/test'; -import { CallStates } from '../../instrumenter/types'; -import { TestDiscrepancyMessage } from './TestDiscrepancyMessage'; +import { CallStates } from '../../instrumenter/types.ts'; +import { TestDiscrepancyMessage } from './TestDiscrepancyMessage.tsx'; type Story = StoryObj; const managerContext: any = { diff --git a/code/core/src/component-testing/components/TestDiscrepancyMessage.tsx b/code/core/src/component-testing/components/TestDiscrepancyMessage.tsx index 3581840fc747..cc3b27b645ce 100644 --- a/code/core/src/component-testing/components/TestDiscrepancyMessage.tsx +++ b/code/core/src/component-testing/components/TestDiscrepancyMessage.tsx @@ -5,8 +5,8 @@ import { Link } from 'storybook/internal/components'; import { useStorybookApi } from 'storybook/manager-api'; import { styled } from 'storybook/theming'; -import { CallStates } from '../../instrumenter/types'; -import { DOCUMENTATION_DISCREPANCY_LINK } from '../constants'; +import { CallStates } from '../../instrumenter/types.ts'; +import { DOCUMENTATION_DISCREPANCY_LINK } from '../constants.ts'; const Wrapper = styled.div(({ theme: { color, typography, background } }) => ({ textAlign: 'start', diff --git a/code/core/src/component-testing/components/Toolbar.stories.tsx b/code/core/src/component-testing/components/Toolbar.stories.tsx index 0e22864e6837..c7972586d78f 100644 --- a/code/core/src/component-testing/components/Toolbar.stories.tsx +++ b/code/core/src/component-testing/components/Toolbar.stories.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { action } from 'storybook/actions'; -import { Toolbar } from './Toolbar'; +import { Toolbar } from './Toolbar.tsx'; export default { title: 'Toolbar', diff --git a/code/core/src/component-testing/components/Toolbar.tsx b/code/core/src/component-testing/components/Toolbar.tsx index b46251e1efdd..55f04008adcf 100644 --- a/code/core/src/component-testing/components/Toolbar.tsx +++ b/code/core/src/component-testing/components/Toolbar.tsx @@ -14,9 +14,9 @@ import { import { type API } from 'storybook/manager-api'; import { styled, useTheme } from 'storybook/theming'; -import { type ControlStates } from '../../instrumenter/types'; -import type { Controls } from './InteractionsPanel'; -import { type PlayStatus, StatusBadge } from './StatusBadge'; +import { type ControlStates } from '../../instrumenter/types.ts'; +import type { Controls } from './InteractionsPanel.tsx'; +import { type PlayStatus, StatusBadge } from './StatusBadge.tsx'; const ToolbarWrapper = styled.div(({ theme }) => ({ boxShadow: `${theme.appBorderColor} 0 -1px 0 0 inset`, diff --git a/code/core/src/component-testing/components/test-fn.stories.tsx b/code/core/src/component-testing/components/test-fn.stories.tsx index d64622a2a07f..2c6cb6d379dd 100644 --- a/code/core/src/component-testing/components/test-fn.stories.tsx +++ b/code/core/src/component-testing/components/test-fn.stories.tsx @@ -4,7 +4,7 @@ import type { StoryContext } from '@storybook/react-vite'; import { expect, fn } from 'storybook/test'; -import preview from '../../../../.storybook/preview'; +import preview from '../../../../.storybook/preview.tsx'; const Button = (args: React.ComponentProps<'button'>) =>