diff --git a/.circleci/config.yml b/.circleci/config.yml index b23edd89ef69..ad07b40429a4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -596,7 +596,7 @@ jobs: at: . - run: name: Install dependencies - command: yarn install + command: yarn install --no-immutable working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >> - run: name: Run Jest tests diff --git a/code/.eslintignore b/code/.eslintignore index 3319047075e6..5ced53c96672 100644 --- a/code/.eslintignore +++ b/code/.eslintignore @@ -17,4 +17,5 @@ ember-output !.eslintrc.js !.eslintrc-markdown.js !.storybook -lib/core-common/templates/base-preview-head.html \ No newline at end of file +lib/core-common/templates/base-preview-head.html +lib/core-server/src/utils/__search-files-tests__ \ No newline at end of file diff --git a/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts b/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts index 3fd56fc8107b..11a13c127e0e 100644 --- a/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts +++ b/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts @@ -4,6 +4,7 @@ import { rendererPackages, frameworkPackages, builderPackages, + extractProperFrameworkName, } from '@storybook/core-common'; import type { StorybookConfigRaw, StorybookConfig } from '@storybook/types'; import type { ConfigFile } from '@storybook/csf-tools'; @@ -30,11 +31,7 @@ export const getFrameworkPackageName = (mainConfig?: StorybookConfigRaw) => { return null; } - const normalizedPath = path.normalize(packageNameOrPath).replace(new RegExp(/\\/, 'g'), '/'); - - return ( - Object.keys(frameworkPackages).find((pkg) => normalizedPath.endsWith(pkg)) || packageNameOrPath - ); + return extractProperFrameworkName(packageNameOrPath); }; /** diff --git a/code/lib/cli/src/helpers.ts b/code/lib/cli/src/helpers.ts index ab3bf239592c..5c8351cca7b0 100644 --- a/code/lib/cli/src/helpers.ts +++ b/code/lib/cli/src/helpers.ts @@ -8,13 +8,13 @@ import stripJsonComments from 'strip-json-comments'; import findUp from 'find-up'; import invariant from 'tiny-invariant'; import { getCliDir, getRendererDir } from './dirs'; -import type { - JsPackageManager, - PackageJson, - PackageJsonWithDepsAndDevDeps, +import { + type JsPackageManager, + type PackageJson, + type PackageJsonWithDepsAndDevDeps, + frameworkToRenderer as CoreFrameworkToRenderer, } from '@storybook/core-common'; -import type { SupportedFrameworks } from '@storybook/types'; -import type { SupportedRenderers } from './project_types'; +import type { SupportedFrameworks, SupportedRenderers } from '@storybook/types'; import { CoreBuilder } from './project_types'; import { SupportedLanguage } from './project_types'; import { versions as storybookMonorepoPackages } from '@storybook/core-common'; @@ -132,40 +132,10 @@ type CopyTemplateFilesOptions = { destination?: string; }; -export const frameworkToRenderer: Record< - SupportedFrameworks | SupportedRenderers, - SupportedRenderers | 'vue' -> = { - // frameworks - angular: 'angular', - ember: 'ember', - 'html-vite': 'html', - 'html-webpack5': 'html', - nextjs: 'react', - 'preact-vite': 'preact', - 'preact-webpack5': 'preact', - qwik: 'qwik', - 'react-vite': 'react', - 'react-webpack5': 'react', - 'server-webpack5': 'server', - solid: 'solid', - 'svelte-vite': 'svelte', - 'svelte-webpack5': 'svelte', - sveltekit: 'svelte', - 'vue3-vite': 'vue3', - 'vue3-webpack5': 'vue3', - 'web-components-vite': 'web-components', - 'web-components-webpack5': 'web-components', - // renderers - html: 'html', - preact: 'preact', - 'react-native': 'react-native', - react: 'react', - server: 'server', - svelte: 'svelte', - vue3: 'vue3', - 'web-components': 'web-components', -}; +/** + * @deprecated Please use `frameworkToRenderer` from `@storybook/core-common` instead + */ +export const frameworkToRenderer = CoreFrameworkToRenderer; export const frameworkToDefaultBuilder: Record = { angular: CoreBuilder.Webpack5, diff --git a/code/lib/cli/src/project_types.ts b/code/lib/cli/src/project_types.ts index 3a5cda3781ef..f8f6973ab31a 100644 --- a/code/lib/cli/src/project_types.ts +++ b/code/lib/cli/src/project_types.ts @@ -1,5 +1,8 @@ import { minVersion, validRange } from 'semver'; -import type { SupportedFrameworks } from '@storybook/types'; +import type { + SupportedFrameworks, + SupportedRenderers as CoreSupportedFrameworks, +} from '@storybook/types'; function eqMajor(versionRange: string, major: number) { // Uses validRange to avoid a throw from minVersion if an invalid range gets passed @@ -22,20 +25,10 @@ export const externalFrameworks: ExternalFramework[] = [ { name: 'solid', frameworks: ['storybook-solidjs-vite'], renderer: 'storybook-solidjs' }, ]; -// Should match @storybook/ -export type SupportedRenderers = - | 'react' - | 'react-native' - | 'vue3' - | 'angular' - | 'ember' - | 'preact' - | 'svelte' - | 'qwik' - | 'html' - | 'web-components' - | 'server' - | 'solid'; +/** + * @deprecated Please use `SupportedFrameworks` from `@storybook/types` instead + */ +export type SupportedRenderers = CoreSupportedFrameworks; export const SUPPORTED_RENDERERS: SupportedRenderers[] = [ 'react', diff --git a/code/lib/core-common/src/index.ts b/code/lib/core-common/src/index.ts index e8f02195ba9d..a568fa70ba4a 100644 --- a/code/lib/core-common/src/index.ts +++ b/code/lib/core-common/src/index.ts @@ -7,6 +7,7 @@ export * from './utils/cli'; export * from './utils/check-addon-order'; export * from './utils/envs'; export * from './utils/common-glob-options'; +export * from './utils/framework-to-renderer'; export * from './utils/get-builder-options'; export * from './utils/get-framework-name'; export * from './utils/get-renderer-name'; diff --git a/code/lib/core-common/src/utils/framework-to-renderer.ts b/code/lib/core-common/src/utils/framework-to-renderer.ts new file mode 100644 index 000000000000..a7c8532529a1 --- /dev/null +++ b/code/lib/core-common/src/utils/framework-to-renderer.ts @@ -0,0 +1,36 @@ +import type { SupportedFrameworks, SupportedRenderers } from '@storybook/types'; + +export const frameworkToRenderer: Record< + SupportedFrameworks | SupportedRenderers, + SupportedRenderers | 'vue' +> = { + // frameworks + angular: 'angular', + ember: 'ember', + 'html-vite': 'html', + 'html-webpack5': 'html', + nextjs: 'react', + 'preact-vite': 'preact', + 'preact-webpack5': 'preact', + qwik: 'qwik', + 'react-vite': 'react', + 'react-webpack5': 'react', + 'server-webpack5': 'server', + solid: 'solid', + 'svelte-vite': 'svelte', + 'svelte-webpack5': 'svelte', + sveltekit: 'svelte', + 'vue3-vite': 'vue3', + 'vue3-webpack5': 'vue3', + 'web-components-vite': 'web-components', + 'web-components-webpack5': 'web-components', + // renderers + html: 'html', + preact: 'preact', + 'react-native': 'react-native', + react: 'react', + server: 'server', + svelte: 'svelte', + vue3: 'vue3', + 'web-components': 'web-components', +}; diff --git a/code/lib/core-common/src/utils/get-framework-name.test.ts b/code/lib/core-common/src/utils/get-framework-name.test.ts new file mode 100644 index 000000000000..9f6a95ba19e7 --- /dev/null +++ b/code/lib/core-common/src/utils/get-framework-name.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest'; +import { extractProperFrameworkName } from './get-framework-name'; + +describe('get-framework-name', () => { + describe('extractProperFrameworkName', () => { + it('should extract the proper framework name from the given framework field', () => { + expect(extractProperFrameworkName('@storybook/angular')).toBe('@storybook/angular'); + expect(extractProperFrameworkName('/path/to/@storybook/angular')).toBe('@storybook/angular'); + expect(extractProperFrameworkName('\\path\\to\\@storybook\\angular')).toBe( + '@storybook/angular' + ); + }); + + it('should return the given framework name if it is a third-party framework', () => { + expect(extractProperFrameworkName('@third-party/framework')).toBe('@third-party/framework'); + }); + }); +}); diff --git a/code/lib/core-common/src/utils/get-framework-name.ts b/code/lib/core-common/src/utils/get-framework-name.ts index e7191545b5a4..fcfef2efee42 100644 --- a/code/lib/core-common/src/utils/get-framework-name.ts +++ b/code/lib/core-common/src/utils/get-framework-name.ts @@ -1,5 +1,7 @@ import { dedent } from 'ts-dedent'; import type { Options } from '@storybook/types'; +import { frameworkPackages } from './get-storybook-info'; +import { normalizePath } from './normalize-path'; /** * Framework can be a string or an object. This utility always returns the string name. @@ -17,3 +19,17 @@ export async function getFrameworkName(options: Options) { return typeof framework === 'object' ? framework.name : framework; } + +/** + * Extracts the proper framework name from the given framework field. + * The framework field can be the framework package name or a path to the framework package. + * @example + * extractProperFrameworkName('/path/to/@storybook/angular') // => '@storybook/angular' + * extractProperFrameworkName('@third-party/framework') // => '@third-party/framework' + */ +export const extractProperFrameworkName = (framework: string) => { + const normalizedPath = normalizePath(framework); + const frameworkName = Object.keys(frameworkPackages).find((pkg) => normalizedPath.endsWith(pkg)); + + return frameworkName ?? framework; +}; diff --git a/code/lib/core-common/src/utils/get-renderer-name.test.ts b/code/lib/core-common/src/utils/get-renderer-name.test.ts new file mode 100644 index 000000000000..f5d10959405c --- /dev/null +++ b/code/lib/core-common/src/utils/get-renderer-name.test.ts @@ -0,0 +1,17 @@ +import { it } from 'node:test'; +import { describe, expect } from 'vitest'; +import { extractProperRendererNameFromFramework } from './get-renderer-name'; + +describe('get-renderer-name', () => { + describe('extractProperRendererNameFromFramework', () => { + it('should return the renderer name for a known framework', async () => { + const renderer = await extractProperRendererNameFromFramework('@storybook/react'); + expect(renderer).toEqual('react'); + }); + + it('should return null for an unknown framework', async () => { + const renderer = await extractProperRendererNameFromFramework('@third-party/framework'); + expect(renderer).toBeNull(); + }); + }); +}); diff --git a/code/lib/core-common/src/utils/get-renderer-name.ts b/code/lib/core-common/src/utils/get-renderer-name.ts index ce4a14891086..2acdcc6421bc 100644 --- a/code/lib/core-common/src/utils/get-renderer-name.ts +++ b/code/lib/core-common/src/utils/get-renderer-name.ts @@ -1,8 +1,11 @@ import type { Options } from '@storybook/types'; -import { getFrameworkName } from './get-framework-name'; +import { extractProperFrameworkName, getFrameworkName } from './get-framework-name'; +import { frameworkPackages } from './get-storybook-info'; +import { frameworkToRenderer } from './framework-to-renderer'; /** * Render is set as a string on core. It must be set by the framework + * It falls back to the framework name if not set */ export async function getRendererName(options: Options) { const core = await options.presets.apply('core', {}, options); @@ -15,3 +18,23 @@ export async function getRendererName(options: Options) { return core.renderer; } + +/** + * Extracts the proper renderer name from the given framework name. + * @param frameworkName The name of the framework. + * @returns The name of the renderer. + * @example + * extractProperRendererNameFromFramework('@storybook/react') // => 'react' + * extractProperRendererNameFromFramework('@storybook/angular') // => 'angular' + * extractProperRendererNameFromFramework('@third-party/framework') // => null + */ +export async function extractProperRendererNameFromFramework(frameworkName: string) { + const extractedFrameworkName = extractProperFrameworkName(frameworkName); + const framework = frameworkPackages[extractedFrameworkName]; + + if (!framework) { + return null; + } + + return frameworkToRenderer[framework as keyof typeof frameworkToRenderer]; +} diff --git a/code/lib/core-common/src/utils/normalize-path.test.ts b/code/lib/core-common/src/utils/normalize-path.test.ts new file mode 100644 index 000000000000..b2688660b6ef --- /dev/null +++ b/code/lib/core-common/src/utils/normalize-path.test.ts @@ -0,0 +1,11 @@ +import { normalizePath } from './normalize-path'; +import { describe, expect, it } from 'vitest'; + +describe('normalize-path', () => { + it('should normalize paths', () => { + expect(normalizePath('path/to/../file')).toBe('path/file'); + expect(normalizePath('path/to/./file')).toBe('path/to/file'); + expect(normalizePath('path\\to\\file')).toBe('path/to/file'); + expect(normalizePath('foo\\..\\bar')).toBe('bar'); + }); +}); diff --git a/code/lib/core-common/src/utils/normalize-path.ts b/code/lib/core-common/src/utils/normalize-path.ts new file mode 100644 index 000000000000..a6db3aa2c9de --- /dev/null +++ b/code/lib/core-common/src/utils/normalize-path.ts @@ -0,0 +1,14 @@ +import path from 'path'; + +/** + * Normalize a path to use forward slashes and remove .. and . + * @param p The path to normalize + * @returns The normalized path + * @example + * normalizePath('path/to/../file') // => 'path/file' + * normalizePath('path/to/./file') // => 'path/to/file' + * normalizePath('path\\to\\file') // => 'path/to/file' + */ +export function normalizePath(p: string) { + return path.posix.normalize(p.replace(/\\/g, '/')); +} diff --git a/code/lib/core-events/src/index.ts b/code/lib/core-events/src/index.ts index 7542d0aa4e57..82978a994d07 100644 --- a/code/lib/core-events/src/index.ts +++ b/code/lib/core-events/src/index.ts @@ -73,6 +73,8 @@ enum events { SET_WHATS_NEW_CACHE = 'setWhatsNewCache', TOGGLE_WHATS_NEW_NOTIFICATIONS = 'toggleWhatsNewNotifications', TELEMETRY_ERROR = 'telemetryError', + FILE_COMPONENT_SEARCH = 'fileComponentSearch', + FILE_COMPONENT_SEARCH_RESULT = 'fileComponentSearchResult', } // Enables: `import Events from ...` @@ -87,6 +89,8 @@ export const { CURRENT_STORY_WAS_SET, DOCS_PREPARED, DOCS_RENDERED, + FILE_COMPONENT_SEARCH, + FILE_COMPONENT_SEARCH_RESULT, FORCE_RE_RENDER, FORCE_REMOUNT, GLOBALS_UPDATED, diff --git a/code/lib/core-server/package.json b/code/lib/core-server/package.json index 58f6be3c9d46..64c7893f8523 100644 --- a/code/lib/core-server/package.json +++ b/code/lib/core-server/package.json @@ -78,9 +78,11 @@ "@types/semver": "^7.3.4", "better-opn": "^3.0.2", "chalk": "^4.1.0", + "cjs-module-lexer": "^1.2.3", "cli-table3": "^0.6.1", "compression": "^1.7.4", "detect-port": "^1.3.0", + "es-module-lexer": "^1.5.0", "express": "^4.17.3", "fs-extra": "^11.1.0", "globby": "^14.0.1", diff --git a/code/lib/core-server/src/presets/common-preset.ts b/code/lib/core-server/src/presets/common-preset.ts index 6dcd6366eff0..fbc39465aad4 100644 --- a/code/lib/core-server/src/presets/common-preset.ts +++ b/code/lib/core-server/src/presets/common-preset.ts @@ -34,6 +34,7 @@ import invariant from 'tiny-invariant'; import { parseStaticDir } from '../utils/server-statics'; import { defaultStaticDirs } from '../utils/constants'; import { sendTelemetryError } from '../withTelemetry'; +import { initFileSearchChannel } from '../server-channel/file-search-channel'; const interpolate = (string: string, data: Record = {}) => Object.entries(data).reduce((acc, [k, v]) => acc.replace(new RegExp(`%${k}%`, 'g'), v), string); @@ -340,6 +341,8 @@ export const experimental_serverChannel = async ( } }); + initFileSearchChannel(channel, options); + return channel; }; diff --git a/code/lib/core-server/src/server-channel/file-search-channel.test.ts b/code/lib/core-server/src/server-channel/file-search-channel.test.ts new file mode 100644 index 000000000000..e967910dd6c7 --- /dev/null +++ b/code/lib/core-server/src/server-channel/file-search-channel.test.ts @@ -0,0 +1,165 @@ +import type { ChannelTransport } from '@storybook/channels'; +import { Channel } from '@storybook/channels'; +import { FILE_COMPONENT_SEARCH, FILE_COMPONENT_SEARCH_RESULT } from '@storybook/core-events'; +import { beforeEach, describe, expect, vi, it } from 'vitest'; + +import { initFileSearchChannel } from './file-search-channel'; + +const mocks = vi.hoisted(() => { + return { + searchFiles: vi.fn(), + }; +}); + +vi.mock('../utils/search-files', () => { + return { + searchFiles: mocks.searchFiles, + }; +}); + +vi.mock('@storybook/core-common', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + getFrameworkName: vi.fn().mockResolvedValue('@storybook/react'), + extractProperRendererNameFromFramework: vi.fn().mockResolvedValue('react'), + getProjectRoot: vi + .fn() + .mockReturnValue(require('path').join(__dirname, '..', 'utils', '__search-files-tests__')), + }; +}); + +describe('file-search-channel', () => { + const transport = { setHandler: vi.fn(), send: vi.fn() } satisfies ChannelTransport; + const mockChannel = new Channel({ transport }); + const searchResultChannelListener = vi.fn(); + + beforeEach(() => { + transport.setHandler.mockClear(); + transport.send.mockClear(); + searchResultChannelListener.mockClear(); + }); + + describe('initFileSearchChannel', async () => { + it('should emit search result event with the search result', async () => { + const mockOptions = {}; + const data = { searchQuery: 'commonjs' }; + + initFileSearchChannel(mockChannel, mockOptions as any); + + mockChannel.addListener(FILE_COMPONENT_SEARCH_RESULT, searchResultChannelListener); + mockChannel.emit(FILE_COMPONENT_SEARCH, data); + + mocks.searchFiles.mockImplementation(async (...args) => { + // @ts-expect-error Ignore type issue + return (await vi.importActual('../utils/search-files')).searchFiles(...args); + }); + + await vi.waitFor( + () => { + expect(searchResultChannelListener).toHaveBeenCalled(); + }, + { timeout: 2000 } + ); + + expect(searchResultChannelListener).toHaveBeenCalledWith({ + error: null, + result: { + files: [ + { + exportedComponents: [ + { + default: false, + name: './commonjs', + }, + ], + filepath: 'src/commonjs-module-default.js', + }, + { + exportedComponents: [ + { + default: false, + name: 'a', + }, + { + default: false, + name: 'b', + }, + { + default: false, + name: 'c', + }, + { + default: false, + name: 'd', + }, + { + default: false, + name: 'e', + }, + ], + filepath: 'src/commonjs-module.js', + }, + ], + searchQuery: 'commonjs', + }, + success: true, + }); + }); + + it('should emit search result event with an empty search result', async () => { + const mockOptions = {}; + const data = { searchQuery: 'no-file-for-search-query' }; + + initFileSearchChannel(mockChannel, mockOptions as any); + + mockChannel.addListener(FILE_COMPONENT_SEARCH_RESULT, searchResultChannelListener); + mockChannel.emit(FILE_COMPONENT_SEARCH, data); + + mocks.searchFiles.mockImplementation(async (...args) => { + // @ts-expect-error Ignore type issue + return (await vi.importActual('../utils/search-files')).searchFiles(...args); + }); + + await vi.waitFor( + () => { + expect(searchResultChannelListener).toHaveBeenCalled(); + }, + { timeout: 2000 } + ); + + expect(searchResultChannelListener).toHaveBeenCalledWith({ + error: null, + result: { + files: [], + searchQuery: 'no-file-for-search-query', + }, + success: true, + }); + }); + + it('should emit an error message if an error occurs while searching for components in the project', async () => { + const mockOptions = {}; + const data = { searchQuery: 'commonjs' }; + + initFileSearchChannel(mockChannel, mockOptions as any); + + mockChannel.addListener(FILE_COMPONENT_SEARCH_RESULT, searchResultChannelListener); + + mockChannel.emit(FILE_COMPONENT_SEARCH, data); + + mocks.searchFiles.mockRejectedValue(new Error('ENOENT: no such file or directory')); + + await vi.waitFor(() => { + expect(searchResultChannelListener).toHaveBeenCalled(); + }); + + expect(searchResultChannelListener).toHaveBeenCalledWith({ + error: + 'An error occurred while searching for components in the project.\nENOENT: no such file or directory', + result: null, + success: false, + }); + }); + }); +}); diff --git a/code/lib/core-server/src/server-channel/file-search-channel.ts b/code/lib/core-server/src/server-channel/file-search-channel.ts new file mode 100644 index 000000000000..3f2884867447 --- /dev/null +++ b/code/lib/core-server/src/server-channel/file-search-channel.ts @@ -0,0 +1,106 @@ +import type { Options, SupportedRenderers } from '@storybook/types'; +import type { Channel } from '@storybook/channels'; +import { + extractProperRendererNameFromFramework, + getFrameworkName, + getProjectRoot, +} from '@storybook/core-common'; +import path from 'path'; +import fs from 'fs/promises'; + +import { getParser } from '../utils/parser'; +import { searchFiles } from '../utils/search-files'; +import { FILE_COMPONENT_SEARCH, FILE_COMPONENT_SEARCH_RESULT } from '@storybook/core-events'; + +interface Data { + // A regular string or a glob pattern + searchQuery?: string; +} + +interface SearchResult { + success: true | false; + result: null | { + searchQuery: string; + files: Array<{ + // The filepath relative to the project root + filepath: string; + // The search query - Helps to identify the event on the frontend + searchQuery: string; + // A list of exported components + exportedComponents: Array<{ + // the name of the exported component + name: string; + // True, if the exported component is a default export + default: boolean; + }>; + }> | null; + }; + error: null | string; +} + +export function initFileSearchChannel(channel: Channel, options: Options) { + /** + * Listens for a search query event and searches for files in the project + */ + channel.on(FILE_COMPONENT_SEARCH, async (data: Data) => { + try { + const searchQuery = data?.searchQuery; + + if (!searchQuery) { + return; + } + + const frameworkName = await getFrameworkName(options); + + const rendererName = (await extractProperRendererNameFromFramework( + frameworkName + )) as SupportedRenderers; + + const projectRoot = getProjectRoot(); + + const files = await searchFiles({ + searchQuery, + cwd: projectRoot, + }); + + const entries = files.map(async (file) => { + const parser = getParser(rendererName); + + try { + const content = await fs.readFile(path.join(projectRoot, file), 'utf-8'); + const info = await parser.parse(content); + + return { + filepath: file, + exportedComponents: info.exports, + }; + } catch (e) { + return { + filepath: file, + exportedComponents: null, + }; + } + }); + + channel.emit(FILE_COMPONENT_SEARCH_RESULT, { + success: true, + result: { + searchQuery, + files: await Promise.all(entries), + }, + error: null, + } as SearchResult); + } catch (e: any) { + /** + * Emits the search result event with an error message + */ + channel.emit(FILE_COMPONENT_SEARCH_RESULT, { + success: false, + result: null, + error: `An error occurred while searching for components in the project.\n${e?.message}`, + } as SearchResult); + } + }); + + return channel; +} diff --git a/code/lib/core-server/src/utils/__search-files-tests__/.gitignore b/code/lib/core-server/src/utils/__search-files-tests__/.gitignore new file mode 100644 index 000000000000..7e4e8f40e4f1 --- /dev/null +++ b/code/lib/core-server/src/utils/__search-files-tests__/.gitignore @@ -0,0 +1 @@ +src/ignored.js \ No newline at end of file diff --git a/code/lib/core-server/src/utils/__search-files-tests__/README.md b/code/lib/core-server/src/utils/__search-files-tests__/README.md new file mode 100644 index 000000000000..926c54d82404 --- /dev/null +++ b/code/lib/core-server/src/utils/__search-files-tests__/README.md @@ -0,0 +1 @@ +The parent directory "\_\_tests\_\_ was created to unit test the search-file functionality diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/assets/asset.css b/code/lib/core-server/src/utils/__search-files-tests__/src/assets/asset.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/assets/asset.json b/code/lib/core-server/src/utils/__search-files-tests__/src/assets/asset.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/assets/asset.png b/code/lib/core-server/src/utils/__search-files-tests__/src/assets/asset.png new file mode 100644 index 000000000000..1914264c0878 Binary files /dev/null and b/code/lib/core-server/src/utils/__search-files-tests__/src/assets/asset.png differ diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/commonjs-module-default.js b/code/lib/core-server/src/utils/__search-files-tests__/src/commonjs-module-default.js new file mode 100644 index 000000000000..aed903ea7003 --- /dev/null +++ b/code/lib/core-server/src/utils/__search-files-tests__/src/commonjs-module-default.js @@ -0,0 +1 @@ +module.exports = require('./commonjs'); diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/commonjs-module.js b/code/lib/core-server/src/utils/__search-files-tests__/src/commonjs-module.js new file mode 100644 index 000000000000..7146ed966336 --- /dev/null +++ b/code/lib/core-server/src/utils/__search-files-tests__/src/commonjs-module.js @@ -0,0 +1,19 @@ +// named exports detection +module.exports.a = 'a'; + +(function () { + exports.b = 'b'; +})(); + +Object.defineProperty(exports, 'c', { value: 'c' }); +/* exports.d = 'not detected'; */ + +// reexports detection +if (maybe) module.exports = require('./dep1.js'); +if (another) module.exports = require('./dep2.js'); + +// literal exports assignments +module.exports = { a, b: c, d, e: f }; + +// __esModule detection +Object.defineProperty(module.exports, '__esModule', { value: true }); diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/es-module.js b/code/lib/core-server/src/utils/__search-files-tests__/src/es-module.js new file mode 100644 index 000000000000..34fc39774ae6 --- /dev/null +++ b/code/lib/core-server/src/utils/__search-files-tests__/src/es-module.js @@ -0,0 +1,16 @@ +/* eslint-disable import/no-unresolved */ +import * as ns from 'external2'; + +export var p = 5; + +export function q() {} + +export class C {} + +export { x as externalName } from 'external'; + +export { ns }; + +export default function () { + return 'default'; +} diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.cjs b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.cjs new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.cjs @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.cts b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.cts new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.cts @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.js b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.js new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.js @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.jsx b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.jsx new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.jsx @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.mjs b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.mjs new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.mjs @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.mts b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.mts new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.mts @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.ts b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.ts new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.ts @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.tsx b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.tsx new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.tsx @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/ignored.js b/code/lib/core-server/src/utils/__search-files-tests__/src/ignored.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/no-export.js b/code/lib/core-server/src/utils/__search-files-tests__/src/no-export.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/node_modules/file-in-common.js b/code/lib/core-server/src/utils/__search-files-tests__/src/node_modules/file-in-common.js new file mode 100644 index 000000000000..1626d1e76d96 --- /dev/null +++ b/code/lib/core-server/src/utils/__search-files-tests__/src/node_modules/file-in-common.js @@ -0,0 +1,3 @@ +export default () => { + return 'commonjs-default'; +} \ No newline at end of file diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/tests/some.spec.ts b/code/lib/core-server/src/utils/__search-files-tests__/src/tests/some.spec.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/lib/core-server/src/utils/__search-files-tests__/src/tests/some.test.ts b/code/lib/core-server/src/utils/__search-files-tests__/src/tests/some.test.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/lib/core-server/src/utils/parser/generic-parser.test.ts b/code/lib/core-server/src/utils/parser/generic-parser.test.ts new file mode 100644 index 000000000000..6d3ff96e15b0 --- /dev/null +++ b/code/lib/core-server/src/utils/parser/generic-parser.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, it } from 'vitest'; +import path from 'path'; +import { GenericParser } from './generic-parser'; +import fs from 'fs'; + +const genericParser = new GenericParser(); + +const TEST_DIR = path.join(__dirname, '..', '__search-files-tests__'); + +describe('generic-parser', () => { + it('should correctly return exports from CommonJS files', async () => { + const content = fs.readFileSync(path.join(TEST_DIR, 'src', 'commonjs-module.js'), 'utf-8'); + const { exports } = await genericParser.parse(content); + + expect(exports).toEqual([ + { + default: false, + name: 'a', + }, + { + default: false, + name: 'b', + }, + { + default: false, + name: 'c', + }, + { + default: false, + name: 'd', + }, + { + default: false, + name: 'e', + }, + ]); + }); + + it('should correctly return exports from ES modules', async () => { + const content = fs.readFileSync(path.join(TEST_DIR, 'src', 'es-module.js'), 'utf-8'); + const { exports } = await genericParser.parse(content); + + expect(exports).toEqual([ + { + default: false, + name: 'p', + }, + { + default: false, + name: 'q', + }, + { + default: false, + name: 'C', + }, + { + default: false, + name: 'externalName', + }, + { + default: false, + name: 'ns', + }, + { + default: true, + name: 'default', + }, + ]); + }); +}); diff --git a/code/lib/core-server/src/utils/parser/generic-parser.ts b/code/lib/core-server/src/utils/parser/generic-parser.ts new file mode 100644 index 000000000000..e297c1e92eed --- /dev/null +++ b/code/lib/core-server/src/utils/parser/generic-parser.ts @@ -0,0 +1,53 @@ +import { parse as parseCjs, init as initCjsParser } from 'cjs-module-lexer'; +import { parse as parseEs } from 'es-module-lexer'; +import assert from 'node:assert'; + +import type { Parser } from './types'; + +/** + * A generic parser that can parse both ES and CJS modules. + */ +export class GenericParser implements Parser { + /** + * Parse the content of a file and return the exports + * @param content The content of the file + * @returns The exports of the file + */ + async parse(content: string) { + try { + // Do NOT remove await here. The types are wrong! It has to be awaited, + // otherwise it will return a Promise> when wasm isn't loaded. + const [, exports] = await parseEs(content); + + assert( + exports.length > 0, + 'No named exports found. Very likely that this is not a ES module.' + ); + + return { + exports: (exports ?? []).map((e) => { + const name = content.substring(e.s, e.e); + return { + name, + default: name === 'default', + }; + }), + }; + // Try to parse as CJS module + } catch { + await initCjsParser(); + + const { exports, reexports } = parseCjs(content); + const filteredExports = [...exports, ...reexports].filter((e: string) => e !== '__esModule'); + + assert(filteredExports.length > 0, 'No named exports found'); + + return { + exports: (filteredExports ?? []).map((name) => ({ + name, + default: name === 'default', + })), + }; + } + } +} diff --git a/code/lib/core-server/src/utils/parser/index.ts b/code/lib/core-server/src/utils/parser/index.ts new file mode 100644 index 000000000000..8f5183a49538 --- /dev/null +++ b/code/lib/core-server/src/utils/parser/index.ts @@ -0,0 +1,15 @@ +import type { SupportedRenderers } from '@storybook/types'; +import { GenericParser } from './generic-parser'; +import type { Parser } from './types'; + +/** + * Get the parser for a given renderer + * @param renderer The renderer to get the parser for + * @returns The parser for the renderer + */ +export function getParser(renderer: SupportedRenderers | null): Parser { + switch (renderer) { + default: + return new GenericParser(); + } +} diff --git a/code/lib/core-server/src/utils/parser/types.ts b/code/lib/core-server/src/utils/parser/types.ts new file mode 100644 index 000000000000..c3c664abc27d --- /dev/null +++ b/code/lib/core-server/src/utils/parser/types.ts @@ -0,0 +1,18 @@ +export type ParserResult = { + exports: Array<{ + name: string; + default: boolean; + }>; +}; + +/** + * A parser that can parse the exports of a file + */ +export interface Parser { + /** + * Parse the content of a file and return the exports + * @param content The content of the file + * @returns The result of the parsing. Contains the exports of the file + */ + parse: (content: string) => Promise; +} diff --git a/code/lib/core-server/src/utils/search-files.test.ts b/code/lib/core-server/src/utils/search-files.test.ts new file mode 100644 index 000000000000..6aaec136df74 --- /dev/null +++ b/code/lib/core-server/src/utils/search-files.test.ts @@ -0,0 +1,86 @@ +import { describe, expect, it } from 'vitest'; +import path from 'path'; +import { searchFiles } from './search-files'; + +describe('search-files', () => { + it('should automatically convert static search to a dynamic glob search', async (t) => { + const files = await searchFiles({ + searchQuery: 'ommonjs', + cwd: path.join(__dirname, '__search-files-tests__'), + }); + + expect(files).toEqual(['src/commonjs-module-default.js', 'src/commonjs-module.js']); + }); + + it('should automatically convert static search to a dynamic glob search (with file extension)', async (t) => { + const files = await searchFiles({ + searchQuery: 'module.js', + cwd: path.join(__dirname, '__search-files-tests__'), + }); + + expect(files).toEqual(['src/commonjs-module.js', 'src/es-module.js']); + }); + + it('should return all files if the search query matches the parent folder', async (t) => { + const files = await searchFiles({ + searchQuery: 'file-extensions', + cwd: path.join(__dirname, '__search-files-tests__'), + }); + + expect(files).toEqual([ + 'src/file-extensions/extension.cjs', + 'src/file-extensions/extension.cts', + 'src/file-extensions/extension.js', + 'src/file-extensions/extension.jsx', + 'src/file-extensions/extension.mjs', + 'src/file-extensions/extension.mts', + 'src/file-extensions/extension.ts', + 'src/file-extensions/extension.tsx', + ]); + }); + + it('should ignore files that do not have the allowed extensions', async (t) => { + const files = await searchFiles({ + searchQuery: 'asset', + cwd: path.join(__dirname, '__search-files-tests__'), + }); + + expect(files).toEqual([]); + }); + + it('should ignore test files (*.spec.*, *.test.*)', async (t) => { + const files = await searchFiles({ + searchQuery: 'tests', + cwd: path.join(__dirname, '__search-files-tests__'), + }); + + expect(files).toEqual([]); + }); + + it('should work with glob search patterns', async (t) => { + const files = await searchFiles({ + searchQuery: '**/commonjs-module.js', + cwd: path.join(__dirname, '__search-files-tests__'), + }); + + expect(files).toEqual(['src/commonjs-module.js']); + }); + + it('should ignore node_modules', async (t) => { + const files = await searchFiles({ + searchQuery: 'file-in-common.js', + cwd: path.join(__dirname, '__search-files-tests__'), + }); + + expect(files).toEqual([]); + }); + + it('should not return files outside of project root', async (t) => { + await expect(() => + searchFiles({ + searchQuery: '../**/*', + cwd: path.join(__dirname, '__search-files-tests__'), + }) + ).rejects.toThrowError(); + }); +}); diff --git a/code/lib/core-server/src/utils/search-files.ts b/code/lib/core-server/src/utils/search-files.ts new file mode 100644 index 000000000000..b6f1bd89ab25 --- /dev/null +++ b/code/lib/core-server/src/utils/search-files.ts @@ -0,0 +1,48 @@ +export type SearchResult = Array; + +/** + * File extensions that should be searched for + */ +const fileExtensions = ['js', 'mjs', 'cjs', 'jsx', 'mts', 'ts', 'tsx', 'cts']; + +/** + * Search for files in a directory that match the search query + * @param searchQuery The search query. This can be a glob pattern + * @param cwd The directory to search in + * @param renderer The renderer to use for parsing the files + * @returns A list of files that match the search query + */ +export async function searchFiles({ + searchQuery, + cwd, +}: { + searchQuery: string; + cwd: string; +}): Promise { + // Dynamically import globby because it is a pure ESM module + const { globby, isDynamicPattern } = await import('globby'); + + const hasSearchSpecialGlobChars = isDynamicPattern(searchQuery, { cwd }); + + const hasFileExtensionRegex = /(\.[a-z]+)$/i; + const searchQueryHasExtension = hasFileExtensionRegex.test(searchQuery); + const fileExtensionsPattern = `{${fileExtensions.join(',')}}`; + + const globbedSearchQuery = hasSearchSpecialGlobChars + ? searchQuery + : searchQueryHasExtension + ? [`**/*${searchQuery}*`, `**/*${searchQuery}*/**`] + : [ + `**/*${searchQuery}*.${fileExtensionsPattern}`, + `**/*${searchQuery}*/**/*.${fileExtensionsPattern}`, + ]; + + const entries = await globby(globbedSearchQuery, { + ignore: ['**/node_modules/**', '**/*.spec.*', '**/*.test.*'], + gitignore: true, + cwd, + objectMode: true, + }); + + return entries.map((entry) => entry.path); +} diff --git a/code/lib/types/src/index.ts b/code/lib/types/src/index.ts index 21a5f9ea000c..523f2c3c184b 100644 --- a/code/lib/types/src/index.ts +++ b/code/lib/types/src/index.ts @@ -10,3 +10,4 @@ export * from './modules/indexer'; export * from './modules/composedStory'; export * from './modules/channelApi'; export * from './modules/frameworks'; +export * from './modules/renderers'; diff --git a/code/lib/types/src/modules/renderers.ts b/code/lib/types/src/modules/renderers.ts new file mode 100644 index 000000000000..4fcf0be99d87 --- /dev/null +++ b/code/lib/types/src/modules/renderers.ts @@ -0,0 +1,14 @@ +// Should match @storybook/ +export type SupportedRenderers = + | 'react' + | 'react-native' + | 'vue3' + | 'angular' + | 'ember' + | 'preact' + | 'svelte' + | 'qwik' + | 'html' + | 'web-components' + | 'server' + | 'solid'; diff --git a/code/ui/manager/src/globals/exports.ts b/code/ui/manager/src/globals/exports.ts index 73881b9b5919..794741a69e8b 100644 --- a/code/ui/manager/src/globals/exports.ts +++ b/code/ui/manager/src/globals/exports.ts @@ -136,6 +136,8 @@ export default { 'CURRENT_STORY_WAS_SET', 'DOCS_PREPARED', 'DOCS_RENDERED', + 'FILE_COMPONENT_SEARCH', + 'FILE_COMPONENT_SEARCH_RESULT', 'FORCE_REMOUNT', 'FORCE_RE_RENDER', 'GLOBALS_UPDATED', diff --git a/code/yarn.lock b/code/yarn.lock index 472dd503b241..7be8a5f1fd96 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5596,9 +5596,11 @@ __metadata: better-opn: "npm:^3.0.2" boxen: "npm:^7.1.1" chalk: "npm:^4.1.0" + cjs-module-lexer: "npm:^1.2.3" cli-table3: "npm:^0.6.1" compression: "npm:^1.7.4" detect-port: "npm:^1.3.0" + es-module-lexer: "npm:^1.5.0" express: "npm:^4.17.3" fs-extra: "npm:^11.1.0" globby: "npm:^14.0.1" diff --git a/test-storybooks/portable-stories-kitchen-sink/nextjs/yarn.lock b/test-storybooks/portable-stories-kitchen-sink/nextjs/yarn.lock index 64f5d683addd..e621c6f62567 100644 --- a/test-storybooks/portable-stories-kitchen-sink/nextjs/yarn.lock +++ b/test-storybooks/portable-stories-kitchen-sink/nextjs/yarn.lock @@ -2404,12 +2404,16 @@ __metadata: "@storybook/addon-controls@file:../../../code/addons/controls::locator=portable-stories-nextjs%40workspace%3A.": version: 8.1.0-alpha.5 - resolution: "@storybook/addon-controls@file:../../../code/addons/controls#../../../code/addons/controls::hash=b0a330&locator=portable-stories-nextjs%40workspace%3A." + resolution: "@storybook/addon-controls@file:../../../code/addons/controls#../../../code/addons/controls::hash=358687&locator=portable-stories-nextjs%40workspace%3A." dependencies: "@storybook/blocks": "workspace:*" + "@storybook/core-common": "workspace:*" + cjs-module-lexer: "npm:^1.2.3" + es-module-lexer: "npm:^1.5.0" + globby: "npm:^14.0.1" lodash: "npm:^4.17.21" ts-dedent: "npm:^2.0.0" - checksum: 10/dcdc61154006c8575e612d77b7cfa0e2266f12429e2560afe49d4ca74b56a40ba4823e2b2a55e6803006e5fbad8ea848ed4d1ec753063763f01b53236b7ba223 + checksum: 10/feb098e18f942562769dfdfb4afc70655f67f11e6d14acf8e3ca4cfe9a22e09e89ede9f17be6ff1c07a56bed8a6fa1961fa044137a3e7a34471345bcccd091f2 languageName: node linkType: hard @@ -2644,7 +2648,7 @@ __metadata: "@storybook/cli@file:../../../code/lib/cli::locator=portable-stories-nextjs%40workspace%3A.": version: 8.1.0-alpha.5 - resolution: "@storybook/cli@file:../../../code/lib/cli#../../../code/lib/cli::hash=d07615&locator=portable-stories-nextjs%40workspace%3A." + resolution: "@storybook/cli@file:../../../code/lib/cli#../../../code/lib/cli::hash=49e395&locator=portable-stories-nextjs%40workspace%3A." dependencies: "@babel/core": "npm:^7.23.0" "@babel/types": "npm:^7.23.0" @@ -2685,7 +2689,7 @@ __metadata: bin: getstorybook: ./bin/index.js sb: ./bin/index.js - checksum: 10/ca2d93c888ae68445728b0dfad15917ca75e7f193c3da8e2e115386cee359246a701d4cad9edd71ebb1377bebaecf9147a267963e972c4085f10082ec4cccf93 + checksum: 10/37be78e7b11cfb9d712f9b948e3017665fcee970e1b3a4806f3f8307ea9db7e39f4b12fa919086a53efbe4e24a5e6196b2daafb958c11145e9e5d224fa70af55 languageName: node linkType: hard @@ -2743,7 +2747,7 @@ __metadata: "@storybook/core-common@file:../../../code/lib/core-common::locator=portable-stories-nextjs%40workspace%3A.": version: 8.1.0-alpha.5 - resolution: "@storybook/core-common@file:../../../code/lib/core-common#../../../code/lib/core-common::hash=d6f826&locator=portable-stories-nextjs%40workspace%3A." + resolution: "@storybook/core-common@file:../../../code/lib/core-common#../../../code/lib/core-common::hash=55d292&locator=portable-stories-nextjs%40workspace%3A." dependencies: "@storybook/core-events": "workspace:*" "@storybook/csf-tools": "workspace:*" @@ -2773,7 +2777,7 @@ __metadata: tiny-invariant: "npm:^1.3.1" ts-dedent: "npm:^2.0.0" util: "npm:^0.12.4" - checksum: 10/5a82e69805fd612b9bb7eaf370d93bbadf34f18401b96b16a1b33eaf8dc9c472fa143ab9dc62f0ca82c27555ac73245bb0fc681264d3485a281132de98458089 + checksum: 10/e9308c3683651e2c925823e4bee0f91156ba544b0dc72ee6291810e7bd2e17690bce3f426c10d942fd5e18eb545589e5a3347cd02eb6ecceb058dad7a56bb775 languageName: node linkType: hard @@ -3239,12 +3243,12 @@ __metadata: "@storybook/types@file:../../../code/lib/types::locator=portable-stories-nextjs%40workspace%3A.": version: 8.1.0-alpha.5 - resolution: "@storybook/types@file:../../../code/lib/types#../../../code/lib/types::hash=22b151&locator=portable-stories-nextjs%40workspace%3A." + resolution: "@storybook/types@file:../../../code/lib/types#../../../code/lib/types::hash=0524c9&locator=portable-stories-nextjs%40workspace%3A." dependencies: "@storybook/channels": "workspace:*" "@types/express": "npm:^4.7.0" file-system-cache: "npm:2.3.0" - checksum: 10/9029701cc4326e000e4f2866601633e54c3167bf5363c97b5190cec9ea762750dcbbc09767695a225bf0804cff6d0e8b9eb55143ef9207798800c6c1dfe7856a + checksum: 10/b2835c9386c22e535e62263fe03ead9c43a1c9762b6524ed8a9b1954887e8853311d580caa7711d57a1eecc9ce30cd7cfd9d814a45723e8434397b7adced1871 languageName: node linkType: hard diff --git a/test-storybooks/portable-stories-kitchen-sink/react/yarn.lock b/test-storybooks/portable-stories-kitchen-sink/react/yarn.lock index ca0b7a5ede87..e56c0e31080a 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/yarn.lock +++ b/test-storybooks/portable-stories-kitchen-sink/react/yarn.lock @@ -2583,6 +2583,10 @@ __metadata: resolution: "@storybook/addon-controls@portal:../../../code/addons/controls::locator=portable-stories-react%40workspace%3A." dependencies: "@storybook/blocks": "workspace:*" + "@storybook/core-common": "workspace:*" + cjs-module-lexer: "npm:^1.2.3" + es-module-lexer: "npm:^1.5.0" + globby: "npm:^14.0.1" lodash: "npm:^4.17.21" ts-dedent: "npm:^2.0.0" languageName: node @@ -5117,7 +5121,7 @@ __metadata: languageName: node linkType: hard -"cjs-module-lexer@npm:^1.0.0": +"cjs-module-lexer@npm:^1.0.0, cjs-module-lexer@npm:^1.2.3": version: 1.2.3 resolution: "cjs-module-lexer@npm:1.2.3" checksum: 10/f96a5118b0a012627a2b1c13bd2fcb92509778422aaa825c5da72300d6dcadfb47134dd2e9d97dfa31acd674891dd91642742772d19a09a8adc3e56bd2f5928c diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/yarn.lock b/test-storybooks/portable-stories-kitchen-sink/svelte/yarn.lock index c741bf702794..24ddbf299123 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/yarn.lock +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/yarn.lock @@ -2217,6 +2217,10 @@ __metadata: resolution: "@storybook/addon-controls@portal:../../../code/addons/controls::locator=portable-stories-svelte%40workspace%3A." dependencies: "@storybook/blocks": "workspace:*" + "@storybook/core-common": "workspace:*" + cjs-module-lexer: "npm:^1.2.3" + es-module-lexer: "npm:^1.5.0" + globby: "npm:^14.0.1" lodash: "npm:^4.17.21" ts-dedent: "npm:^2.0.0" languageName: node @@ -4110,6 +4114,13 @@ __metadata: languageName: node linkType: hard +"cjs-module-lexer@npm:^1.2.3": + version: 1.2.3 + resolution: "cjs-module-lexer@npm:1.2.3" + checksum: 10/f96a5118b0a012627a2b1c13bd2fcb92509778422aaa825c5da72300d6dcadfb47134dd2e9d97dfa31acd674891dd91642742772d19a09a8adc3e56bd2f5928c + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/yarn.lock b/test-storybooks/portable-stories-kitchen-sink/vue3/yarn.lock index 6a3a4679f469..bab162b80aac 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/yarn.lock +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/yarn.lock @@ -2249,6 +2249,10 @@ __metadata: resolution: "@storybook/addon-controls@portal:../../../code/addons/controls::locator=portable-stories-vue3%40workspace%3A." dependencies: "@storybook/blocks": "workspace:*" + "@storybook/core-common": "workspace:*" + cjs-module-lexer: "npm:^1.2.3" + es-module-lexer: "npm:^1.5.0" + globby: "npm:^14.0.1" lodash: "npm:^4.17.21" ts-dedent: "npm:^2.0.0" languageName: node @@ -4473,6 +4477,13 @@ __metadata: languageName: node linkType: hard +"cjs-module-lexer@npm:^1.2.3": + version: 1.2.3 + resolution: "cjs-module-lexer@npm:1.2.3" + checksum: 10/f96a5118b0a012627a2b1c13bd2fcb92509778422aaa825c5da72300d6dcadfb47134dd2e9d97dfa31acd674891dd91642742772d19a09a8adc3e56bd2f5928c + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0"