From 74b74344f363a95e2916bf6bd21665acdbedeba6 Mon Sep 17 00:00:00 2001 From: John Williams Metservice <83933275+jwilliams-met@users.noreply.github.com> Date: Wed, 27 Mar 2024 17:58:57 +1300 Subject: [PATCH 01/78] Bump webpack-dev-middleware to patch high security issue webpack-dev-middleware 6.1.1 contains logged high risk, https://github.com/webpack/webpack-dev-middleware/security/advisories/GHSA-wr3j-pwj9-hqq6 --- code/builders/builder-webpack5/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/builders/builder-webpack5/package.json b/code/builders/builder-webpack5/package.json index 00f9c9c1b5e9..5b767dc9389a 100644 --- a/code/builders/builder-webpack5/package.json +++ b/code/builders/builder-webpack5/package.json @@ -94,7 +94,7 @@ "util": "^0.12.4", "util-deprecate": "^1.0.2", "webpack": "5", - "webpack-dev-middleware": "^6.1.1", + "webpack-dev-middleware": "^6.1.2", "webpack-hot-middleware": "^2.25.1", "webpack-virtual-modules": "^0.5.0" }, From e7e74423f440970b8b87f2703a773bac2519b505 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 27 Mar 2024 07:53:15 +0100 Subject: [PATCH 02/78] Update yarn.lock --- code/yarn.lock | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/code/yarn.lock b/code/yarn.lock index f4deb1517325..1b022371574c 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5348,7 +5348,7 @@ __metadata: util: "npm:^0.12.4" util-deprecate: "npm:^1.0.2" webpack: "npm:5" - webpack-dev-middleware: "npm:^6.1.1" + webpack-dev-middleware: "npm:^6.1.2" webpack-hot-middleware: "npm:^2.25.1" webpack-virtual-modules: "npm:^0.5.0" peerDependenciesMeta: @@ -28914,7 +28914,7 @@ __metadata: languageName: node linkType: hard -"webpack-dev-middleware@npm:6.1.1, webpack-dev-middleware@npm:^6.1.1": +"webpack-dev-middleware@npm:6.1.1": version: 6.1.1 resolution: "webpack-dev-middleware@npm:6.1.1" dependencies: @@ -28947,6 +28947,24 @@ __metadata: languageName: node linkType: hard +"webpack-dev-middleware@npm:^6.1.2": + version: 6.1.2 + resolution: "webpack-dev-middleware@npm:6.1.2" + dependencies: + colorette: "npm:^2.0.10" + memfs: "npm:^3.4.12" + mime-types: "npm:^2.1.31" + range-parser: "npm:^1.2.1" + schema-utils: "npm:^4.0.0" + peerDependencies: + webpack: ^5.0.0 + peerDependenciesMeta: + webpack: + optional: true + checksum: 10c0/90c415a770c7db493f4a7d8f3308d761ff63249f628fa8a133eac5a61e849cdf658398e189fc2d95ce0ea884641363f964db6b269c6cea877765321dd7f14b9a + languageName: node + linkType: hard + "webpack-dev-server@npm:4.15.1": version: 4.15.1 resolution: "webpack-dev-server@npm:4.15.1" From 6ea5bd899fbc0c82699637aeb4eac20a61de9e73 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 4 Apr 2024 16:12:10 +0200 Subject: [PATCH 03/78] Make mocks reactive, and listen to them in addon actions --- code/addons/actions/src/loaders.ts | 38 +++++++++++----------------- code/lib/test/package.json | 1 + code/lib/test/src/index.ts | 4 ++- code/lib/test/src/spy.ts | 40 ++++++++++++++++++++++++++++-- code/yarn.lock | 1 + 5 files changed, 57 insertions(+), 27 deletions(-) diff --git a/code/addons/actions/src/loaders.ts b/code/addons/actions/src/loaders.ts index 3acfa9795eef..356fb51adecb 100644 --- a/code/addons/actions/src/loaders.ts +++ b/code/addons/actions/src/loaders.ts @@ -1,36 +1,26 @@ /* eslint-disable no-underscore-dangle */ import type { LoaderFunction } from '@storybook/types'; +import { global } from '@storybook/global'; +import type { onMockCalled as onMockCalledType } from '@storybook/test'; import { action } from './runtime'; -export const tinySpyInternalState = Symbol.for('tinyspy:spy'); +let subscribed = false; -const attachActionsToFunctionMocks: LoaderFunction = (context) => { +const logActionsWhenMockCalled: LoaderFunction = (context) => { const { - args, parameters: { actions }, } = context; if (actions?.disable) return; - Object.entries(args) - .filter( - ([, value]) => - typeof value === 'function' && '_isMockFunction' in value && value._isMockFunction - ) - .forEach(([key, value]) => { - // See this discussion for context: - // https://github.com/vitest-dev/vitest/pull/5352 - const previous = - value.getMockImplementation() ?? - (tinySpyInternalState in value ? value[tinySpyInternalState]?.getOriginal() : undefined); - if (previous?._actionAttached !== true && previous?.isAction !== true) { - const implementation = (...params: unknown[]) => { - action(key)(...params); - return previous?.(...params); - }; - implementation._actionAttached = true; - value.mockImplementation(implementation); - } - }); + if ( + !subscribed && + '__STORYBOOK_TEST_ON_MOCK_CALLED__' in global && + typeof global.__STORYBOOK_TEST_ON_MOCK_CALLED__ === 'function' + ) { + const onMockCalled = global.__STORYBOOK_TEST_ON_MOCK_CALLED__ as typeof onMockCalledType; + onMockCalled((mock, args) => action(mock.getMockName())(args)); + subscribed = true; + } }; -export const loaders: LoaderFunction[] = [attachActionsToFunctionMocks]; +export const loaders: LoaderFunction[] = [logActionsWhenMockCalled]; diff --git a/code/lib/test/package.json b/code/lib/test/package.json index 81c72b9980d5..73554c492350 100644 --- a/code/lib/test/package.json +++ b/code/lib/test/package.json @@ -56,6 +56,7 @@ "util": "^0.12.4" }, "devDependencies": { + "tinyspy": "^2.2.0", "ts-dedent": "^2.2.0", "type-fest": "~2.19", "typescript": "^5.3.2" diff --git a/code/lib/test/src/index.ts b/code/lib/test/src/index.ts index 7bd72666f341..7ef1f5f312dd 100644 --- a/code/lib/test/src/index.ts +++ b/code/lib/test/src/index.ts @@ -3,7 +3,7 @@ import { type LoaderFunction } from '@storybook/csf'; import chai from 'chai'; import { global } from '@storybook/global'; import { expect as rawExpect } from './expect'; -import { clearAllMocks, resetAllMocks, restoreAllMocks } from './spy'; +import { clearAllMocks, onMockCalled, resetAllMocks, restoreAllMocks } from './spy'; export * from './spy'; @@ -39,3 +39,5 @@ const resetAllMocksLoader: LoaderFunction = ({ parameters }) => { // We are using this as a default Storybook loader, when the test package is used. This avoids the need for optional peer dependency workarounds. // eslint-disable-next-line no-underscore-dangle (global as any).__STORYBOOK_TEST_LOADERS__ = [resetAllMocksLoader]; +// eslint-disable-next-line no-underscore-dangle +(global as any).__STORYBOOK_TEST_ON_MOCK_CALLED__ = onMockCalled; diff --git a/code/lib/test/src/spy.ts b/code/lib/test/src/spy.ts index 3208df77ae76..124a3477a29e 100644 --- a/code/lib/test/src/spy.ts +++ b/code/lib/test/src/spy.ts @@ -1,17 +1,53 @@ +/* eslint-disable @typescript-eslint/no-shadow */ +import type { Mock } from '@vitest/spy'; import { spyOn, isMockFunction, - fn, + fn as vitestFn, mocks, type MaybeMocked, type MaybeMockedDeep, type MaybePartiallyMocked, type MaybePartiallyMockedDeep, } from '@vitest/spy'; +import type { SpyInternalImpl } from 'tinyspy'; +import * as tinyspy from 'tinyspy'; export type * from '@vitest/spy'; -export { spyOn, isMockFunction, fn, mocks }; +export { spyOn, isMockFunction, mocks }; + +type Listener = (mock: Mock, args: unknown[]) => void; +let listeners: Listener[] = []; + +export function onMockCalled(callback: Listener): () => void { + listeners = [...listeners, callback]; + return () => { + listeners = listeners.filter((listener) => listener !== callback); + }; +} + +export function fn(): Mock; +export function fn( + implementation: (...args: TArgs) => R +): Mock; +export function fn(implementation?: (...args: TArgs) => R) { + const mock = implementation ? vitestFn(implementation) : vitestFn(); + const reactive = reactiveMock(mock); + const originalMockImplementation = reactive.mockImplementation.bind(null); + reactive.mockImplementation = (fn) => reactiveMock(originalMockImplementation(fn)); + return reactive; +} + +function reactiveMock(mock: Mock) { + const state = tinyspy.getInternalState(mock as unknown as SpyInternalImpl); + const impl = state.impl?.bind(null); + state.willCall((...args) => { + listeners.forEach((listener) => listener(mock, args)); + impl?.(...args); + }); + return mock; +} /** * Calls [`.mockClear()`](https://vitest.dev/api/mock#mockclear) on every mocked function. This will only empty `.mock` state, it will not reset implementation. diff --git a/code/yarn.lock b/code/yarn.lock index 472dd503b241..e2aa602d6f4c 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6667,6 +6667,7 @@ __metadata: "@vitest/expect": "npm:1.3.1" "@vitest/spy": "npm:^1.3.1" chai: "npm:^4.4.1" + tinyspy: "npm:^2.2.0" ts-dedent: "npm:^2.2.0" type-fest: "npm:~2.19" typescript: "npm:^5.3.2" From d2ad4a011cb8d9230388c40ae1d6ef9d975ecd5a Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 4 Apr 2024 16:34:04 +0200 Subject: [PATCH 04/78] Add test --- code/lib/test/src/spy.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 code/lib/test/src/spy.test.ts diff --git a/code/lib/test/src/spy.test.ts b/code/lib/test/src/spy.test.ts new file mode 100644 index 000000000000..54c287dcaf01 --- /dev/null +++ b/code/lib/test/src/spy.test.ts @@ -0,0 +1,15 @@ +import { it, vi, expect, beforeEach } from 'vitest'; +import { fn, onMockCalled } from './spy'; + +const vitestSpy = vi.fn(); + +beforeEach(() => { + const unsubscribe = onMockCalled(vitestSpy); + return () => unsubscribe(); +}); + +it('mocks are reactive', () => { + const storybookSpy = fn(); + storybookSpy(1); + expect(vitestSpy).toHaveBeenCalledWith(storybookSpy, [1]); +}); From 7b5f532662677021c283dcbe7de74f135ceee866 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 4 Apr 2024 16:37:51 +0200 Subject: [PATCH 05/78] Fix bug --- code/lib/test/src/spy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/test/src/spy.ts b/code/lib/test/src/spy.ts index 124a3477a29e..51742262b1e4 100644 --- a/code/lib/test/src/spy.ts +++ b/code/lib/test/src/spy.ts @@ -44,7 +44,7 @@ function reactiveMock(mock: Mock) { const impl = state.impl?.bind(null); state.willCall((...args) => { listeners.forEach((listener) => listener(mock, args)); - impl?.(...args); + return impl?.(...args); }); return mock; } From afdfd038b2c0148c7bdc340ec79e45e9c6ce462d Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 4 Apr 2024 16:41:07 +0200 Subject: [PATCH 06/78] Change name --- code/addons/actions/src/loaders.ts | 10 +++++----- code/lib/test/src/index.ts | 4 ++-- code/lib/test/src/spy.test.ts | 4 ++-- code/lib/test/src/spy.ts | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/code/addons/actions/src/loaders.ts b/code/addons/actions/src/loaders.ts index 356fb51adecb..d49a048da231 100644 --- a/code/addons/actions/src/loaders.ts +++ b/code/addons/actions/src/loaders.ts @@ -1,7 +1,7 @@ /* eslint-disable no-underscore-dangle */ import type { LoaderFunction } from '@storybook/types'; import { global } from '@storybook/global'; -import type { onMockCalled as onMockCalledType } from '@storybook/test'; +import type { onMockCall as onMockCallType } from '@storybook/test'; import { action } from './runtime'; let subscribed = false; @@ -14,11 +14,11 @@ const logActionsWhenMockCalled: LoaderFunction = (context) => { if ( !subscribed && - '__STORYBOOK_TEST_ON_MOCK_CALLED__' in global && - typeof global.__STORYBOOK_TEST_ON_MOCK_CALLED__ === 'function' + '__STORYBOOK_TEST_ON_MOCK_CALL__' in global && + typeof global.__STORYBOOK_TEST_ON_MOCK_CALL__ === 'function' ) { - const onMockCalled = global.__STORYBOOK_TEST_ON_MOCK_CALLED__ as typeof onMockCalledType; - onMockCalled((mock, args) => action(mock.getMockName())(args)); + const onMockCall = global.__STORYBOOK_TEST_ON_MOCK_CALL__ as typeof onMockCallType; + onMockCall((mock, args) => action(mock.getMockName())(args)); subscribed = true; } }; diff --git a/code/lib/test/src/index.ts b/code/lib/test/src/index.ts index 7ef1f5f312dd..3ddf1fcd5687 100644 --- a/code/lib/test/src/index.ts +++ b/code/lib/test/src/index.ts @@ -3,7 +3,7 @@ import { type LoaderFunction } from '@storybook/csf'; import chai from 'chai'; import { global } from '@storybook/global'; import { expect as rawExpect } from './expect'; -import { clearAllMocks, onMockCalled, resetAllMocks, restoreAllMocks } from './spy'; +import { clearAllMocks, onMockCall, resetAllMocks, restoreAllMocks } from './spy'; export * from './spy'; @@ -40,4 +40,4 @@ const resetAllMocksLoader: LoaderFunction = ({ parameters }) => { // eslint-disable-next-line no-underscore-dangle (global as any).__STORYBOOK_TEST_LOADERS__ = [resetAllMocksLoader]; // eslint-disable-next-line no-underscore-dangle -(global as any).__STORYBOOK_TEST_ON_MOCK_CALLED__ = onMockCalled; +(global as any).__STORYBOOK_TEST_ON_MOCK_CALL__ = onMockCall; diff --git a/code/lib/test/src/spy.test.ts b/code/lib/test/src/spy.test.ts index 54c287dcaf01..5c3dcf13d924 100644 --- a/code/lib/test/src/spy.test.ts +++ b/code/lib/test/src/spy.test.ts @@ -1,10 +1,10 @@ import { it, vi, expect, beforeEach } from 'vitest'; -import { fn, onMockCalled } from './spy'; +import { fn, onMockCall } from './spy'; const vitestSpy = vi.fn(); beforeEach(() => { - const unsubscribe = onMockCalled(vitestSpy); + const unsubscribe = onMockCall(vitestSpy); return () => unsubscribe(); }); diff --git a/code/lib/test/src/spy.ts b/code/lib/test/src/spy.ts index 51742262b1e4..ce696dc832ff 100644 --- a/code/lib/test/src/spy.ts +++ b/code/lib/test/src/spy.ts @@ -20,7 +20,7 @@ export { spyOn, isMockFunction, mocks }; type Listener = (mock: Mock, args: unknown[]) => void; let listeners: Listener[] = []; -export function onMockCalled(callback: Listener): () => void { +export function onMockCall(callback: Listener): () => void { listeners = [...listeners, callback]; return () => { listeners = listeners.filter((listener) => listener !== callback); From 0dc9e5813c308f979787e7aeccd6439be417657e Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 4 Apr 2024 17:04:24 +0200 Subject: [PATCH 07/78] Also make spyOn reactive --- code/lib/test/src/spy.ts | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/code/lib/test/src/spy.ts b/code/lib/test/src/spy.ts index ce696dc832ff..a8fa2b935b1c 100644 --- a/code/lib/test/src/spy.ts +++ b/code/lib/test/src/spy.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-shadow */ -import type { Mock } from '@vitest/spy'; +import type { Mock, MockInstance } from '@vitest/spy'; import { - spyOn, + spyOn as vitestSpyOn, isMockFunction, fn as vitestFn, mocks, @@ -15,9 +15,9 @@ import * as tinyspy from 'tinyspy'; export type * from '@vitest/spy'; -export { spyOn, isMockFunction, mocks }; +export { isMockFunction, mocks }; -type Listener = (mock: Mock, args: unknown[]) => void; +type Listener = (mock: MockInstance, args: unknown[]) => void; let listeners: Listener[] = []; export function onMockCall(callback: Listener): () => void { @@ -27,19 +27,29 @@ export function onMockCall(callback: Listener): () => void { }; } +// @ts-expect-error TS is hard you know +export const spyOn: typeof vitestSpyOn = (...args) => { + const mock = vitestSpyOn(...(args as Parameters)); + return reactiveMock(mock); +}; + export function fn(): Mock; export function fn( implementation: (...args: TArgs) => R ): Mock; export function fn(implementation?: (...args: TArgs) => R) { const mock = implementation ? vitestFn(implementation) : vitestFn(); - const reactive = reactiveMock(mock); + return reactiveMock(mock); +} + +function reactiveMock(mock: MockInstance) { + const reactive = listenWhenCalled(mock); const originalMockImplementation = reactive.mockImplementation.bind(null); - reactive.mockImplementation = (fn) => reactiveMock(originalMockImplementation(fn)); + reactive.mockImplementation = (fn) => listenWhenCalled(originalMockImplementation(fn)); return reactive; } -function reactiveMock(mock: Mock) { +function listenWhenCalled(mock: MockInstance) { const state = tinyspy.getInternalState(mock as unknown as SpyInternalImpl); const impl = state.impl?.bind(null); state.willCall((...args) => { From e69fa60f1acf877703fcf048be8f52be247c71ce Mon Sep 17 00:00:00 2001 From: Lars Douwe Schuitema Date: Thu, 4 Apr 2024 17:04:35 +0200 Subject: [PATCH 08/78] docs: set correct dependency in import example --- docs/snippets/vue/vue3-vite-add-framework.ts.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/snippets/vue/vue3-vite-add-framework.ts.mdx b/docs/snippets/vue/vue3-vite-add-framework.ts.mdx index c02ed2ed0b3b..404f53f5cc47 100644 --- a/docs/snippets/vue/vue3-vite-add-framework.ts.mdx +++ b/docs/snippets/vue/vue3-vite-add-framework.ts.mdx @@ -1,6 +1,6 @@ ```ts // .storybook/main.ts -import { StorybookConfig } from '@storybook/nextjs'; +import { StorybookConfig } from '@storybook/vue3-vite'; const config: StorybookConfig = { // ... From c0ad197f0107653859beac2e770fa48e3dc13422 Mon Sep 17 00:00:00 2001 From: Lars Rickert Date: Thu, 28 Mar 2024 09:16:49 +0100 Subject: [PATCH 09/78] fix(vue): disable controls for events, slots and expose --- .../extractArgTypes.test.ts.snap | 120 +++--------------- .../vue3/src/docs/extractArgTypes.ts | 9 +- 2 files changed, 20 insertions(+), 109 deletions(-) diff --git a/code/renderers/vue3/src/docs/__snapshots__/extractArgTypes.test.ts.snap b/code/renderers/vue3/src/docs/__snapshots__/extractArgTypes.test.ts.snap index cbdcb9f51226..edb1e5751dc1 100644 --- a/code/renderers/vue3/src/docs/__snapshots__/extractArgTypes.test.ts.snap +++ b/code/renderers/vue3/src/docs/__snapshots__/extractArgTypes.test.ts.snap @@ -4,7 +4,7 @@ exports[`extractArgTypes (vue-docgen-api) > should extract events for Vue compon { "bar": { "control": { - "disabled": true, + "disable": true, }, "description": "", "name": "bar", @@ -21,7 +21,7 @@ exports[`extractArgTypes (vue-docgen-api) > should extract events for Vue compon }, "baz": { "control": { - "disabled": true, + "disable": true, }, "description": "", "name": "baz", @@ -38,7 +38,7 @@ exports[`extractArgTypes (vue-docgen-api) > should extract events for Vue compon }, "foo": { "control": { - "disabled": true, + "disable": true, }, "description": "", "name": "foo", @@ -60,7 +60,7 @@ exports[`extractArgTypes (vue-docgen-api) > should extract events for component { "bar": { "control": { - "disabled": true, + "disable": true, }, "description": "Test description bar", "name": "bar", @@ -80,7 +80,7 @@ exports[`extractArgTypes (vue-docgen-api) > should extract events for component }, "baz": { "control": { - "disabled": true, + "disable": true, }, "description": "Test description baz", "name": "baz", @@ -103,7 +103,7 @@ exports[`extractArgTypes (vue-docgen-api) > should extract expose for component { "count": { "control": { - "disabled": true, + "disable": true, }, "description": "a count number", "name": "count", @@ -120,7 +120,7 @@ exports[`extractArgTypes (vue-docgen-api) > should extract expose for component }, "label": { "control": { - "disabled": true, + "disable": true, }, "description": "a label string", "name": "label", @@ -141,9 +141,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract expose for component exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1`] = ` { "array": { - "control": { - "disabled": false, - }, "defaultValue": undefined, "description": "description required array object", "name": "array", @@ -170,9 +167,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1 }, }, "arrayOptional": { - "control": { - "disabled": false, - }, "defaultValue": undefined, "description": "description optional array object", "name": "arrayOptional", @@ -199,9 +193,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1 }, }, "bar": { - "control": { - "disabled": false, - }, "defaultValue": { "summary": "1", }, @@ -222,9 +213,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1 }, }, "baz": { - "control": { - "disabled": false, - }, "defaultValue": undefined, "description": "description baz is required boolean", "name": "baz", @@ -241,9 +229,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1 }, }, "enumValue": { - "control": { - "disabled": false, - }, "defaultValue": undefined, "description": "description enum value", "name": "enumValue", @@ -265,9 +250,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1 }, }, "foo": { - "control": { - "disabled": false, - }, "defaultValue": undefined, "description": "@default: "rounded"
@since: v1.0.0
@see: https://vuejs.org/
@deprecated: v1.1.0

string foo", "name": "foo", @@ -284,9 +266,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1 }, }, "inlined": { - "control": { - "disabled": false, - }, "defaultValue": undefined, "description": "", "name": "inlined", @@ -309,9 +288,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1 }, }, "literalFromContext": { - "control": { - "disabled": false, - }, "defaultValue": undefined, "description": "description literal type alias that require context", "name": "literalFromContext", @@ -336,9 +312,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1 }, }, "nested": { - "control": { - "disabled": false, - }, "defaultValue": undefined, "description": "description nested is required nested object", "name": "nested", @@ -361,9 +334,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1 }, }, "nestedIntersection": { - "control": { - "disabled": false, - }, "defaultValue": undefined, "description": "description required nested object with intersection", "name": "nestedIntersection", @@ -390,9 +360,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1 }, }, "nestedOptional": { - "control": { - "disabled": false, - }, "defaultValue": undefined, "description": "description optional nested object", "name": "nestedOptional", @@ -431,9 +398,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1 }, }, "recursive": { - "control": { - "disabled": false, - }, "defaultValue": undefined, "description": "", "name": "recursive", @@ -457,9 +421,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1 }, }, "stringArray": { - "control": { - "disabled": false, - }, "defaultValue": { "summary": "["foo", "bar"]", }, @@ -484,9 +445,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1 }, }, "union": { - "control": { - "disabled": false, - }, "defaultValue": undefined, "description": "description union is required union type", "name": "union", @@ -513,9 +471,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1 }, }, "unionOptional": { - "control": { - "disabled": false, - }, "defaultValue": undefined, "description": "description unionOptional is optional union type", "name": "unionOptional", @@ -551,9 +506,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 1 exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2`] = ` { "array": { - "control": { - "disabled": false, - }, "description": "description required array object", "name": "array", "table": { @@ -571,9 +523,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2 }, }, "arrayOptional": { - "control": { - "disabled": false, - }, "description": "description optional array object", "name": "arrayOptional", "table": { @@ -591,9 +540,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2 }, }, "bar": { - "control": { - "disabled": false, - }, "description": "description bar is optional number", "name": "bar", "table": { @@ -612,9 +558,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2 }, }, "baz": { - "control": { - "disabled": false, - }, "description": "description baz is required boolean", "name": "baz", "table": { @@ -631,9 +574,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2 }, }, "enumValue": { - "control": { - "disabled": false, - }, "description": "description enum value", "name": "enumValue", "table": { @@ -651,9 +591,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2 }, }, "foo": { - "control": { - "disabled": false, - }, "description": "string foo", "name": "foo", "table": { @@ -670,9 +607,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2 }, }, "inlined": { - "control": { - "disabled": false, - }, "description": undefined, "name": "inlined", "table": { @@ -690,9 +624,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2 }, }, "literalFromContext": { - "control": { - "disabled": false, - }, "description": "description literal type alias that require context", "name": "literalFromContext", "table": { @@ -710,9 +641,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2 }, }, "nested": { - "control": { - "disabled": false, - }, "description": "description nested is required nested object", "name": "nested", "table": { @@ -730,9 +658,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2 }, }, "nestedIntersection": { - "control": { - "disabled": false, - }, "description": "description required nested object with intersection", "name": "nestedIntersection", "table": { @@ -755,9 +680,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2 }, }, "nestedOptional": { - "control": { - "disabled": false, - }, "description": "description optional nested object", "name": "nestedOptional", "table": { @@ -784,9 +706,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2 }, }, "recursive": { - "control": { - "disabled": false, - }, "description": undefined, "name": "recursive", "table": { @@ -804,9 +723,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2 }, }, "stringArray": { - "control": { - "disabled": false, - }, "description": "description stringArray is string array", "name": "stringArray", "table": { @@ -826,9 +742,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2 }, }, "union": { - "control": { - "disabled": false, - }, "description": "description union is required union type", "name": "union", "table": { @@ -853,9 +766,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2 }, }, "unionOptional": { - "control": { - "disabled": false, - }, "description": "description unionOptional is optional union type", "name": "unionOptional", "table": { @@ -889,7 +799,7 @@ exports[`extractArgTypes (vue-docgen-api) > should extract slots for component 1 { "default": { "control": { - "disabled": false, + "disable": true, }, "description": undefined, "name": "default", @@ -909,7 +819,7 @@ exports[`extractArgTypes (vue-docgen-api) > should extract slots for component 1 }, "named": { "control": { - "disabled": false, + "disable": true, }, "description": undefined, "name": "named", @@ -929,7 +839,7 @@ exports[`extractArgTypes (vue-docgen-api) > should extract slots for component 1 }, "no-bind": { "control": { - "disabled": false, + "disable": true, }, "description": undefined, "name": "no-bind", @@ -947,7 +857,7 @@ exports[`extractArgTypes (vue-docgen-api) > should extract slots for component 1 }, "vbind": { "control": { - "disabled": false, + "disable": true, }, "description": undefined, "name": "vbind", @@ -972,7 +882,7 @@ exports[`extractArgTypes (vue-docgen-api) > should extract slots type for Vue co { "default": { "control": { - "disabled": false, + "disable": true, }, "description": "", "name": "default", @@ -989,7 +899,7 @@ exports[`extractArgTypes (vue-docgen-api) > should extract slots type for Vue co }, "named": { "control": { - "disabled": false, + "disable": true, }, "description": "", "name": "named", @@ -1006,7 +916,7 @@ exports[`extractArgTypes (vue-docgen-api) > should extract slots type for Vue co }, "no-bind": { "control": { - "disabled": false, + "disable": true, }, "description": "", "name": "no-bind", @@ -1023,7 +933,7 @@ exports[`extractArgTypes (vue-docgen-api) > should extract slots type for Vue co }, "vbind": { "control": { - "disabled": false, + "disable": true, }, "description": "", "name": "vbind", diff --git a/code/renderers/vue3/src/docs/extractArgTypes.ts b/code/renderers/vue3/src/docs/extractArgTypes.ts index 151476491c95..4bc29dcbc0df 100644 --- a/code/renderers/vue3/src/docs/extractArgTypes.ts +++ b/code/renderers/vue3/src/docs/extractArgTypes.ts @@ -44,10 +44,11 @@ export const extractArgTypes: ArgTypesExtractor = (component) => { // skip duplicate and global props if (!argType || argTypes[argType.name]) return; - argTypes[argType.name] = { - ...argType, - control: { disabled: !['props', 'slots'].includes(section) }, - }; + if (section !== 'props') { + argType.control = { disable: true }; + } + + argTypes[argType.name] = argType; }); }); From 771691d486865dd67f378270e4794b3a3e0013df Mon Sep 17 00:00:00 2001 From: Lars Rickert Date: Fri, 29 Mar 2024 21:25:31 +0100 Subject: [PATCH 10/78] only disable events and exposed --- code/renderers/vue3/src/docs/extractArgTypes.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/renderers/vue3/src/docs/extractArgTypes.ts b/code/renderers/vue3/src/docs/extractArgTypes.ts index 4bc29dcbc0df..d466cea2e1cd 100644 --- a/code/renderers/vue3/src/docs/extractArgTypes.ts +++ b/code/renderers/vue3/src/docs/extractArgTypes.ts @@ -44,7 +44,9 @@ export const extractArgTypes: ArgTypesExtractor = (component) => { // skip duplicate and global props if (!argType || argTypes[argType.name]) return; - if (section !== 'props') { + // disable controls for events and exposed since they can not be controlled + const sectionsToDisableControls: (typeof section)[] = ['events', 'expose', 'exposed']; + if (sectionsToDisableControls.includes(section)) { argType.control = { disable: true }; } From 4c42d2196614befa0e1b5c641c2237dd2546e903 Mon Sep 17 00:00:00 2001 From: Lars Rickert Date: Fri, 29 Mar 2024 21:27:37 +0100 Subject: [PATCH 11/78] update vitest snapshots --- .../extractArgTypes.test.ts.snap | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/code/renderers/vue3/src/docs/__snapshots__/extractArgTypes.test.ts.snap b/code/renderers/vue3/src/docs/__snapshots__/extractArgTypes.test.ts.snap index edb1e5751dc1..164f338725e3 100644 --- a/code/renderers/vue3/src/docs/__snapshots__/extractArgTypes.test.ts.snap +++ b/code/renderers/vue3/src/docs/__snapshots__/extractArgTypes.test.ts.snap @@ -798,9 +798,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract props for component 2 exports[`extractArgTypes (vue-docgen-api) > should extract slots for component 1`] = ` { "default": { - "control": { - "disable": true, - }, "description": undefined, "name": "default", "table": { @@ -818,9 +815,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract slots for component 1 }, }, "named": { - "control": { - "disable": true, - }, "description": undefined, "name": "named", "table": { @@ -838,9 +832,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract slots for component 1 }, }, "no-bind": { - "control": { - "disable": true, - }, "description": undefined, "name": "no-bind", "table": { @@ -856,9 +847,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract slots for component 1 }, }, "vbind": { - "control": { - "disable": true, - }, "description": undefined, "name": "vbind", "table": { @@ -881,9 +869,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract slots for component 1 exports[`extractArgTypes (vue-docgen-api) > should extract slots type for Vue component 1`] = ` { "default": { - "control": { - "disable": true, - }, "description": "", "name": "default", "table": { @@ -898,9 +883,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract slots type for Vue co }, }, "named": { - "control": { - "disable": true, - }, "description": "", "name": "named", "table": { @@ -915,9 +897,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract slots type for Vue co }, }, "no-bind": { - "control": { - "disable": true, - }, "description": "", "name": "no-bind", "table": { @@ -932,9 +911,6 @@ exports[`extractArgTypes (vue-docgen-api) > should extract slots type for Vue co }, }, "vbind": { - "control": { - "disable": true, - }, "description": "", "name": "vbind", "table": { From c8f70900b19131e4e606501ab38594daabd1bd48 Mon Sep 17 00:00:00 2001 From: storybook-bot <32066757+storybook-bot@users.noreply.github.com> Date: Fri, 5 Apr 2024 13:40:55 +0000 Subject: [PATCH 12/78] Update CHANGELOG.md for v8.0.6 [skip ci] --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2de9a1f7882f..61ad5d1f5e1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 8.0.6 + +- CLI: Add --config-dir flag to migrate command - [#26721](https://github.com/storybookjs/storybook/pull/26721), thanks @yannbf! +- Next.js: Fix next/font usage on Windows machines - [#26700](https://github.com/storybookjs/storybook/pull/26700), thanks @valentinpalkovic! +- Next.js: Support path aliases when no base url is set - [#26651](https://github.com/storybookjs/storybook/pull/26651), thanks @yannbf! +- Webpack: Fix sourcemap generation in webpack react-docgen-loader - [#26676](https://github.com/storybookjs/storybook/pull/26676), thanks @valentinpalkovic! + ## 8.0.5 - Addon-docs: Fix `react-dom/server` imports breaking stories and docs - [#26557](https://github.com/storybookjs/storybook/pull/26557), thanks @JReinhold! From df20fa82d26455c0d84b7dbf9816c4f40ff9065f Mon Sep 17 00:00:00 2001 From: Kyle Gach Date: Fri, 5 Apr 2024 11:27:05 -0600 Subject: [PATCH 13/78] Fix incorrect link --- code/addons/gfm/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/addons/gfm/README.md b/code/addons/gfm/README.md index 038d46c44d40..8245b7975b2b 100644 --- a/code/addons/gfm/README.md +++ b/code/addons/gfm/README.md @@ -6,6 +6,6 @@ The "@storybook/addon-mdx-gfm" addon is meant as a migration assistant for Story > This addon will likely be removed in a future version. It's recommended you read this document and follow its instructions instead: -https://storybook.js.org/docs/react/writing-docs/mdx#lack-of-github-flavored-markdown-gfm +https://storybook.js.org/docs/writing-docs/mdx#markdown-tables-arent-rendering-correctly Once you've made the necessary changes, you can remove the addon from your package.json and storybook config. From 27f10ba543bb8b368912304ec42bd7096964e1e4 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Mon, 8 Apr 2024 16:21:35 +0200 Subject: [PATCH 14/78] add VTA automigration --- code/lib/cli/src/automigrate/fixes/vta.ts | 60 +++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 code/lib/cli/src/automigrate/fixes/vta.ts diff --git a/code/lib/cli/src/automigrate/fixes/vta.ts b/code/lib/cli/src/automigrate/fixes/vta.ts new file mode 100644 index 000000000000..683c8d5fc540 --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/vta.ts @@ -0,0 +1,60 @@ +import { dedent } from 'ts-dedent'; +import { updateMainConfig } from '../helpers/mainConfigFile'; +import type { Fix } from '../types'; +import { getStorybookVersionSpecifier } from '../../helpers'; + +const logger = console; + +interface Options { + value?: boolean; +} + +/** + */ +export const vta: Fix = { + id: 'visual-testing-addon', + + versionRange: ['<7', '>=7'], + + async check({ mainConfig }) { + const hadAddonInstalled = !!mainConfig?.addons?.find((addon) => + addon.toString().includes('@chromatic-com/storybook') + ); + + // @ts-expect-error (user might be upgrading from an older version that still had it) + const usesMDX1 = mainConfig?.features?.legacyMdx1 === true || false; + const skip = hadAddonInstalled; + + if (skip) { + return null; + } + + return {}; + }, + + prompt() { + return dedent` + We've detected that you're not yet using the Visual Testing Addon. Would you like to add it? + `; + }, + + async run({ packageManager, dryRun, mainConfigPath, skipInstall }) { + if (!dryRun) { + const packageJson = await packageManager.retrievePackageJson(); + const versionToInstall = getStorybookVersionSpecifier( + await packageManager.retrievePackageJson() + ); + await packageManager.addDependencies( + { installAsDevDependencies: true, skipInstall, packageJson }, + [`@storybook/addon-mdx-gfm@${versionToInstall}`] + ); + + await updateMainConfig({ mainConfigPath, dryRun: !!dryRun }, async (main) => { + logger.info(`✅ Adding "@chromatic-com/storybook" addon`); + if (!dryRun) { + main.appendValueToArray(['addons'], '@chromatic-dom/storybook'); + } + }); + } + }, +}; From c298abd2f8fa0e00ba3c17a3b81b64620c415471 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Mon, 8 Apr 2024 16:22:40 +0200 Subject: [PATCH 15/78] cleanup --- code/lib/cli/src/automigrate/fixes/vta.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/code/lib/cli/src/automigrate/fixes/vta.ts b/code/lib/cli/src/automigrate/fixes/vta.ts index 683c8d5fc540..837aa5867fb9 100644 --- a/code/lib/cli/src/automigrate/fixes/vta.ts +++ b/code/lib/cli/src/automigrate/fixes/vta.ts @@ -5,9 +5,7 @@ import { getStorybookVersionSpecifier } from '../../helpers'; const logger = console; -interface Options { - value?: boolean; -} +interface Options {} /** */ From 777433d8a8be71d69fe0effffb60f7fa3a72807b Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Mon, 8 Apr 2024 16:23:51 +0200 Subject: [PATCH 16/78] add to index --- code/lib/cli/src/automigrate/fixes/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/lib/cli/src/automigrate/fixes/index.ts b/code/lib/cli/src/automigrate/fixes/index.ts index 022074fa8301..72f3eb9a605d 100644 --- a/code/lib/cli/src/automigrate/fixes/index.ts +++ b/code/lib/cli/src/automigrate/fixes/index.ts @@ -26,6 +26,7 @@ import { removeJestTestingLibrary } from './remove-jest-testing-library'; import { addonsAPI } from './addons-api'; import { mdx1to3 } from './mdx-1-to-3'; import { addonPostCSS } from './addon-postcss'; +import { vta } from './vta'; import { upgradeStorybookRelatedDependencies } from './upgrade-storybook-related-dependencies'; export * from '../types'; @@ -58,6 +59,7 @@ export const allFixes: Fix[] = [ webpack5CompilerSetup, mdx1to3, upgradeStorybookRelatedDependencies, + vta, ]; export const initFixes: Fix[] = [eslintPlugin]; From ab38a3cf463eb87f876b3ce0c62601f18822d677 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Mon, 8 Apr 2024 16:29:30 +0200 Subject: [PATCH 17/78] "It should only run if beforeVersion is < 8.0.x where x is the version we release this in" --- code/lib/cli/src/automigrate/fixes/vta.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/cli/src/automigrate/fixes/vta.ts b/code/lib/cli/src/automigrate/fixes/vta.ts index 837aa5867fb9..cb274baaa7bc 100644 --- a/code/lib/cli/src/automigrate/fixes/vta.ts +++ b/code/lib/cli/src/automigrate/fixes/vta.ts @@ -12,7 +12,7 @@ interface Options {} export const vta: Fix = { id: 'visual-testing-addon', - versionRange: ['<7', '>=7'], + versionRange: ['<8.0.7', '>=7'], async check({ mainConfig }) { const hadAddonInstalled = !!mainConfig?.addons?.find((addon) => From 29078ce5820024c5e014ecce41646843f3342d4d Mon Sep 17 00:00:00 2001 From: Eric Berndt Date: Mon, 8 Apr 2024 17:11:36 +0000 Subject: [PATCH 18/78] CLI: Add --config-dir flag to add command --- code/lib/cli/src/add.ts | 17 +++++++++++++---- code/lib/cli/src/generate.ts | 1 + 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/code/lib/cli/src/add.ts b/code/lib/cli/src/add.ts index 19a4b552fcc0..c0bb071dda5b 100644 --- a/code/lib/cli/src/add.ts +++ b/code/lib/cli/src/add.ts @@ -48,6 +48,12 @@ const checkInstalled = (addonName: string, main: any) => { const isCoreAddon = (addonName: string) => Object.hasOwn(versions, addonName); +type CLIOptions = { + packageManager?: PackageManagerName; + configDir?: string; + skipPostinstall: boolean; +}; + /** * Install the given addon package and add it to main.js * @@ -61,15 +67,18 @@ const isCoreAddon = (addonName: string) => Object.hasOwn(versions, addonName); */ export async function add( addon: string, - options: { packageManager: PackageManagerName; skipPostinstall: boolean }, + { packageManager: pkgMgr, skipPostinstall, configDir: userSpecifiedConfigDir }: CLIOptions, logger = console ) { - const { packageManager: pkgMgr } = options; const [addonName, inputVersion] = getVersionSpecifier(addon); const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr }); const packageJson = await packageManager.retrievePackageJson(); - const { mainConfig, configDir } = getStorybookInfo(packageJson); + const { mainConfig, configDir: inferredConfigDir } = getStorybookInfo( + packageJson, + userSpecifiedConfigDir + ); + const configDir = userSpecifiedConfigDir || inferredConfigDir || '.storybook'; if (typeof configDir === 'undefined') { throw new Error(dedent` @@ -119,7 +128,7 @@ export async function add( main.appendValueToArray(['addons'], addonName); await writeConfig(main); - if (!options.skipPostinstall && isCoreAddon(addonName)) { + if (!skipPostinstall && isCoreAddon(addonName)) { await postinstallAddon(addonName, { packageManager: packageManager.type }); } } diff --git a/code/lib/cli/src/generate.ts b/code/lib/cli/src/generate.ts index 36de347730a7..b5aa64f7f77d 100644 --- a/code/lib/cli/src/generate.ts +++ b/code/lib/cli/src/generate.ts @@ -68,6 +68,7 @@ command('add ') '--package-manager ', 'Force package manager for installing dependencies' ) + .option('-c, --config-dir ', 'Directory where to load Storybook configurations from') .option('-s --skip-postinstall', 'Skip package specific postinstall config modifications') .action((addonName: string, options: any) => add(addonName, options)); From 3a3542860940c25e88c08e8975d0532a7d10dc88 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 2 Apr 2024 11:48:29 +0200 Subject: [PATCH 19/78] Implement draft version of the file search --- code/addons/controls/package.json | 8 +++ code/addons/controls/preset.js | 1 + code/addons/controls/src/constants.ts | 2 + code/addons/controls/src/preset.ts | 38 ++++++++++++ .../src/utils/__tests__/src/common.js | 0 .../src/utils/__tests__/src/esmodule.js | 0 .../src/utils/__tests__/src/ignored.js | 0 code/addons/controls/src/utils/filesearch.ts | 58 +++++++++++++++++++ code/addons/controls/src/utils/parser.ts | 23 ++++++++ .../addons/controls/src/utils/parser/react.ts | 36 ++++++++++++ code/yarn.lock | 47 ++++++++++++++- 11 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 code/addons/controls/preset.js create mode 100644 code/addons/controls/src/preset.ts create mode 100644 code/addons/controls/src/utils/__tests__/src/common.js create mode 100644 code/addons/controls/src/utils/__tests__/src/esmodule.js create mode 100644 code/addons/controls/src/utils/__tests__/src/ignored.js create mode 100644 code/addons/controls/src/utils/filesearch.ts create mode 100644 code/addons/controls/src/utils/parser.ts create mode 100644 code/addons/controls/src/utils/parser/react.ts diff --git a/code/addons/controls/package.json b/code/addons/controls/package.json index d13ea6560843..e7f1a604bffa 100644 --- a/code/addons/controls/package.json +++ b/code/addons/controls/package.json @@ -33,6 +33,7 @@ "import": "./dist/index.mjs" }, "./manager": "./dist/manager.js", + "./preset": "./dist/preset.js", "./register": "./dist/manager.js", "./package.json": "./package.json" }, @@ -52,6 +53,10 @@ }, "dependencies": { "@storybook/blocks": "workspace:*", + "cjs-module-lexer": "^1.2.3", + "es-module-lexer": "^1.5.0", + "glob": "^10.3.12", + "globby": "^14.0.1", "lodash": "^4.17.21", "ts-dedent": "^2.0.0" }, @@ -71,6 +76,9 @@ "access": "public" }, "bundler": { + "nodeEntries": [ + "./src/preset.ts" + ], "exportEntries": [ "./src/index.ts" ], diff --git a/code/addons/controls/preset.js b/code/addons/controls/preset.js new file mode 100644 index 000000000000..a83f95279e7f --- /dev/null +++ b/code/addons/controls/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/preset'); diff --git a/code/addons/controls/src/constants.ts b/code/addons/controls/src/constants.ts index a2360dfff87c..fc0f91e21ec8 100644 --- a/code/addons/controls/src/constants.ts +++ b/code/addons/controls/src/constants.ts @@ -1,2 +1,4 @@ export const ADDON_ID = 'addon-controls' as const; export const PARAM_KEY = 'controls' as const; +export const FILE_COMPONENT_SEARCH = 'file-component-search' as const; +export const FILE_COMPONENT_SEARCH_RESULT = 'file-component-search-result' as const; diff --git a/code/addons/controls/src/preset.ts b/code/addons/controls/src/preset.ts new file mode 100644 index 000000000000..84f39990cb1f --- /dev/null +++ b/code/addons/controls/src/preset.ts @@ -0,0 +1,38 @@ +import type { Options } from '@storybook/types'; +import type { Channel } from '@storybook/channels'; +import { FILE_COMPONENT_SEARCH, FILE_COMPONENT_SEARCH_RESULT } from './constants'; + +enum ErrorCode {} + +interface Data { + // A regular string or a glob pattern + searchQuery: string; +} + +interface SearchResult { + success: true | false; + 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; + }>; + }>; + error: null | ErrorCode; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const experimental_serverChannel = async (channel: Channel, options: Options) => { + channel.on(FILE_COMPONENT_SEARCH, async (data: Data) => { + // Emit an event using the search results + channel.emit(FILE_COMPONENT_SEARCH_RESULT, { result: {} as SearchResult }); + }); + + return channel; +}; diff --git a/code/addons/controls/src/utils/__tests__/src/common.js b/code/addons/controls/src/utils/__tests__/src/common.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/addons/controls/src/utils/__tests__/src/esmodule.js b/code/addons/controls/src/utils/__tests__/src/esmodule.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/addons/controls/src/utils/__tests__/src/ignored.js b/code/addons/controls/src/utils/__tests__/src/ignored.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/addons/controls/src/utils/filesearch.ts b/code/addons/controls/src/utils/filesearch.ts new file mode 100644 index 000000000000..f115ade901b1 --- /dev/null +++ b/code/addons/controls/src/utils/filesearch.ts @@ -0,0 +1,58 @@ +import { globby } from 'globby'; +import type { SupportedRenderer } from './parser'; +import { getParser } from './parser'; + +export interface Data { + searchQuery: string; +} + +export interface SearchResult { + success: boolean; + files: null | Array<{ + filepath: string; + searchQuery: string; + exportedComponents: Array<{ + name: string; + default: boolean; + }>; + }>; + error: null | string; +} + +export async function searchFiles( + data: Data, + cwd: string, + renderer: SupportedRenderer +): Promise { + try { + const entries = await globby(data.searchQuery, { + ignore: ['**/node_modules/**'], + gitignore: true, + cwd, + objectMode: true, + }); + + const files = entries.map(async (entry) => { + const parser = getParser(renderer); + const info = await parser.parse(entry.path); + + return { + filepath: entry.path, + searchQuery: data.searchQuery, + exportedComponents: info.exports, + }; + }); + + return { + success: true, + files: await Promise.all(files), + error: null, + }; + } catch (e) { + return { + success: false, + files: null, + error: 'An error occurred while searching for files', + }; + } +} diff --git a/code/addons/controls/src/utils/parser.ts b/code/addons/controls/src/utils/parser.ts new file mode 100644 index 000000000000..e6e82ff521db --- /dev/null +++ b/code/addons/controls/src/utils/parser.ts @@ -0,0 +1,23 @@ +import { ReactParser } from './parser/react'; + +export type SupportedRenderer = 'react'; + +export type ParserResult = { + exports: Array<{ + name: string; + default: boolean; + }>; +}; + +export interface Parser { + parse: (content: string) => Promise; +} + +export function getParser(renderer: SupportedRenderer): Parser { + switch (renderer) { + case 'react': + return new ReactParser(); + default: + throw new Error(`Unsupported renderer: ${renderer}`); + } +} diff --git a/code/addons/controls/src/utils/parser/react.ts b/code/addons/controls/src/utils/parser/react.ts new file mode 100644 index 000000000000..38b99b8175a0 --- /dev/null +++ b/code/addons/controls/src/utils/parser/react.ts @@ -0,0 +1,36 @@ +import { parse as parseCjs, init as initCjsParser } from 'cjs-module-lexer'; +import { parse as parseEs } from 'es-module-lexer'; + +import type { Parser } from '../parser'; + +export class ReactParser implements Parser { + async parse(source: 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(source); + + return { + exports: (exports ?? []).map((e) => { + const name = source.substring(e.s, e.e); + return { + name, + default: name === 'default', + }; + }), + }; + // Try to parse as CJS module + } catch { + await initCjsParser(); + + const exports = (parseCjs(source).exports ?? []).filter((e: string) => e !== '__esModule'); + + return { + exports: (exports ?? []).map((name) => ({ + name, + default: name === 'default', + })), + }; + } + } +} diff --git a/code/yarn.lock b/code/yarn.lock index 472dd503b241..9fd0e6014754 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -4796,6 +4796,10 @@ __metadata: "@storybook/preview-api": "workspace:*" "@storybook/theming": "workspace:*" "@storybook/types": "workspace:*" + cjs-module-lexer: "npm:^1.2.3" + es-module-lexer: "npm:^1.5.0" + glob: "npm:^10.3.12" + globby: "npm:^14.0.1" lodash: "npm:^4.17.21" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" @@ -13631,6 +13635,13 @@ __metadata: languageName: node linkType: hard +"es-module-lexer@npm:^1.5.0": + version: 1.5.0 + resolution: "es-module-lexer@npm:1.5.0" + checksum: 10c0/d199853404f3381801eb102befb84a8fc48f93ed86b852c2461c2c4ad4bbbc91128f3d974ff9b8718628260ae3f36e661295ab3e419222868aa31269284e34c9 + languageName: node + linkType: hard + "es-set-tostringtag@npm:^2.0.2": version: 2.0.3 resolution: "es-set-tostringtag@npm:2.0.3" @@ -15839,6 +15850,21 @@ __metadata: languageName: node linkType: hard +"glob@npm:^10.3.12": + version: 10.3.12 + resolution: "glob@npm:10.3.12" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^2.3.6" + minimatch: "npm:^9.0.1" + minipass: "npm:^7.0.4" + path-scurry: "npm:^1.10.2" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/f60cefdc1cf3f958b2bb5823e1b233727f04916d489dc4641d76914f016e6704421e06a83cbb68b0cb1cb9382298b7a88075b844ad2127fc9727ea22b18b0711 + languageName: node + linkType: hard + "glob@npm:^5.0.10": version: 5.0.15 resolution: "glob@npm:5.0.15" @@ -17857,7 +17883,7 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^2.3.5": +"jackspeak@npm:^2.3.5, jackspeak@npm:^2.3.6": version: 2.3.6 resolution: "jackspeak@npm:2.3.6" dependencies: @@ -19030,6 +19056,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^10.2.0": + version: 10.2.0 + resolution: "lru-cache@npm:10.2.0" + checksum: 10c0/c9847612aa2daaef102d30542a8d6d9b2c2bb36581c1bf0dc3ebf5e5f3352c772a749e604afae2e46873b930a9e9523743faac4e5b937c576ab29196774712ee + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -20661,7 +20694,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3": +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4": version: 7.0.4 resolution: "minipass@npm:7.0.4" checksum: 10c0/6c7370a6dfd257bf18222da581ba89a5eaedca10e158781232a8b5542a90547540b4b9b7e7f490e4cda43acfbd12e086f0453728ecf8c19e0ef6921bc5958ac5 @@ -22333,6 +22366,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^1.10.2": + version: 1.10.2 + resolution: "path-scurry@npm:1.10.2" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10c0/d723777fbf9627f201e64656680f66ebd940957eebacf780e6cce1c2919c29c116678b2d7dbf8821b3a2caa758d125f4444005ccec886a25c8f324504e48e601 + languageName: node + linkType: hard + "path-to-regexp@npm:0.1.7": version: 0.1.7 resolution: "path-to-regexp@npm:0.1.7" From 5199da51282f2556b3a40d778f531c2d03f6da11 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 2 Apr 2024 11:56:32 +0200 Subject: [PATCH 20/78] Add test files --- code/addons/controls/src/utils/__tests__/.gitignore | 2 ++ code/addons/controls/src/utils/__tests__/src/common.js | 0 .../controls/src/utils/__tests__/src/commonjs-default.js | 3 +++ code/addons/controls/src/utils/__tests__/src/commonjs.js | 6 ++++++ code/addons/controls/src/utils/__tests__/src/esmodule.js | 5 +++++ .../src/utils/__tests__/src/node_modules/file-in-common.js | 3 +++ code/addons/controls/src/utils/parser.ts | 6 ++---- .../controls/src/utils/parser/{react.ts => common.ts} | 2 +- 8 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 code/addons/controls/src/utils/__tests__/.gitignore delete mode 100644 code/addons/controls/src/utils/__tests__/src/common.js create mode 100644 code/addons/controls/src/utils/__tests__/src/commonjs-default.js create mode 100644 code/addons/controls/src/utils/__tests__/src/commonjs.js create mode 100644 code/addons/controls/src/utils/__tests__/src/node_modules/file-in-common.js rename code/addons/controls/src/utils/parser/{react.ts => common.ts} (95%) diff --git a/code/addons/controls/src/utils/__tests__/.gitignore b/code/addons/controls/src/utils/__tests__/.gitignore new file mode 100644 index 000000000000..95ef72e99f83 --- /dev/null +++ b/code/addons/controls/src/utils/__tests__/.gitignore @@ -0,0 +1,2 @@ +src/ignored.js +!node_modules \ No newline at end of file diff --git a/code/addons/controls/src/utils/__tests__/src/common.js b/code/addons/controls/src/utils/__tests__/src/common.js deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/code/addons/controls/src/utils/__tests__/src/commonjs-default.js b/code/addons/controls/src/utils/__tests__/src/commonjs-default.js new file mode 100644 index 000000000000..363ddea237f1 --- /dev/null +++ b/code/addons/controls/src/utils/__tests__/src/commonjs-default.js @@ -0,0 +1,3 @@ +module.exports = () => { + return 'commonjs-default'; +}; diff --git a/code/addons/controls/src/utils/__tests__/src/commonjs.js b/code/addons/controls/src/utils/__tests__/src/commonjs.js new file mode 100644 index 000000000000..cfceeb00b1e3 --- /dev/null +++ b/code/addons/controls/src/utils/__tests__/src/commonjs.js @@ -0,0 +1,6 @@ +const Component1 = ''; + +module.exports = { + Component2: '', + Component1, +}; diff --git a/code/addons/controls/src/utils/__tests__/src/esmodule.js b/code/addons/controls/src/utils/__tests__/src/esmodule.js index e69de29bb2d1..a3ed90231391 100644 --- a/code/addons/controls/src/utils/__tests__/src/esmodule.js +++ b/code/addons/controls/src/utils/__tests__/src/esmodule.js @@ -0,0 +1,5 @@ +const Component1 = ''; + +export const Component2 = ''; + +export default Component1; diff --git a/code/addons/controls/src/utils/__tests__/src/node_modules/file-in-common.js b/code/addons/controls/src/utils/__tests__/src/node_modules/file-in-common.js new file mode 100644 index 000000000000..1626d1e76d96 --- /dev/null +++ b/code/addons/controls/src/utils/__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/addons/controls/src/utils/parser.ts b/code/addons/controls/src/utils/parser.ts index e6e82ff521db..4f29aac3af7c 100644 --- a/code/addons/controls/src/utils/parser.ts +++ b/code/addons/controls/src/utils/parser.ts @@ -1,4 +1,4 @@ -import { ReactParser } from './parser/react'; +import { CommonParser } from './parser/common'; export type SupportedRenderer = 'react'; @@ -15,9 +15,7 @@ export interface Parser { export function getParser(renderer: SupportedRenderer): Parser { switch (renderer) { - case 'react': - return new ReactParser(); default: - throw new Error(`Unsupported renderer: ${renderer}`); + return new CommonParser(); } } diff --git a/code/addons/controls/src/utils/parser/react.ts b/code/addons/controls/src/utils/parser/common.ts similarity index 95% rename from code/addons/controls/src/utils/parser/react.ts rename to code/addons/controls/src/utils/parser/common.ts index 38b99b8175a0..7bc8e891595f 100644 --- a/code/addons/controls/src/utils/parser/react.ts +++ b/code/addons/controls/src/utils/parser/common.ts @@ -3,7 +3,7 @@ import { parse as parseEs } from 'es-module-lexer'; import type { Parser } from '../parser'; -export class ReactParser implements Parser { +export class CommonParser implements Parser { async parse(source: string) { try { // Do NOT remove await here. The types are wrong! It has to be awaited, From df6e16c514da269ef181bb98b24b9e3d455dd2f0 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 2 Apr 2024 11:56:59 +0200 Subject: [PATCH 21/78] Remove node_modules from gitignore --- code/addons/controls/src/utils/__tests__/.gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code/addons/controls/src/utils/__tests__/.gitignore b/code/addons/controls/src/utils/__tests__/.gitignore index 95ef72e99f83..7e4e8f40e4f1 100644 --- a/code/addons/controls/src/utils/__tests__/.gitignore +++ b/code/addons/controls/src/utils/__tests__/.gitignore @@ -1,2 +1 @@ -src/ignored.js -!node_modules \ No newline at end of file +src/ignored.js \ No newline at end of file From d0a68b229ab5f32453d85fa2f79eb83e67d5c2ce Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 3 Apr 2024 13:51:02 +0200 Subject: [PATCH 22/78] Add normalize-path helper util --- .../core-common/src/utils/normalize-path.test.ts | 10 ++++++++++ code/lib/core-common/src/utils/normalize-path.ts | 14 ++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 code/lib/core-common/src/utils/normalize-path.test.ts create mode 100644 code/lib/core-common/src/utils/normalize-path.ts 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..5c10c487d5af --- /dev/null +++ b/code/lib/core-common/src/utils/normalize-path.test.ts @@ -0,0 +1,10 @@ +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'); + }); +}); 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..7407ace285ca --- /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.normalize(p).replace(/\\/g, '/'); +} From abeaac68a347b13f0add9f4c08e9c304a2323898 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 3 Apr 2024 13:51:38 +0200 Subject: [PATCH 23/78] Adjust test files in controls --- .../utils/__tests__/src/commonjs-default.js | 4 +--- .../src/utils/__tests__/src/commonjs.js | 23 +++++++++++++++---- .../src/utils/__tests__/src/esmodule.js | 15 +++++++++--- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/code/addons/controls/src/utils/__tests__/src/commonjs-default.js b/code/addons/controls/src/utils/__tests__/src/commonjs-default.js index 363ddea237f1..aed903ea7003 100644 --- a/code/addons/controls/src/utils/__tests__/src/commonjs-default.js +++ b/code/addons/controls/src/utils/__tests__/src/commonjs-default.js @@ -1,3 +1 @@ -module.exports = () => { - return 'commonjs-default'; -}; +module.exports = require('./commonjs'); diff --git a/code/addons/controls/src/utils/__tests__/src/commonjs.js b/code/addons/controls/src/utils/__tests__/src/commonjs.js index cfceeb00b1e3..7146ed966336 100644 --- a/code/addons/controls/src/utils/__tests__/src/commonjs.js +++ b/code/addons/controls/src/utils/__tests__/src/commonjs.js @@ -1,6 +1,19 @@ -const Component1 = ''; +// named exports detection +module.exports.a = 'a'; -module.exports = { - Component2: '', - Component1, -}; +(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/addons/controls/src/utils/__tests__/src/esmodule.js b/code/addons/controls/src/utils/__tests__/src/esmodule.js index a3ed90231391..e4785feb4fd1 100644 --- a/code/addons/controls/src/utils/__tests__/src/esmodule.js +++ b/code/addons/controls/src/utils/__tests__/src/esmodule.js @@ -1,5 +1,14 @@ -const Component1 = ''; +/* eslint-disable import/no-unresolved */ +import * as ns from 'external2'; -export const Component2 = ''; +export var p = 5; -export default Component1; +export function q() {} + +export { x as externalName } from 'external'; + +export { ns }; + +export default function () { + return 'default'; +} From f0eec0a77575cf16ca45ee12da88bc143c607ac1 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 3 Apr 2024 13:52:04 +0200 Subject: [PATCH 24/78] Finalized filesearch in controls --- .../controls/src/utils/filesearch.test.ts | 37 ++++++++ code/addons/controls/src/utils/filesearch.ts | 89 ++++++++++--------- 2 files changed, 83 insertions(+), 43 deletions(-) create mode 100644 code/addons/controls/src/utils/filesearch.test.ts diff --git a/code/addons/controls/src/utils/filesearch.test.ts b/code/addons/controls/src/utils/filesearch.test.ts new file mode 100644 index 000000000000..7291edd137ca --- /dev/null +++ b/code/addons/controls/src/utils/filesearch.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; +import path from 'path'; +import { searchFiles } from './filesearch'; + +describe('filesearch', () => { + describe('search result', () => { + it('should automatically convert normal search to a glob search', async (t) => { + const files = await searchFiles('commonjs', path.join(__dirname, '__tests__'), 'react'); + + expect(files?.map((f) => f.filepath)).toEqual(['src/commonjs-default.js', 'src/commonjs.js']); + }); + + it('should work with glob search patterns', async (t) => { + const files = await searchFiles('**/commonjs.js', path.join(__dirname, '__tests__'), 'react'); + + expect(files?.map((f) => f.filepath)).toEqual(['src/commonjs.js']); + }); + + it('should ignore node_modules', async (t) => { + const files = await searchFiles( + 'file-in-common.js', + path.join(__dirname, '__tests__'), + 'react' + ); + + expect(files).toEqual([]); + }); + }); + + describe('exported components', () => { + it('should correctly return the exported components', async (t) => { + const files = await searchFiles('commonjs.js', path.join(__dirname, '__tests__'), 'react'); + + expect(files?.flatMap((f) => f.exportedComponents)).toHaveLength(5); + }); + }); +}); diff --git a/code/addons/controls/src/utils/filesearch.ts b/code/addons/controls/src/utils/filesearch.ts index f115ade901b1..45ba992ed8f6 100644 --- a/code/addons/controls/src/utils/filesearch.ts +++ b/code/addons/controls/src/utils/filesearch.ts @@ -1,58 +1,61 @@ import { globby } from 'globby'; -import type { SupportedRenderer } from './parser'; -import { getParser } from './parser'; +import path from 'path'; +import fs from 'fs'; -export interface Data { - searchQuery: string; -} +import type { SupportedRenderer } from './parser/types'; +import { getParser } from './parser'; +import { isNotNull } from './ts-utils'; -export interface SearchResult { - success: boolean; - files: null | Array<{ - filepath: string; - searchQuery: string; - exportedComponents: Array<{ - name: string; - default: boolean; - }>; +export type SearchResult = Array<{ + filepath: string; + exportedComponents: Array<{ + name: string; + default: boolean; }>; - error: null | string; -} +}>; + +/** + * Characters that are used in glob patterns to identify search queries that are not just filenames + */ +const globPatternChars = ['*', '+(', '@(', '?(', '!(', '[', ']']; +/** + * 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 and has exports + */ export async function searchFiles( - data: Data, + searchQuery: string, cwd: string, renderer: SupportedRenderer ): Promise { - try { - const entries = await globby(data.searchQuery, { - ignore: ['**/node_modules/**'], - gitignore: true, - cwd, - objectMode: true, - }); - - const files = entries.map(async (entry) => { - const parser = getParser(renderer); - const info = await parser.parse(entry.path); + const hasGlobChars = globPatternChars.some((char) => searchQuery.includes(char)); + + const globbedSearchQuery = hasGlobChars ? searchQuery : `**/${searchQuery}**`; + + const entries = await globby(globbedSearchQuery, { + ignore: ['**/node_modules/**'], + gitignore: true, + cwd, + objectMode: true, + }); + + const files = entries.map(async (entry) => { + const parser = getParser(renderer); + const content = fs.readFileSync(path.join(cwd, entry.path), 'utf-8'); + + try { + const info = await parser.parse(content); return { filepath: entry.path, - searchQuery: data.searchQuery, exportedComponents: info.exports, }; - }); - - return { - success: true, - files: await Promise.all(files), - error: null, - }; - } catch (e) { - return { - success: false, - files: null, - error: 'An error occurred while searching for files', - }; - } + } catch (e) { + return null; + } + }); + return (await Promise.all(files)).filter(isNotNull); } From b696c7827692d78fac298f23d77b920780a8999d Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 3 Apr 2024 13:52:45 +0200 Subject: [PATCH 25/78] Finalized parser API --- code/addons/controls/src/utils/parser.ts | 21 ------ .../controls/src/utils/parser/common.ts | 36 ---------- .../src/utils/parser/generic-parser.test.ts | 66 +++++++++++++++++++ .../src/utils/parser/generic-parser.ts | 56 ++++++++++++++++ .../addons/controls/src/utils/parser/index.ts | 14 ++++ .../addons/controls/src/utils/parser/types.ts | 20 ++++++ 6 files changed, 156 insertions(+), 57 deletions(-) delete mode 100644 code/addons/controls/src/utils/parser.ts delete mode 100644 code/addons/controls/src/utils/parser/common.ts create mode 100644 code/addons/controls/src/utils/parser/generic-parser.test.ts create mode 100644 code/addons/controls/src/utils/parser/generic-parser.ts create mode 100644 code/addons/controls/src/utils/parser/index.ts create mode 100644 code/addons/controls/src/utils/parser/types.ts diff --git a/code/addons/controls/src/utils/parser.ts b/code/addons/controls/src/utils/parser.ts deleted file mode 100644 index 4f29aac3af7c..000000000000 --- a/code/addons/controls/src/utils/parser.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { CommonParser } from './parser/common'; - -export type SupportedRenderer = 'react'; - -export type ParserResult = { - exports: Array<{ - name: string; - default: boolean; - }>; -}; - -export interface Parser { - parse: (content: string) => Promise; -} - -export function getParser(renderer: SupportedRenderer): Parser { - switch (renderer) { - default: - return new CommonParser(); - } -} diff --git a/code/addons/controls/src/utils/parser/common.ts b/code/addons/controls/src/utils/parser/common.ts deleted file mode 100644 index 7bc8e891595f..000000000000 --- a/code/addons/controls/src/utils/parser/common.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { parse as parseCjs, init as initCjsParser } from 'cjs-module-lexer'; -import { parse as parseEs } from 'es-module-lexer'; - -import type { Parser } from '../parser'; - -export class CommonParser implements Parser { - async parse(source: 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(source); - - return { - exports: (exports ?? []).map((e) => { - const name = source.substring(e.s, e.e); - return { - name, - default: name === 'default', - }; - }), - }; - // Try to parse as CJS module - } catch { - await initCjsParser(); - - const exports = (parseCjs(source).exports ?? []).filter((e: string) => e !== '__esModule'); - - return { - exports: (exports ?? []).map((name) => ({ - name, - default: name === 'default', - })), - }; - } - } -} diff --git a/code/addons/controls/src/utils/parser/generic-parser.test.ts b/code/addons/controls/src/utils/parser/generic-parser.test.ts new file mode 100644 index 000000000000..9f9e98391e16 --- /dev/null +++ b/code/addons/controls/src/utils/parser/generic-parser.test.ts @@ -0,0 +1,66 @@ +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, '..', '__tests__'); + +describe('generic-parser', () => { + it('should correctly return exports from CommonJS files', async () => { + const content = fs.readFileSync(path.join(TEST_DIR, 'src', 'commonjs.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', 'esmodule.js'), 'utf-8'); + const { exports } = await genericParser.parse(content); + + expect(exports).toEqual([ + { + default: false, + name: 'p', + }, + { + default: false, + name: 'q', + }, + { + default: false, + name: 'externalName', + }, + { + default: false, + name: 'ns', + }, + { + default: true, + name: 'default', + }, + ]); + }); +}); diff --git a/code/addons/controls/src/utils/parser/generic-parser.ts b/code/addons/controls/src/utils/parser/generic-parser.ts new file mode 100644 index 000000000000..30562128eb1d --- /dev/null +++ b/code/addons/controls/src/utils/parser/generic-parser.ts @@ -0,0 +1,56 @@ +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. Very likely that this is not a CJS module.' + ); + + return { + exports: (filteredExports ?? []).map((name) => ({ + name, + default: name === 'default', + })), + }; + } + } +} diff --git a/code/addons/controls/src/utils/parser/index.ts b/code/addons/controls/src/utils/parser/index.ts new file mode 100644 index 000000000000..1d17272bb13d --- /dev/null +++ b/code/addons/controls/src/utils/parser/index.ts @@ -0,0 +1,14 @@ +import { GenericParser } from './generic-parser'; +import type { Parser, SupportedRenderer } 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: SupportedRenderer): Parser { + switch (renderer) { + default: + return new GenericParser(); + } +} diff --git a/code/addons/controls/src/utils/parser/types.ts b/code/addons/controls/src/utils/parser/types.ts new file mode 100644 index 000000000000..47529b441825 --- /dev/null +++ b/code/addons/controls/src/utils/parser/types.ts @@ -0,0 +1,20 @@ +export type SupportedRenderer = 'react'; + +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; +} From 4aaa5b6c46d269490316279e1021770824a86b8a Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 3 Apr 2024 13:52:56 +0200 Subject: [PATCH 26/78] Add isNotNull function to ts-utils.ts --- code/addons/controls/src/utils/ts-utils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 code/addons/controls/src/utils/ts-utils.ts diff --git a/code/addons/controls/src/utils/ts-utils.ts b/code/addons/controls/src/utils/ts-utils.ts new file mode 100644 index 000000000000..e3d720f8e17c --- /dev/null +++ b/code/addons/controls/src/utils/ts-utils.ts @@ -0,0 +1,8 @@ +/** + * Check if a value is not null. This is a type guard for TypeScript usually used in conjunction with array filter. + * @param value - The value to check + * @returns Whether the value is not null + */ +export function isNotNull(value: T | null): value is T { + return value !== null; +} From 48f0f3b0e982fbc3f7761c3acd887d61c8d6f661 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 3 Apr 2024 13:55:29 +0200 Subject: [PATCH 27/78] API Doc: Add fallback for renderer name if not set --- code/lib/core-common/src/utils/get-renderer-name.ts | 1 + 1 file changed, 1 insertion(+) 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..3127e5db1204 100644 --- a/code/lib/core-common/src/utils/get-renderer-name.ts +++ b/code/lib/core-common/src/utils/get-renderer-name.ts @@ -3,6 +3,7 @@ import { getFrameworkName } from './get-framework-name'; /** * 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); From efec0b54c40b48ac2532ce7228514a226a501611 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 3 Apr 2024 13:56:08 +0200 Subject: [PATCH 28/78] Extract util to extract proper framework name --- .../src/automigrate/helpers/mainConfigFile.ts | 7 ++----- .../src/utils/get-framework-name.test.ts | 18 ++++++++++++++++++ .../src/utils/get-framework-name.ts | 16 ++++++++++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 code/lib/core-common/src/utils/get-framework-name.test.ts 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/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; +}; From 6654fd6d1ddf216940b9521f24c3d6047a312360 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 3 Apr 2024 14:09:11 +0200 Subject: [PATCH 29/78] Extract utils and types into @storybook/core-common and @storybook/types --- code/lib/cli/src/helpers.ts | 50 ++++--------------- code/lib/cli/src/project_types.ts | 23 +++------ code/lib/core-common/src/index.ts | 1 + .../src/utils/framework-to-renderer.ts | 36 +++++++++++++ code/lib/types/src/index.ts | 1 + code/lib/types/src/modules/renderers.ts | 14 ++++++ 6 files changed, 70 insertions(+), 55 deletions(-) create mode 100644 code/lib/core-common/src/utils/framework-to-renderer.ts create mode 100644 code/lib/types/src/modules/renderers.ts 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/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'; From e86e004b2ac99f082d17e632480b530dfbce63c0 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 3 Apr 2024 14:13:04 +0200 Subject: [PATCH 30/78] Add new function extractProperRendererNameFromFramework --- .../src/utils/get-renderer-name.test.ts | 17 +++++++++++++ .../src/utils/get-renderer-name.ts | 24 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 code/lib/core-common/src/utils/get-renderer-name.test.ts 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 3127e5db1204..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,5 +1,7 @@ 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 @@ -16,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]; +} From 0ee1d50d5c41356ab9c4271e11151ac0d43c708c Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 3 Apr 2024 15:24:45 +0200 Subject: [PATCH 31/78] Implement file-search channel --- code/addons/controls/src/preset.ts | 34 +---- .../file-search-channel.test.ts | 130 ++++++++++++++++++ .../src/server-channel/file-search-channel.ts | 86 ++++++++++++ code/addons/controls/src/utils/filesearch.ts | 4 +- .../addons/controls/src/utils/parser/index.ts | 5 +- .../addons/controls/src/utils/parser/types.ts | 2 - 6 files changed, 223 insertions(+), 38 deletions(-) create mode 100644 code/addons/controls/src/server-channel/file-search-channel.test.ts create mode 100644 code/addons/controls/src/server-channel/file-search-channel.ts diff --git a/code/addons/controls/src/preset.ts b/code/addons/controls/src/preset.ts index 84f39990cb1f..0eea2c3485f4 100644 --- a/code/addons/controls/src/preset.ts +++ b/code/addons/controls/src/preset.ts @@ -1,38 +1,8 @@ import type { Options } from '@storybook/types'; import type { Channel } from '@storybook/channels'; -import { FILE_COMPONENT_SEARCH, FILE_COMPONENT_SEARCH_RESULT } from './constants'; - -enum ErrorCode {} - -interface Data { - // A regular string or a glob pattern - searchQuery: string; -} - -interface SearchResult { - success: true | false; - 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; - }>; - }>; - error: null | ErrorCode; -} +import { initFileSearchChannel } from './server-channel/file-search-channel'; // eslint-disable-next-line @typescript-eslint/naming-convention export const experimental_serverChannel = async (channel: Channel, options: Options) => { - channel.on(FILE_COMPONENT_SEARCH, async (data: Data) => { - // Emit an event using the search results - channel.emit(FILE_COMPONENT_SEARCH_RESULT, { result: {} as SearchResult }); - }); - - return channel; + initFileSearchChannel(channel, options); }; diff --git a/code/addons/controls/src/server-channel/file-search-channel.test.ts b/code/addons/controls/src/server-channel/file-search-channel.test.ts new file mode 100644 index 000000000000..44b983f6f69e --- /dev/null +++ b/code/addons/controls/src/server-channel/file-search-channel.test.ts @@ -0,0 +1,130 @@ +import type { ChannelTransport } from '@storybook/channels'; +import { Channel } from '@storybook/channels'; +import { FILE_COMPONENT_SEARCH, FILE_COMPONENT_SEARCH_RESULT } from '../constants'; +import { initFileSearchChannel } from './file-search-channel'; +import { beforeEach, describe, expect, vi, it } from 'vitest'; + +const mocks = vi.hoisted(() => { + return { + searchFiles: vi.fn(), + }; +}); + +vi.mock('../utils/filesearch', () => { + 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', '__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/filesearch')).searchFiles(...args); + }); + + await vi.waitFor(() => { + expect(searchResultChannelListener).toHaveBeenCalled(); + }); + + expect(searchResultChannelListener).toHaveBeenCalledWith({ + error: null, + result: { + files: [ + { + exportedComponents: [ + { + default: false, + name: './commonjs', + }, + ], + filepath: 'src/commonjs-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.js', + }, + ], + searchQuery: 'commonjs', + }, + 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/addons/controls/src/server-channel/file-search-channel.ts b/code/addons/controls/src/server-channel/file-search-channel.ts new file mode 100644 index 000000000000..75880af04d9d --- /dev/null +++ b/code/addons/controls/src/server-channel/file-search-channel.ts @@ -0,0 +1,86 @@ +import type { Options, SupportedRenderers } from '@storybook/types'; +import type { Channel } from '@storybook/channels'; +import { FILE_COMPONENT_SEARCH, FILE_COMPONENT_SEARCH_RESULT } from '../constants'; +import { searchFiles } from '../utils/filesearch'; +import { + extractProperRendererNameFromFramework, + getFrameworkName, + getProjectRoot, +} from '@storybook/core-common'; +import dedent from 'ts-dedent'; +import assert from 'node:assert'; + +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) { + /** + * Listenes 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; + + assert(searchQuery, 'searchQuery is required'); + + const frameworkName = await getFrameworkName(options); + + const rendererName = (await extractProperRendererNameFromFramework( + frameworkName + )) as SupportedRenderers; + + const projectRoot = getProjectRoot(); + + const files = await searchFiles(searchQuery, projectRoot, rendererName); + + /** + * Emits the search result event with the search result + */ + channel.emit(FILE_COMPONENT_SEARCH_RESULT, { + success: true, + result: { + searchQuery, + files, + }, + 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: dedent` + An error occurred while searching for components in the project. + ${e?.message} + `, + } as SearchResult); + } + }); + + return channel; +} diff --git a/code/addons/controls/src/utils/filesearch.ts b/code/addons/controls/src/utils/filesearch.ts index 45ba992ed8f6..f20cfac2cfb5 100644 --- a/code/addons/controls/src/utils/filesearch.ts +++ b/code/addons/controls/src/utils/filesearch.ts @@ -2,9 +2,9 @@ import { globby } from 'globby'; import path from 'path'; import fs from 'fs'; -import type { SupportedRenderer } from './parser/types'; import { getParser } from './parser'; import { isNotNull } from './ts-utils'; +import type { SupportedRenderers } from '@storybook/types'; export type SearchResult = Array<{ filepath: string; @@ -29,7 +29,7 @@ const globPatternChars = ['*', '+(', '@(', '?(', '!(', '[', ']']; export async function searchFiles( searchQuery: string, cwd: string, - renderer: SupportedRenderer + renderer: SupportedRenderers | null ): Promise { const hasGlobChars = globPatternChars.some((char) => searchQuery.includes(char)); diff --git a/code/addons/controls/src/utils/parser/index.ts b/code/addons/controls/src/utils/parser/index.ts index 1d17272bb13d..8f5183a49538 100644 --- a/code/addons/controls/src/utils/parser/index.ts +++ b/code/addons/controls/src/utils/parser/index.ts @@ -1,12 +1,13 @@ +import type { SupportedRenderers } from '@storybook/types'; import { GenericParser } from './generic-parser'; -import type { Parser, SupportedRenderer } from './types'; +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: SupportedRenderer): Parser { +export function getParser(renderer: SupportedRenderers | null): Parser { switch (renderer) { default: return new GenericParser(); diff --git a/code/addons/controls/src/utils/parser/types.ts b/code/addons/controls/src/utils/parser/types.ts index 47529b441825..c3c664abc27d 100644 --- a/code/addons/controls/src/utils/parser/types.ts +++ b/code/addons/controls/src/utils/parser/types.ts @@ -1,5 +1,3 @@ -export type SupportedRenderer = 'react'; - export type ParserResult = { exports: Array<{ name: string; From 492a43b134d31c48738d1070f4e12eb7a9313df0 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 3 Apr 2024 15:55:08 +0200 Subject: [PATCH 32/78] Adjust globbification of search query --- code/addons/controls/src/utils/filesearch.test.ts | 2 +- code/addons/controls/src/utils/filesearch.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/addons/controls/src/utils/filesearch.test.ts b/code/addons/controls/src/utils/filesearch.test.ts index 7291edd137ca..d97fed425aaa 100644 --- a/code/addons/controls/src/utils/filesearch.test.ts +++ b/code/addons/controls/src/utils/filesearch.test.ts @@ -5,7 +5,7 @@ import { searchFiles } from './filesearch'; describe('filesearch', () => { describe('search result', () => { it('should automatically convert normal search to a glob search', async (t) => { - const files = await searchFiles('commonjs', path.join(__dirname, '__tests__'), 'react'); + const files = await searchFiles('ommonjs', path.join(__dirname, '__tests__'), 'react'); expect(files?.map((f) => f.filepath)).toEqual(['src/commonjs-default.js', 'src/commonjs.js']); }); diff --git a/code/addons/controls/src/utils/filesearch.ts b/code/addons/controls/src/utils/filesearch.ts index f20cfac2cfb5..3b9c31a51275 100644 --- a/code/addons/controls/src/utils/filesearch.ts +++ b/code/addons/controls/src/utils/filesearch.ts @@ -33,7 +33,7 @@ export async function searchFiles( ): Promise { const hasGlobChars = globPatternChars.some((char) => searchQuery.includes(char)); - const globbedSearchQuery = hasGlobChars ? searchQuery : `**/${searchQuery}**`; + const globbedSearchQuery = hasGlobChars ? searchQuery : `**/*${searchQuery}**`; const entries = await globby(globbedSearchQuery, { ignore: ['**/node_modules/**'], From 272c3d53481bc57029f6d01029dd87947001f334 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 3 Apr 2024 15:58:04 +0200 Subject: [PATCH 33/78] Add dependencies and remove unused dependency in package.json and yarn.lock --- code/addons/controls/package.json | 3 +-- code/yarn.lock | 37 ++----------------------------- 2 files changed, 3 insertions(+), 37 deletions(-) diff --git a/code/addons/controls/package.json b/code/addons/controls/package.json index e7f1a604bffa..74bc77b72a22 100644 --- a/code/addons/controls/package.json +++ b/code/addons/controls/package.json @@ -53,9 +53,9 @@ }, "dependencies": { "@storybook/blocks": "workspace:*", + "@storybook/core-common": "workspace:*", "cjs-module-lexer": "^1.2.3", "es-module-lexer": "^1.5.0", - "glob": "^10.3.12", "globby": "^14.0.1", "lodash": "^4.17.21", "ts-dedent": "^2.0.0" @@ -63,7 +63,6 @@ "devDependencies": { "@storybook/client-logger": "workspace:*", "@storybook/components": "workspace:*", - "@storybook/core-common": "workspace:*", "@storybook/manager-api": "workspace:*", "@storybook/node-logger": "workspace:*", "@storybook/preview-api": "workspace:*", diff --git a/code/yarn.lock b/code/yarn.lock index 9fd0e6014754..c7b50b3f06cb 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -4798,7 +4798,6 @@ __metadata: "@storybook/types": "workspace:*" cjs-module-lexer: "npm:^1.2.3" es-module-lexer: "npm:^1.5.0" - glob: "npm:^10.3.12" globby: "npm:^14.0.1" lodash: "npm:^4.17.21" react: "npm:^18.2.0" @@ -15850,21 +15849,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.3.12": - version: 10.3.12 - resolution: "glob@npm:10.3.12" - dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^2.3.6" - minimatch: "npm:^9.0.1" - minipass: "npm:^7.0.4" - path-scurry: "npm:^1.10.2" - bin: - glob: dist/esm/bin.mjs - checksum: 10c0/f60cefdc1cf3f958b2bb5823e1b233727f04916d489dc4641d76914f016e6704421e06a83cbb68b0cb1cb9382298b7a88075b844ad2127fc9727ea22b18b0711 - languageName: node - linkType: hard - "glob@npm:^5.0.10": version: 5.0.15 resolution: "glob@npm:5.0.15" @@ -17883,7 +17867,7 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^2.3.5, jackspeak@npm:^2.3.6": +"jackspeak@npm:^2.3.5": version: 2.3.6 resolution: "jackspeak@npm:2.3.6" dependencies: @@ -19056,13 +19040,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^10.2.0": - version: 10.2.0 - resolution: "lru-cache@npm:10.2.0" - checksum: 10c0/c9847612aa2daaef102d30542a8d6d9b2c2bb36581c1bf0dc3ebf5e5f3352c772a749e604afae2e46873b930a9e9523743faac4e5b937c576ab29196774712ee - languageName: node - linkType: hard - "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -20694,7 +20671,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4": +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3": version: 7.0.4 resolution: "minipass@npm:7.0.4" checksum: 10c0/6c7370a6dfd257bf18222da581ba89a5eaedca10e158781232a8b5542a90547540b4b9b7e7f490e4cda43acfbd12e086f0453728ecf8c19e0ef6921bc5958ac5 @@ -22366,16 +22343,6 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.10.2": - version: 1.10.2 - resolution: "path-scurry@npm:1.10.2" - dependencies: - lru-cache: "npm:^10.2.0" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10c0/d723777fbf9627f201e64656680f66ebd940957eebacf780e6cce1c2919c29c116678b2d7dbf8821b3a2caa758d125f4444005ccec886a25c8f324504e48e601 - languageName: node - linkType: hard - "path-to-regexp@npm:0.1.7": version: 0.1.7 resolution: "path-to-regexp@npm:0.1.7" From 7e79e80070f99d0b6172e3bcb237a59931d63d43 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 3 Apr 2024 16:14:11 +0200 Subject: [PATCH 34/78] Improve glob search pattern for file search --- code/addons/controls/src/utils/filesearch.test.ts | 10 ++++++++++ code/addons/controls/src/utils/filesearch.ts | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/code/addons/controls/src/utils/filesearch.test.ts b/code/addons/controls/src/utils/filesearch.test.ts index d97fed425aaa..9bb8ebafc057 100644 --- a/code/addons/controls/src/utils/filesearch.test.ts +++ b/code/addons/controls/src/utils/filesearch.test.ts @@ -10,6 +10,16 @@ describe('filesearch', () => { expect(files?.map((f) => f.filepath)).toEqual(['src/commonjs-default.js', 'src/commonjs.js']); }); + it('should return all files if the search query matches the parent folder', async (t) => { + const files = await searchFiles('src', path.join(__dirname, '__tests__'), 'react'); + + expect(files?.map((f) => f.filepath)).toEqual([ + 'src/commonjs-default.js', + 'src/commonjs.js', + 'src/esmodule.js', + ]); + }); + it('should work with glob search patterns', async (t) => { const files = await searchFiles('**/commonjs.js', path.join(__dirname, '__tests__'), 'react'); diff --git a/code/addons/controls/src/utils/filesearch.ts b/code/addons/controls/src/utils/filesearch.ts index 3b9c31a51275..a71ceb8913bf 100644 --- a/code/addons/controls/src/utils/filesearch.ts +++ b/code/addons/controls/src/utils/filesearch.ts @@ -33,7 +33,9 @@ export async function searchFiles( ): Promise { const hasGlobChars = globPatternChars.some((char) => searchQuery.includes(char)); - const globbedSearchQuery = hasGlobChars ? searchQuery : `**/*${searchQuery}**`; + const globbedSearchQuery = hasGlobChars + ? searchQuery + : [`**/*${searchQuery}*`, `**/*${searchQuery}*/**`]; const entries = await globby(globbedSearchQuery, { ignore: ['**/node_modules/**'], From 3ae848c3bc5c437a6503a66e73912a94d1373bd7 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 3 Apr 2024 16:29:09 +0200 Subject: [PATCH 35/78] Update yarn install command to exclude immutable flag --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 75b487380d8ea1b8d0cef17fefbc22431bd89660 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 3 Apr 2024 16:31:50 +0200 Subject: [PATCH 36/78] Update yarn.lock --- code/yarn.lock | 7 ------- 1 file changed, 7 deletions(-) diff --git a/code/yarn.lock b/code/yarn.lock index c7b50b3f06cb..e50a89d526de 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -13634,13 +13634,6 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^1.5.0": - version: 1.5.0 - resolution: "es-module-lexer@npm:1.5.0" - checksum: 10c0/d199853404f3381801eb102befb84a8fc48f93ed86b852c2461c2c4ad4bbbc91128f3d974ff9b8718628260ae3f36e661295ab3e419222868aa31269284e34c9 - languageName: node - linkType: hard - "es-set-tostringtag@npm:^2.0.2": version: 2.0.3 resolution: "es-set-tostringtag@npm:2.0.3" From 871699efcbbe541d74a2ef2963f593ed8c7a96b1 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 4 Apr 2024 10:46:09 +0200 Subject: [PATCH 37/78] import globby dynamically because it is a pure ESM module --- code/addons/controls/src/utils/filesearch.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/addons/controls/src/utils/filesearch.ts b/code/addons/controls/src/utils/filesearch.ts index a71ceb8913bf..75dffbd3f0d3 100644 --- a/code/addons/controls/src/utils/filesearch.ts +++ b/code/addons/controls/src/utils/filesearch.ts @@ -1,4 +1,3 @@ -import { globby } from 'globby'; import path from 'path'; import fs from 'fs'; @@ -37,6 +36,9 @@ export async function searchFiles( ? searchQuery : [`**/*${searchQuery}*`, `**/*${searchQuery}*/**`]; + // Dynamically import globby because it is a pure ESM module + const { globby } = await import('globby'); + const entries = await globby(globbedSearchQuery, { ignore: ['**/node_modules/**'], gitignore: true, From ca397190ac47c6ee13fea1b8f5ece71d00a66e5b Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 4 Apr 2024 13:24:51 +0200 Subject: [PATCH 38/78] Bump esmodule-lexer --- .../nextjs/yarn.lock | 20 +++++++++++-------- .../react/yarn.lock | 6 +++++- .../svelte/yarn.lock | 11 ++++++++++ .../vue3/yarn.lock | 11 ++++++++++ 4 files changed, 39 insertions(+), 9 deletions(-) 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" From a1305c33619a41f19a39c62db021724310be1382 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 4 Apr 2024 15:38:23 +0200 Subject: [PATCH 39/78] Only search for specific file extensions --- .../src/utils/__tests__/src/assets/asset.css | 0 .../src/utils/__tests__/src/assets/asset.json | 0 .../src/utils/__tests__/src/assets/asset.png | 0 ...-default.js => commonjs-module-default.js} | 0 .../src/{commonjs.js => commonjs-module.js} | 0 .../src/{esmodule.js => es-module.js} | 0 .../src/file-extensions/extension.cjs | 3 ++ .../src/file-extensions/extension.cts | 3 ++ .../src/file-extensions/extension.js | 3 ++ .../src/file-extensions/extension.jsx | 3 ++ .../src/file-extensions/extension.mts | 3 ++ .../src/file-extensions/extension.ts | 3 ++ .../src/file-extensions/extension.tsx | 3 ++ .../controls/src/utils/filesearch.test.ts | 41 +++++++++++++++---- code/addons/controls/src/utils/filesearch.ts | 25 +++++++---- 15 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 code/addons/controls/src/utils/__tests__/src/assets/asset.css create mode 100644 code/addons/controls/src/utils/__tests__/src/assets/asset.json create mode 100644 code/addons/controls/src/utils/__tests__/src/assets/asset.png rename code/addons/controls/src/utils/__tests__/src/{commonjs-default.js => commonjs-module-default.js} (100%) rename code/addons/controls/src/utils/__tests__/src/{commonjs.js => commonjs-module.js} (100%) rename code/addons/controls/src/utils/__tests__/src/{esmodule.js => es-module.js} (100%) create mode 100644 code/addons/controls/src/utils/__tests__/src/file-extensions/extension.cjs create mode 100644 code/addons/controls/src/utils/__tests__/src/file-extensions/extension.cts create mode 100644 code/addons/controls/src/utils/__tests__/src/file-extensions/extension.js create mode 100644 code/addons/controls/src/utils/__tests__/src/file-extensions/extension.jsx create mode 100644 code/addons/controls/src/utils/__tests__/src/file-extensions/extension.mts create mode 100644 code/addons/controls/src/utils/__tests__/src/file-extensions/extension.ts create mode 100644 code/addons/controls/src/utils/__tests__/src/file-extensions/extension.tsx diff --git a/code/addons/controls/src/utils/__tests__/src/assets/asset.css b/code/addons/controls/src/utils/__tests__/src/assets/asset.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/addons/controls/src/utils/__tests__/src/assets/asset.json b/code/addons/controls/src/utils/__tests__/src/assets/asset.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/addons/controls/src/utils/__tests__/src/assets/asset.png b/code/addons/controls/src/utils/__tests__/src/assets/asset.png new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/addons/controls/src/utils/__tests__/src/commonjs-default.js b/code/addons/controls/src/utils/__tests__/src/commonjs-module-default.js similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/commonjs-default.js rename to code/addons/controls/src/utils/__tests__/src/commonjs-module-default.js diff --git a/code/addons/controls/src/utils/__tests__/src/commonjs.js b/code/addons/controls/src/utils/__tests__/src/commonjs-module.js similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/commonjs.js rename to code/addons/controls/src/utils/__tests__/src/commonjs-module.js diff --git a/code/addons/controls/src/utils/__tests__/src/esmodule.js b/code/addons/controls/src/utils/__tests__/src/es-module.js similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/esmodule.js rename to code/addons/controls/src/utils/__tests__/src/es-module.js diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.cjs b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.cjs new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.cjs @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.cts b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.cts new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.cts @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.js b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.js new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.js @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.jsx b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.jsx new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.jsx @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.mts b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.mts new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.mts @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.ts b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.ts new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.ts @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.tsx b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.tsx new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.tsx @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/addons/controls/src/utils/filesearch.test.ts b/code/addons/controls/src/utils/filesearch.test.ts index 9bb8ebafc057..f6a9918e0bf1 100644 --- a/code/addons/controls/src/utils/filesearch.test.ts +++ b/code/addons/controls/src/utils/filesearch.test.ts @@ -4,26 +4,45 @@ import { searchFiles } from './filesearch'; describe('filesearch', () => { describe('search result', () => { - it('should automatically convert normal search to a glob search', async (t) => { + it('should automatically convert static search to a dynamic glob search', async (t) => { const files = await searchFiles('ommonjs', path.join(__dirname, '__tests__'), 'react'); - expect(files?.map((f) => f.filepath)).toEqual(['src/commonjs-default.js', 'src/commonjs.js']); + expect(files?.map((f) => f.filepath)).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('module.js', path.join(__dirname, '__tests__'), 'react'); + + expect(files?.map((f) => f.filepath)).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('src', path.join(__dirname, '__tests__'), 'react'); + const files = await searchFiles('module', path.join(__dirname, '__tests__'), 'react'); expect(files?.map((f) => f.filepath)).toEqual([ - 'src/commonjs-default.js', - 'src/commonjs.js', - 'src/esmodule.js', + 'src/commonjs-module-default.js', + 'src/commonjs-module.js', + 'src/es-module.js', ]); }); + it('should ignore files that do not have the allowed extensions', async (t) => { + const files = await searchFiles('asset', path.join(__dirname, '__tests__'), 'react'); + + expect(files).toEqual([]); + }); + it('should work with glob search patterns', async (t) => { - const files = await searchFiles('**/commonjs.js', path.join(__dirname, '__tests__'), 'react'); + const files = await searchFiles( + '**/commonjs-module.js', + path.join(__dirname, '__tests__'), + 'react' + ); - expect(files?.map((f) => f.filepath)).toEqual(['src/commonjs.js']); + expect(files?.map((f) => f.filepath)).toEqual(['src/commonjs-module.js']); }); it('should ignore node_modules', async (t) => { @@ -39,7 +58,11 @@ describe('filesearch', () => { describe('exported components', () => { it('should correctly return the exported components', async (t) => { - const files = await searchFiles('commonjs.js', path.join(__dirname, '__tests__'), 'react'); + const files = await searchFiles( + 'commonjs-module.js', + path.join(__dirname, '__tests__'), + 'react' + ); expect(files?.flatMap((f) => f.exportedComponents)).toHaveLength(5); }); diff --git a/code/addons/controls/src/utils/filesearch.ts b/code/addons/controls/src/utils/filesearch.ts index 75dffbd3f0d3..1445afbfd7f1 100644 --- a/code/addons/controls/src/utils/filesearch.ts +++ b/code/addons/controls/src/utils/filesearch.ts @@ -14,9 +14,9 @@ export type SearchResult = Array<{ }>; /** - * Characters that are used in glob patterns to identify search queries that are not just filenames + * File extensions that should be searched for */ -const globPatternChars = ['*', '+(', '@(', '?(', '!(', '[', ']']; +const fileExtensions = ['js', 'mjs', 'cjs', 'jsx', 'mts', 'ts', 'tsx', 'cts']; /** * Search for files in a directory that match the search query @@ -30,14 +30,23 @@ export async function searchFiles( cwd: string, renderer: SupportedRenderers | null ): Promise { - const hasGlobChars = globPatternChars.some((char) => searchQuery.includes(char)); + // Dynamically import globby because it is a pure ESM module + const { globby, isDynamicPattern } = await import('globby'); - const globbedSearchQuery = hasGlobChars - ? searchQuery - : [`**/*${searchQuery}*`, `**/*${searchQuery}*/**`]; + const hasSearchSpecialGlobChars = isDynamicPattern(searchQuery, { cwd }); - // Dynamically import globby because it is a pure ESM module - const { globby } = await import('globby'); + 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/**'], From 7431a0e390073f682cb819aff5e1af86ef951adc Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 4 Apr 2024 15:41:09 +0200 Subject: [PATCH 40/78] Ignore test files in filesearch --- .../controls/src/utils/__tests__/src/tests/some.spec.ts | 0 .../controls/src/utils/__tests__/src/tests/some.test.ts | 0 code/addons/controls/src/utils/filesearch.test.ts | 6 ++++++ code/addons/controls/src/utils/filesearch.ts | 2 +- 4 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 code/addons/controls/src/utils/__tests__/src/tests/some.spec.ts create mode 100644 code/addons/controls/src/utils/__tests__/src/tests/some.test.ts diff --git a/code/addons/controls/src/utils/__tests__/src/tests/some.spec.ts b/code/addons/controls/src/utils/__tests__/src/tests/some.spec.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/addons/controls/src/utils/__tests__/src/tests/some.test.ts b/code/addons/controls/src/utils/__tests__/src/tests/some.test.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/addons/controls/src/utils/filesearch.test.ts b/code/addons/controls/src/utils/filesearch.test.ts index f6a9918e0bf1..adba15c34513 100644 --- a/code/addons/controls/src/utils/filesearch.test.ts +++ b/code/addons/controls/src/utils/filesearch.test.ts @@ -35,6 +35,12 @@ describe('filesearch', () => { expect(files).toEqual([]); }); + it('should ignore test files (*.spec.*, *.test.*)', async (t) => { + const files = await searchFiles('tests', path.join(__dirname, '__tests__'), 'react'); + + expect(files).toEqual([]); + }); + it('should work with glob search patterns', async (t) => { const files = await searchFiles( '**/commonjs-module.js', diff --git a/code/addons/controls/src/utils/filesearch.ts b/code/addons/controls/src/utils/filesearch.ts index 1445afbfd7f1..909faf93c59d 100644 --- a/code/addons/controls/src/utils/filesearch.ts +++ b/code/addons/controls/src/utils/filesearch.ts @@ -49,7 +49,7 @@ export async function searchFiles( ]; const entries = await globby(globbedSearchQuery, { - ignore: ['**/node_modules/**'], + ignore: ['**/node_modules/**', '**/*.spec.*', '**/*.test.*'], gitignore: true, cwd, objectMode: true, From 7095732703120df488513d22aca239209313ca69 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 4 Apr 2024 15:45:44 +0200 Subject: [PATCH 41/78] Fix tests --- code/addons/controls/src/utils/parser/generic-parser.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/addons/controls/src/utils/parser/generic-parser.test.ts b/code/addons/controls/src/utils/parser/generic-parser.test.ts index 9f9e98391e16..02dce49436f1 100644 --- a/code/addons/controls/src/utils/parser/generic-parser.test.ts +++ b/code/addons/controls/src/utils/parser/generic-parser.test.ts @@ -9,7 +9,7 @@ const TEST_DIR = path.join(__dirname, '..', '__tests__'); describe('generic-parser', () => { it('should correctly return exports from CommonJS files', async () => { - const content = fs.readFileSync(path.join(TEST_DIR, 'src', 'commonjs.js'), 'utf-8'); + const content = fs.readFileSync(path.join(TEST_DIR, 'src', 'commonjs-module.js'), 'utf-8'); const { exports } = await genericParser.parse(content); expect(exports).toEqual([ @@ -37,7 +37,7 @@ describe('generic-parser', () => { }); it('should correctly return exports from ES modules', async () => { - const content = fs.readFileSync(path.join(TEST_DIR, 'src', 'esmodule.js'), 'utf-8'); + const content = fs.readFileSync(path.join(TEST_DIR, 'src', 'es-module.js'), 'utf-8'); const { exports } = await genericParser.parse(content); expect(exports).toEqual([ From e4a49f88c54535221471711339d3adec3b14be34 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 4 Apr 2024 16:11:08 +0200 Subject: [PATCH 42/78] Fix test --- .../controls/src/server-channel/file-search-channel.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/addons/controls/src/server-channel/file-search-channel.test.ts b/code/addons/controls/src/server-channel/file-search-channel.test.ts index 44b983f6f69e..d6fa31085b76 100644 --- a/code/addons/controls/src/server-channel/file-search-channel.test.ts +++ b/code/addons/controls/src/server-channel/file-search-channel.test.ts @@ -69,7 +69,7 @@ describe('file-search-channel', () => { name: './commonjs', }, ], - filepath: 'src/commonjs-default.js', + filepath: 'src/commonjs-module-default.js', }, { exportedComponents: [ @@ -94,7 +94,7 @@ describe('file-search-channel', () => { name: 'e', }, ], - filepath: 'src/commonjs.js', + filepath: 'src/commonjs-module.js', }, ], searchQuery: 'commonjs', From 061dbf9d1eb422d7baa089661d91a331cc0ee2d2 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 4 Apr 2024 16:13:03 +0200 Subject: [PATCH 43/78] Return null for files which cannot be parsed --- .../controls/src/utils/__tests__/src/no-export.js | 0 code/addons/controls/src/utils/filesearch.test.ts | 11 +++++++++++ code/addons/controls/src/utils/filesearch.ts | 11 +++++++++-- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 code/addons/controls/src/utils/__tests__/src/no-export.js diff --git a/code/addons/controls/src/utils/__tests__/src/no-export.js b/code/addons/controls/src/utils/__tests__/src/no-export.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/addons/controls/src/utils/filesearch.test.ts b/code/addons/controls/src/utils/filesearch.test.ts index adba15c34513..e517b4708f8c 100644 --- a/code/addons/controls/src/utils/filesearch.test.ts +++ b/code/addons/controls/src/utils/filesearch.test.ts @@ -72,5 +72,16 @@ describe('filesearch', () => { expect(files?.flatMap((f) => f.exportedComponents)).toHaveLength(5); }); + + it('should return null for exportedComponents if parsing fails', async (t) => { + const files = await searchFiles('no-export.js', path.join(__dirname, '__tests__'), 'react'); + + expect(files).toEqual([ + { + exportedComponents: null, + filepath: 'src/no-export.js', + }, + ]); + }); }); }); diff --git a/code/addons/controls/src/utils/filesearch.ts b/code/addons/controls/src/utils/filesearch.ts index 909faf93c59d..f375e983200a 100644 --- a/code/addons/controls/src/utils/filesearch.ts +++ b/code/addons/controls/src/utils/filesearch.ts @@ -6,11 +6,15 @@ import { isNotNull } from './ts-utils'; import type { SupportedRenderers } from '@storybook/types'; export type SearchResult = Array<{ + /** The path to the file relative to the project root */ filepath: string; + /** + * The exported components in the file. + * It is null if the file couldn't be parsed or doesn't have any exports */ exportedComponents: Array<{ name: string; default: boolean; - }>; + }> | null; }>; /** @@ -67,7 +71,10 @@ export async function searchFiles( exportedComponents: info.exports, }; } catch (e) { - return null; + return { + filepath: entry.path, + exportedComponents: null, + }; } }); return (await Promise.all(files)).filter(isNotNull); From e79979b145db8ad114d71c19bc8eafbca01b7de1 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 4 Apr 2024 16:31:17 +0200 Subject: [PATCH 44/78] Add .eslintignore changes and exclude __tests__ directory --- code/.eslintignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/.eslintignore b/code/.eslintignore index 3319047075e6..beba5132a1e5 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 +__tests__ \ No newline at end of file From 8adf047daca8de5e67483be9cf1966eb55d042eb Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 4 Apr 2024 17:49:33 +0200 Subject: [PATCH 45/78] Increase timeout in test --- .../src/server-channel/file-search-channel.test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/code/addons/controls/src/server-channel/file-search-channel.test.ts b/code/addons/controls/src/server-channel/file-search-channel.test.ts index d6fa31085b76..62a521afec78 100644 --- a/code/addons/controls/src/server-channel/file-search-channel.test.ts +++ b/code/addons/controls/src/server-channel/file-search-channel.test.ts @@ -54,9 +54,12 @@ describe('file-search-channel', () => { return (await vi.importActual('../utils/filesearch')).searchFiles(...args); }); - await vi.waitFor(() => { - expect(searchResultChannelListener).toHaveBeenCalled(); - }); + await vi.waitFor( + () => { + expect(searchResultChannelListener).toHaveBeenCalled(); + }, + { timeout: 2000 } + ); expect(searchResultChannelListener).toHaveBeenCalledWith({ error: null, From d01541957884d15a7e3678c569151ac874bfe5af Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 8 Apr 2024 14:35:53 +0200 Subject: [PATCH 46/78] Apply requested changes --- .../file-search-channel.test.ts | 35 ++++++- .../src/server-channel/file-search-channel.ts | 50 +++++++--- .../src/utils/__tests__/src/assets/asset.png | Bin 0 -> 95 bytes .../src/utils/__tests__/src/es-module.js | 2 + .../src/file-extensions/extension.mjs | 3 + .../controls/src/utils/filesearch.test.ts | 87 ------------------ .../src/utils/parser/generic-parser.test.ts | 4 + .../src/utils/parser/generic-parser.ts | 5 +- .../controls/src/utils/search-files.test.ts | 86 +++++++++++++++++ .../utils/{filesearch.ts => search-files.ts} | 53 ++--------- code/addons/controls/src/utils/ts-utils.ts | 8 -- .../src/utils/normalize-path.test.ts | 1 + .../core-common/src/utils/normalize-path.ts | 2 +- 13 files changed, 176 insertions(+), 160 deletions(-) create mode 100644 code/addons/controls/src/utils/__tests__/src/file-extensions/extension.mjs delete mode 100644 code/addons/controls/src/utils/filesearch.test.ts create mode 100644 code/addons/controls/src/utils/search-files.test.ts rename code/addons/controls/src/utils/{filesearch.ts => search-files.ts} (50%) delete mode 100644 code/addons/controls/src/utils/ts-utils.ts diff --git a/code/addons/controls/src/server-channel/file-search-channel.test.ts b/code/addons/controls/src/server-channel/file-search-channel.test.ts index 62a521afec78..b2d6b2db1b06 100644 --- a/code/addons/controls/src/server-channel/file-search-channel.test.ts +++ b/code/addons/controls/src/server-channel/file-search-channel.test.ts @@ -10,7 +10,7 @@ const mocks = vi.hoisted(() => { }; }); -vi.mock('../utils/filesearch', () => { +vi.mock('../utils/search-files', () => { return { searchFiles: mocks.searchFiles, }; @@ -51,7 +51,7 @@ describe('file-search-channel', () => { mocks.searchFiles.mockImplementation(async (...args) => { // @ts-expect-error Ignore type issue - return (await vi.importActual('../utils/filesearch')).searchFiles(...args); + return (await vi.importActual('../utils/search-files')).searchFiles(...args); }); await vi.waitFor( @@ -106,6 +106,37 @@ describe('file-search-channel', () => { }); }); + 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' }; diff --git a/code/addons/controls/src/server-channel/file-search-channel.ts b/code/addons/controls/src/server-channel/file-search-channel.ts index 75880af04d9d..e1786fe57a8a 100644 --- a/code/addons/controls/src/server-channel/file-search-channel.ts +++ b/code/addons/controls/src/server-channel/file-search-channel.ts @@ -1,14 +1,16 @@ import type { Options, SupportedRenderers } from '@storybook/types'; import type { Channel } from '@storybook/channels'; -import { FILE_COMPONENT_SEARCH, FILE_COMPONENT_SEARCH_RESULT } from '../constants'; -import { searchFiles } from '../utils/filesearch'; import { extractProperRendererNameFromFramework, getFrameworkName, getProjectRoot, } from '@storybook/core-common'; -import dedent from 'ts-dedent'; -import assert from 'node:assert'; +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 '../constants'; interface Data { // A regular string or a glob pattern @@ -38,13 +40,15 @@ interface SearchResult { export function initFileSearchChannel(channel: Channel, options: Options) { /** - * Listenes for a search query event and searches for files in the project + * 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; - assert(searchQuery, 'searchQuery is required'); + if (!searchQuery) { + return; + } const frameworkName = await getFrameworkName(options); @@ -54,16 +58,35 @@ export function initFileSearchChannel(channel: Channel, options: Options) { const projectRoot = getProjectRoot(); - const files = await searchFiles(searchQuery, projectRoot, rendererName); + 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, + }; + } + }); - /** - * Emits the search result event with the search result - */ channel.emit(FILE_COMPONENT_SEARCH_RESULT, { success: true, result: { searchQuery, - files, + files: await Promise.all(entries), }, error: null, } as SearchResult); @@ -74,10 +97,7 @@ export function initFileSearchChannel(channel: Channel, options: Options) { channel.emit(FILE_COMPONENT_SEARCH_RESULT, { success: false, result: null, - error: dedent` - An error occurred while searching for components in the project. - ${e?.message} - `, + error: `An error occurred while searching for components in the project.\n${e?.message}`, } as SearchResult); } }); diff --git a/code/addons/controls/src/utils/__tests__/src/assets/asset.png b/code/addons/controls/src/utils/__tests__/src/assets/asset.png index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1914264c08781d1f30ee0b8482bccf44586f2dc1 100644 GIT binary patch literal 95 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga%mF?ju0VQumF+E%TuG2$FoVOh l8)-lem#2$k2*>s01R$Gz9%CSj!PC{xWt~$(697H@6ZHT9 literal 0 HcmV?d00001 diff --git a/code/addons/controls/src/utils/__tests__/src/es-module.js b/code/addons/controls/src/utils/__tests__/src/es-module.js index e4785feb4fd1..34fc39774ae6 100644 --- a/code/addons/controls/src/utils/__tests__/src/es-module.js +++ b/code/addons/controls/src/utils/__tests__/src/es-module.js @@ -5,6 +5,8 @@ export var p = 5; export function q() {} +export class C {} + export { x as externalName } from 'external'; export { ns }; diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.mjs b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.mjs new file mode 100644 index 000000000000..970afab48505 --- /dev/null +++ b/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.mjs @@ -0,0 +1,3 @@ +export default function () { + return 'default'; +} diff --git a/code/addons/controls/src/utils/filesearch.test.ts b/code/addons/controls/src/utils/filesearch.test.ts deleted file mode 100644 index e517b4708f8c..000000000000 --- a/code/addons/controls/src/utils/filesearch.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import path from 'path'; -import { searchFiles } from './filesearch'; - -describe('filesearch', () => { - describe('search result', () => { - it('should automatically convert static search to a dynamic glob search', async (t) => { - const files = await searchFiles('ommonjs', path.join(__dirname, '__tests__'), 'react'); - - expect(files?.map((f) => f.filepath)).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('module.js', path.join(__dirname, '__tests__'), 'react'); - - expect(files?.map((f) => f.filepath)).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('module', path.join(__dirname, '__tests__'), 'react'); - - expect(files?.map((f) => f.filepath)).toEqual([ - 'src/commonjs-module-default.js', - 'src/commonjs-module.js', - 'src/es-module.js', - ]); - }); - - it('should ignore files that do not have the allowed extensions', async (t) => { - const files = await searchFiles('asset', path.join(__dirname, '__tests__'), 'react'); - - expect(files).toEqual([]); - }); - - it('should ignore test files (*.spec.*, *.test.*)', async (t) => { - const files = await searchFiles('tests', path.join(__dirname, '__tests__'), 'react'); - - expect(files).toEqual([]); - }); - - it('should work with glob search patterns', async (t) => { - const files = await searchFiles( - '**/commonjs-module.js', - path.join(__dirname, '__tests__'), - 'react' - ); - - expect(files?.map((f) => f.filepath)).toEqual(['src/commonjs-module.js']); - }); - - it('should ignore node_modules', async (t) => { - const files = await searchFiles( - 'file-in-common.js', - path.join(__dirname, '__tests__'), - 'react' - ); - - expect(files).toEqual([]); - }); - }); - - describe('exported components', () => { - it('should correctly return the exported components', async (t) => { - const files = await searchFiles( - 'commonjs-module.js', - path.join(__dirname, '__tests__'), - 'react' - ); - - expect(files?.flatMap((f) => f.exportedComponents)).toHaveLength(5); - }); - - it('should return null for exportedComponents if parsing fails', async (t) => { - const files = await searchFiles('no-export.js', path.join(__dirname, '__tests__'), 'react'); - - expect(files).toEqual([ - { - exportedComponents: null, - filepath: 'src/no-export.js', - }, - ]); - }); - }); -}); diff --git a/code/addons/controls/src/utils/parser/generic-parser.test.ts b/code/addons/controls/src/utils/parser/generic-parser.test.ts index 02dce49436f1..df450fd797df 100644 --- a/code/addons/controls/src/utils/parser/generic-parser.test.ts +++ b/code/addons/controls/src/utils/parser/generic-parser.test.ts @@ -49,6 +49,10 @@ describe('generic-parser', () => { default: false, name: 'q', }, + { + default: false, + name: 'C', + }, { default: false, name: 'externalName', diff --git a/code/addons/controls/src/utils/parser/generic-parser.ts b/code/addons/controls/src/utils/parser/generic-parser.ts index 30562128eb1d..e297c1e92eed 100644 --- a/code/addons/controls/src/utils/parser/generic-parser.ts +++ b/code/addons/controls/src/utils/parser/generic-parser.ts @@ -40,10 +40,7 @@ export class GenericParser implements Parser { const { exports, reexports } = parseCjs(content); const filteredExports = [...exports, ...reexports].filter((e: string) => e !== '__esModule'); - assert( - filteredExports.length > 0, - 'No named exports found. Very likely that this is not a CJS module.' - ); + assert(filteredExports.length > 0, 'No named exports found'); return { exports: (filteredExports ?? []).map((name) => ({ diff --git a/code/addons/controls/src/utils/search-files.test.ts b/code/addons/controls/src/utils/search-files.test.ts new file mode 100644 index 000000000000..54e32858a63e --- /dev/null +++ b/code/addons/controls/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, '__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, '__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, '__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, '__tests__'), + }); + + expect(files).toEqual([]); + }); + + it('should ignore test files (*.spec.*, *.test.*)', async (t) => { + const files = await searchFiles({ + searchQuery: 'tests', + cwd: path.join(__dirname, '__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, '__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, '__tests__'), + }); + + expect(files).toEqual([]); + }); + + it('should not return files outside of project root', async (t) => { + await expect(() => + searchFiles({ + searchQuery: '../**/*', + cwd: path.join(__dirname, '__tests__'), + }) + ).rejects.toThrowError(); + }); +}); diff --git a/code/addons/controls/src/utils/filesearch.ts b/code/addons/controls/src/utils/search-files.ts similarity index 50% rename from code/addons/controls/src/utils/filesearch.ts rename to code/addons/controls/src/utils/search-files.ts index f375e983200a..b6f1bd89ab25 100644 --- a/code/addons/controls/src/utils/filesearch.ts +++ b/code/addons/controls/src/utils/search-files.ts @@ -1,21 +1,4 @@ -import path from 'path'; -import fs from 'fs'; - -import { getParser } from './parser'; -import { isNotNull } from './ts-utils'; -import type { SupportedRenderers } from '@storybook/types'; - -export type SearchResult = Array<{ - /** The path to the file relative to the project root */ - filepath: string; - /** - * The exported components in the file. - * It is null if the file couldn't be parsed or doesn't have any exports */ - exportedComponents: Array<{ - name: string; - default: boolean; - }> | null; -}>; +export type SearchResult = Array; /** * File extensions that should be searched for @@ -27,13 +10,15 @@ const fileExtensions = ['js', 'mjs', 'cjs', 'jsx', 'mts', 'ts', 'tsx', 'cts']; * @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 and has exports + * @returns A list of files that match the search query */ -export async function searchFiles( - searchQuery: string, - cwd: string, - renderer: SupportedRenderers | null -): Promise { +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'); @@ -59,23 +44,5 @@ export async function searchFiles( objectMode: true, }); - const files = entries.map(async (entry) => { - const parser = getParser(renderer); - const content = fs.readFileSync(path.join(cwd, entry.path), 'utf-8'); - - try { - const info = await parser.parse(content); - - return { - filepath: entry.path, - exportedComponents: info.exports, - }; - } catch (e) { - return { - filepath: entry.path, - exportedComponents: null, - }; - } - }); - return (await Promise.all(files)).filter(isNotNull); + return entries.map((entry) => entry.path); } diff --git a/code/addons/controls/src/utils/ts-utils.ts b/code/addons/controls/src/utils/ts-utils.ts deleted file mode 100644 index e3d720f8e17c..000000000000 --- a/code/addons/controls/src/utils/ts-utils.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Check if a value is not null. This is a type guard for TypeScript usually used in conjunction with array filter. - * @param value - The value to check - * @returns Whether the value is not null - */ -export function isNotNull(value: T | null): value is T { - return value !== null; -} diff --git a/code/lib/core-common/src/utils/normalize-path.test.ts b/code/lib/core-common/src/utils/normalize-path.test.ts index 5c10c487d5af..b2688660b6ef 100644 --- a/code/lib/core-common/src/utils/normalize-path.test.ts +++ b/code/lib/core-common/src/utils/normalize-path.test.ts @@ -6,5 +6,6 @@ describe('normalize-path', () => { 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 index 7407ace285ca..782e792c1011 100644 --- a/code/lib/core-common/src/utils/normalize-path.ts +++ b/code/lib/core-common/src/utils/normalize-path.ts @@ -10,5 +10,5 @@ import path from 'path'; * normalizePath('path\\to\\file') // => 'path/to/file' */ export function normalizePath(p: string) { - return path.normalize(p).replace(/\\/g, '/'); + return path.normalize(p.replace(/\\/g, '/')); } From 7fec522a3cad5d23c08e680929b7a0e8f9f4546b Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 8 Apr 2024 14:42:20 +0200 Subject: [PATCH 47/78] Only eslintignore specific __tests__ directory --- code/.eslintignore | 2 +- code/addons/controls/src/utils/__tests__/README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 code/addons/controls/src/utils/__tests__/README.md diff --git a/code/.eslintignore b/code/.eslintignore index beba5132a1e5..11f8af7fff77 100644 --- a/code/.eslintignore +++ b/code/.eslintignore @@ -18,4 +18,4 @@ ember-output !.eslintrc-markdown.js !.storybook lib/core-common/templates/base-preview-head.html -__tests__ \ No newline at end of file +code/addons/controls/src/utils/__tests__ \ No newline at end of file diff --git a/code/addons/controls/src/utils/__tests__/README.md b/code/addons/controls/src/utils/__tests__/README.md new file mode 100644 index 000000000000..926c54d82404 --- /dev/null +++ b/code/addons/controls/src/utils/__tests__/README.md @@ -0,0 +1 @@ +The parent directory "\_\_tests\_\_ was created to unit test the search-file functionality From e36a1c0ad6bdcb6fa70e6ae808046a0629077955 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 8 Apr 2024 15:27:59 +0200 Subject: [PATCH 48/78] Fix eslintignore pattern --- code/.eslintignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/.eslintignore b/code/.eslintignore index 11f8af7fff77..bcc715b171a8 100644 --- a/code/.eslintignore +++ b/code/.eslintignore @@ -18,4 +18,4 @@ ember-output !.eslintrc-markdown.js !.storybook lib/core-common/templates/base-preview-head.html -code/addons/controls/src/utils/__tests__ \ No newline at end of file +addons/controls/src/utils/__tests__ \ No newline at end of file From 926472884e7e75f379ddd980478f59663c1ed23c Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 8 Apr 2024 16:01:42 +0200 Subject: [PATCH 49/78] Fix loading of experimental channel for essential addons --- code/addons/controls/src/preset.ts | 2 ++ code/addons/essentials/package.json | 3 +++ code/addons/essentials/preset.js | 1 + code/addons/essentials/src/preset.ts | 10 ++++++++++ code/yarn.lock | 2 ++ 5 files changed, 18 insertions(+) create mode 100644 code/addons/essentials/preset.js create mode 100644 code/addons/essentials/src/preset.ts diff --git a/code/addons/controls/src/preset.ts b/code/addons/controls/src/preset.ts index 0eea2c3485f4..cc227c5b6ccd 100644 --- a/code/addons/controls/src/preset.ts +++ b/code/addons/controls/src/preset.ts @@ -1,8 +1,10 @@ import type { Options } from '@storybook/types'; import type { Channel } from '@storybook/channels'; import { initFileSearchChannel } from './server-channel/file-search-channel'; +import { initCreateNewStoryChannel } from './server-channel/create-new-story-channel'; // eslint-disable-next-line @typescript-eslint/naming-convention export const experimental_serverChannel = async (channel: Channel, options: Options) => { + initCreateNewStoryChannel(channel, options); initFileSearchChannel(channel, options); }; diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json index 2d22b284ac3d..c38ca9845ab1 100644 --- a/code/addons/essentials/package.json +++ b/code/addons/essentials/package.json @@ -98,10 +98,12 @@ "@storybook/addon-outline": "workspace:*", "@storybook/addon-toolbars": "workspace:*", "@storybook/addon-viewport": "workspace:*", + "@storybook/channels": "workspace:*", "@storybook/core-common": "workspace:*", "@storybook/manager-api": "workspace:*", "@storybook/node-logger": "workspace:*", "@storybook/preview-api": "workspace:*", + "@storybook/types": "workspace:*", "ts-dedent": "^2.0.0" }, "devDependencies": { @@ -113,6 +115,7 @@ "bundler": { "nodeEntries": [ "./src/index.ts", + "./src/preset.ts", "./src/docs/preset.ts", "./src/docs/mdx-react-shim.ts" ], diff --git a/code/addons/essentials/preset.js b/code/addons/essentials/preset.js new file mode 100644 index 000000000000..a83f95279e7f --- /dev/null +++ b/code/addons/essentials/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/preset'); diff --git a/code/addons/essentials/src/preset.ts b/code/addons/essentials/src/preset.ts new file mode 100644 index 000000000000..3e6004741ac7 --- /dev/null +++ b/code/addons/essentials/src/preset.ts @@ -0,0 +1,10 @@ +// @ts-expect-error no types +import { experimental_serverChannel as experimentalServerChannelControls } from '@storybook/addon-controls/dist/preset'; + +import type { Channel } from '@storybook/channels'; +import type { Options } from '@storybook/types'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const experimental_serverChannel = async (channel: Channel, options: Options) => { + experimentalServerChannelControls(channel, options); +}; diff --git a/code/yarn.lock b/code/yarn.lock index e50a89d526de..7b3b1ceb2a00 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -4874,10 +4874,12 @@ __metadata: "@storybook/addon-outline": "workspace:*" "@storybook/addon-toolbars": "workspace:*" "@storybook/addon-viewport": "workspace:*" + "@storybook/channels": "workspace:*" "@storybook/core-common": "workspace:*" "@storybook/manager-api": "workspace:*" "@storybook/node-logger": "workspace:*" "@storybook/preview-api": "workspace:*" + "@storybook/types": "workspace:*" ts-dedent: "npm:^2.0.0" typescript: "npm:^5.3.2" languageName: unknown From 8cbb7fa200e8a0d766b8ee38872eeb0e5edf8a35 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 8 Apr 2024 16:52:38 +0200 Subject: [PATCH 50/78] Fix server channel registration --- code/addons/controls/src/preset.ts | 2 ++ code/addons/essentials/package.json | 1 + code/addons/essentials/src/preset.ts | 4 +++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/code/addons/controls/src/preset.ts b/code/addons/controls/src/preset.ts index cc227c5b6ccd..a9593da44adc 100644 --- a/code/addons/controls/src/preset.ts +++ b/code/addons/controls/src/preset.ts @@ -7,4 +7,6 @@ import { initCreateNewStoryChannel } from './server-channel/create-new-story-cha export const experimental_serverChannel = async (channel: Channel, options: Options) => { initCreateNewStoryChannel(channel, options); initFileSearchChannel(channel, options); + + return channel; }; diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json index c38ca9845ab1..e4056091d3bd 100644 --- a/code/addons/essentials/package.json +++ b/code/addons/essentials/package.json @@ -59,6 +59,7 @@ "require": "./dist/measure/preview.js" }, "./measure/manager": "./dist/measure/manager.js", + "./preset": "./dist/preset.js", "./outline/preview": { "types": "./dist/outline/preview.d.ts", "import": "./dist/outline/preview.mjs", diff --git a/code/addons/essentials/src/preset.ts b/code/addons/essentials/src/preset.ts index 3e6004741ac7..2e5ebcd07df4 100644 --- a/code/addons/essentials/src/preset.ts +++ b/code/addons/essentials/src/preset.ts @@ -1,5 +1,5 @@ // @ts-expect-error no types -import { experimental_serverChannel as experimentalServerChannelControls } from '@storybook/addon-controls/dist/preset'; +import { experimental_serverChannel as experimentalServerChannelControls } from '@storybook/addon-controls/preset'; import type { Channel } from '@storybook/channels'; import type { Options } from '@storybook/types'; @@ -7,4 +7,6 @@ import type { Options } from '@storybook/types'; // eslint-disable-next-line @typescript-eslint/naming-convention export const experimental_serverChannel = async (channel: Channel, options: Options) => { experimentalServerChannelControls(channel, options); + + return channel; }; From 58c9c7818479b8eeab8d23eabf0279fc1cddfe83 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 8 Apr 2024 16:54:24 +0200 Subject: [PATCH 51/78] Cleanup --- code/addons/controls/src/preset.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/addons/controls/src/preset.ts b/code/addons/controls/src/preset.ts index a9593da44adc..88cfe074eaf9 100644 --- a/code/addons/controls/src/preset.ts +++ b/code/addons/controls/src/preset.ts @@ -1,11 +1,9 @@ import type { Options } from '@storybook/types'; import type { Channel } from '@storybook/channels'; import { initFileSearchChannel } from './server-channel/file-search-channel'; -import { initCreateNewStoryChannel } from './server-channel/create-new-story-channel'; // eslint-disable-next-line @typescript-eslint/naming-convention export const experimental_serverChannel = async (channel: Channel, options: Options) => { - initCreateNewStoryChannel(channel, options); initFileSearchChannel(channel, options); return channel; From 23fce6f5c8177819d25630afad2989eadbb52a39 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 9 Apr 2024 10:14:37 +0200 Subject: [PATCH 52/78] Fix normalization on windows --- code/lib/core-common/src/utils/normalize-path.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/core-common/src/utils/normalize-path.ts b/code/lib/core-common/src/utils/normalize-path.ts index 782e792c1011..a6db3aa2c9de 100644 --- a/code/lib/core-common/src/utils/normalize-path.ts +++ b/code/lib/core-common/src/utils/normalize-path.ts @@ -10,5 +10,5 @@ import path from 'path'; * normalizePath('path\\to\\file') // => 'path/to/file' */ export function normalizePath(p: string) { - return path.normalize(p.replace(/\\/g, '/')); + return path.posix.normalize(p.replace(/\\/g, '/')); } From 4a879f7590ea4f00817ee031d198c775c80d291a Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Tue, 9 Apr 2024 10:51:06 +0200 Subject: [PATCH 53/78] Add VTA automigration tests --- .../lib/cli/src/automigrate/fixes/vta.test.ts | 61 +++++++++++++++++++ code/lib/cli/src/automigrate/fixes/vta.ts | 6 +- 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 code/lib/cli/src/automigrate/fixes/vta.test.ts diff --git a/code/lib/cli/src/automigrate/fixes/vta.test.ts b/code/lib/cli/src/automigrate/fixes/vta.test.ts new file mode 100644 index 000000000000..ff1cc2109470 --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/vta.test.ts @@ -0,0 +1,61 @@ +import { describe, expect, it } from 'vitest'; +import type { StorybookConfig } from '@storybook/types'; +import { vta } from './vta'; + +const check = async ({ + packageManager, + main: mainConfig, + storybookVersion = '7.0.0', +}: { + packageManager: any; + main: Partial & Record; + storybookVersion?: string; +}) => { + return vta.check({ + packageManager, + configDir: '', + mainConfig: mainConfig as any, + storybookVersion, + }); +}; + +describe('no-ops', () => { + it('with addon setup', async () => { + await expect( + check({ + packageManager: {}, + main: { + addons: ['@chromatic-com/storybook'], + }, + }) + ).resolves.toBeFalsy(); + }); + it('with addon setup with options', async () => { + await expect( + check({ + packageManager: {}, + main: { + addons: [ + { + name: '@chromatic-com/storybook', + options: {}, + }, + ], + }, + }) + ).resolves.toBeFalsy(); + }); +}); + +describe('continue', () => { + it('no addons', async () => { + await expect( + check({ + packageManager: {}, + main: { + stories: ['**/*.stories.mdx'], + }, + }) + ).resolves.toBeTruthy(); + }); +}); diff --git a/code/lib/cli/src/automigrate/fixes/vta.ts b/code/lib/cli/src/automigrate/fixes/vta.ts index cb274baaa7bc..b641513b55a7 100644 --- a/code/lib/cli/src/automigrate/fixes/vta.ts +++ b/code/lib/cli/src/automigrate/fixes/vta.ts @@ -12,11 +12,13 @@ interface Options {} export const vta: Fix = { id: 'visual-testing-addon', - versionRange: ['<8.0.7', '>=7'], + versionRange: ['<8.0.7', '>=8.0.7'], async check({ mainConfig }) { const hadAddonInstalled = !!mainConfig?.addons?.find((addon) => - addon.toString().includes('@chromatic-com/storybook') + typeof addon === 'string' + ? addon.includes('@chromatic-com/storybook') + : addon.name.includes('@chromatic-com/storybook') ); // @ts-expect-error (user might be upgrading from an older version that still had it) From 7b6e966543e6ab6d4404140788c5c009d0e0ca78 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 9 Apr 2024 13:02:05 +0200 Subject: [PATCH 54/78] Move code to core-server --- code/addons/controls/package.json | 8 -------- code/addons/controls/preset.js | 1 - code/addons/controls/src/preset.ts | 10 ---------- code/addons/controls/src/utils/__tests__/.gitignore | 1 - code/addons/essentials/preset.js | 1 - code/addons/essentials/src/preset.ts | 12 ------------ code/lib/core-events/src/index.ts | 4 ++++ code/lib/core-server/package.json | 2 ++ .../src/server-channel/file-search-channel.test.ts | 2 +- .../src/server-channel/file-search-channel.ts | 2 +- .../src/utils/__search-files-tests__/.gitignore | 1 + .../src/utils/__search-files-tests__}/README.md | 0 .../__search-files-tests__}/src/assets/asset.css | 0 .../__search-files-tests__}/src/assets/asset.json | 0 .../__search-files-tests__}/src/assets/asset.png | Bin .../src/commonjs-module-default.js | 0 .../__search-files-tests__}/src/commonjs-module.js | 0 .../utils/__search-files-tests__}/src/es-module.js | 0 .../src/file-extensions/extension.cjs | 0 .../src/file-extensions/extension.cts | 0 .../src/file-extensions/extension.js | 0 .../src/file-extensions/extension.jsx | 0 .../src/file-extensions/extension.mjs | 0 .../src/file-extensions/extension.mts | 0 .../src/file-extensions/extension.ts | 0 .../src/file-extensions/extension.tsx | 0 .../utils/__search-files-tests__}/src/ignored.js | 0 .../utils/__search-files-tests__}/src/no-export.js | 0 .../src/node_modules/file-in-common.js | 0 .../__search-files-tests__}/src/tests/some.spec.ts | 0 .../__search-files-tests__}/src/tests/some.test.ts | 0 .../src/utils/parser/generic-parser.test.ts | 0 .../core-server}/src/utils/parser/generic-parser.ts | 0 .../core-server}/src/utils/parser/index.ts | 0 .../core-server}/src/utils/parser/types.ts | 0 .../core-server}/src/utils/search-files.test.ts | 0 .../core-server}/src/utils/search-files.ts | 0 code/yarn.lock | 6 ++---- 38 files changed, 11 insertions(+), 39 deletions(-) delete mode 100644 code/addons/controls/preset.js delete mode 100644 code/addons/controls/src/preset.ts delete mode 100644 code/addons/controls/src/utils/__tests__/.gitignore delete mode 100644 code/addons/essentials/preset.js delete mode 100644 code/addons/essentials/src/preset.ts rename code/{addons/controls => lib/core-server}/src/server-channel/file-search-channel.test.ts (99%) rename code/{addons/controls => lib/core-server}/src/server-channel/file-search-channel.ts (98%) create mode 100644 code/lib/core-server/src/utils/__search-files-tests__/.gitignore rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/README.md (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/assets/asset.css (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/assets/asset.json (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/assets/asset.png (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/commonjs-module-default.js (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/commonjs-module.js (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/es-module.js (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/file-extensions/extension.cjs (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/file-extensions/extension.cts (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/file-extensions/extension.js (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/file-extensions/extension.jsx (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/file-extensions/extension.mjs (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/file-extensions/extension.mts (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/file-extensions/extension.ts (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/file-extensions/extension.tsx (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/ignored.js (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/no-export.js (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/node_modules/file-in-common.js (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/tests/some.spec.ts (100%) rename code/{addons/controls/src/utils/__tests__ => lib/core-server/src/utils/__search-files-tests__}/src/tests/some.test.ts (100%) rename code/{addons/controls => lib/core-server}/src/utils/parser/generic-parser.test.ts (100%) rename code/{addons/controls => lib/core-server}/src/utils/parser/generic-parser.ts (100%) rename code/{addons/controls => lib/core-server}/src/utils/parser/index.ts (100%) rename code/{addons/controls => lib/core-server}/src/utils/parser/types.ts (100%) rename code/{addons/controls => lib/core-server}/src/utils/search-files.test.ts (100%) rename code/{addons/controls => lib/core-server}/src/utils/search-files.ts (100%) diff --git a/code/addons/controls/package.json b/code/addons/controls/package.json index 74bc77b72a22..d0ae7fea7e3f 100644 --- a/code/addons/controls/package.json +++ b/code/addons/controls/package.json @@ -33,7 +33,6 @@ "import": "./dist/index.mjs" }, "./manager": "./dist/manager.js", - "./preset": "./dist/preset.js", "./register": "./dist/manager.js", "./package.json": "./package.json" }, @@ -53,10 +52,6 @@ }, "dependencies": { "@storybook/blocks": "workspace:*", - "@storybook/core-common": "workspace:*", - "cjs-module-lexer": "^1.2.3", - "es-module-lexer": "^1.5.0", - "globby": "^14.0.1", "lodash": "^4.17.21", "ts-dedent": "^2.0.0" }, @@ -75,9 +70,6 @@ "access": "public" }, "bundler": { - "nodeEntries": [ - "./src/preset.ts" - ], "exportEntries": [ "./src/index.ts" ], diff --git a/code/addons/controls/preset.js b/code/addons/controls/preset.js deleted file mode 100644 index a83f95279e7f..000000000000 --- a/code/addons/controls/preset.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/preset'); diff --git a/code/addons/controls/src/preset.ts b/code/addons/controls/src/preset.ts deleted file mode 100644 index 88cfe074eaf9..000000000000 --- a/code/addons/controls/src/preset.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Options } from '@storybook/types'; -import type { Channel } from '@storybook/channels'; -import { initFileSearchChannel } from './server-channel/file-search-channel'; - -// eslint-disable-next-line @typescript-eslint/naming-convention -export const experimental_serverChannel = async (channel: Channel, options: Options) => { - initFileSearchChannel(channel, options); - - return channel; -}; diff --git a/code/addons/controls/src/utils/__tests__/.gitignore b/code/addons/controls/src/utils/__tests__/.gitignore deleted file mode 100644 index 7e4e8f40e4f1..000000000000 --- a/code/addons/controls/src/utils/__tests__/.gitignore +++ /dev/null @@ -1 +0,0 @@ -src/ignored.js \ No newline at end of file diff --git a/code/addons/essentials/preset.js b/code/addons/essentials/preset.js deleted file mode 100644 index a83f95279e7f..000000000000 --- a/code/addons/essentials/preset.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/preset'); diff --git a/code/addons/essentials/src/preset.ts b/code/addons/essentials/src/preset.ts deleted file mode 100644 index 2e5ebcd07df4..000000000000 --- a/code/addons/essentials/src/preset.ts +++ /dev/null @@ -1,12 +0,0 @@ -// @ts-expect-error no types -import { experimental_serverChannel as experimentalServerChannelControls } from '@storybook/addon-controls/preset'; - -import type { Channel } from '@storybook/channels'; -import type { Options } from '@storybook/types'; - -// eslint-disable-next-line @typescript-eslint/naming-convention -export const experimental_serverChannel = async (channel: Channel, options: Options) => { - experimentalServerChannelControls(channel, options); - - return channel; -}; diff --git a/code/lib/core-events/src/index.ts b/code/lib/core-events/src/index.ts index 7542d0aa4e57..112a6f99ac23 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_RESULT, + FILE_COMPONENT_SEARCH, 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/addons/controls/src/server-channel/file-search-channel.test.ts b/code/lib/core-server/src/server-channel/file-search-channel.test.ts similarity index 99% rename from code/addons/controls/src/server-channel/file-search-channel.test.ts rename to code/lib/core-server/src/server-channel/file-search-channel.test.ts index b2d6b2db1b06..33c2e234329b 100644 --- a/code/addons/controls/src/server-channel/file-search-channel.test.ts +++ b/code/lib/core-server/src/server-channel/file-search-channel.test.ts @@ -1,6 +1,6 @@ import type { ChannelTransport } from '@storybook/channels'; import { Channel } from '@storybook/channels'; -import { FILE_COMPONENT_SEARCH, FILE_COMPONENT_SEARCH_RESULT } from '../constants'; +import { FILE_COMPONENT_SEARCH, FILE_COMPONENT_SEARCH_RESULT } from '@storybook/core-events'; import { initFileSearchChannel } from './file-search-channel'; import { beforeEach, describe, expect, vi, it } from 'vitest'; diff --git a/code/addons/controls/src/server-channel/file-search-channel.ts b/code/lib/core-server/src/server-channel/file-search-channel.ts similarity index 98% rename from code/addons/controls/src/server-channel/file-search-channel.ts rename to code/lib/core-server/src/server-channel/file-search-channel.ts index e1786fe57a8a..3f2884867447 100644 --- a/code/addons/controls/src/server-channel/file-search-channel.ts +++ b/code/lib/core-server/src/server-channel/file-search-channel.ts @@ -10,7 +10,7 @@ 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 '../constants'; +import { FILE_COMPONENT_SEARCH, FILE_COMPONENT_SEARCH_RESULT } from '@storybook/core-events'; interface Data { // A regular string or a glob pattern 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..736e8ae58ad8 --- /dev/null +++ b/code/lib/core-server/src/utils/__search-files-tests__/.gitignore @@ -0,0 +1 @@ +!node_modules \ No newline at end of file diff --git a/code/addons/controls/src/utils/__tests__/README.md b/code/lib/core-server/src/utils/__search-files-tests__/README.md similarity index 100% rename from code/addons/controls/src/utils/__tests__/README.md rename to code/lib/core-server/src/utils/__search-files-tests__/README.md diff --git a/code/addons/controls/src/utils/__tests__/src/assets/asset.css b/code/lib/core-server/src/utils/__search-files-tests__/src/assets/asset.css similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/assets/asset.css rename to code/lib/core-server/src/utils/__search-files-tests__/src/assets/asset.css diff --git a/code/addons/controls/src/utils/__tests__/src/assets/asset.json b/code/lib/core-server/src/utils/__search-files-tests__/src/assets/asset.json similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/assets/asset.json rename to code/lib/core-server/src/utils/__search-files-tests__/src/assets/asset.json diff --git a/code/addons/controls/src/utils/__tests__/src/assets/asset.png b/code/lib/core-server/src/utils/__search-files-tests__/src/assets/asset.png similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/assets/asset.png rename to code/lib/core-server/src/utils/__search-files-tests__/src/assets/asset.png diff --git a/code/addons/controls/src/utils/__tests__/src/commonjs-module-default.js b/code/lib/core-server/src/utils/__search-files-tests__/src/commonjs-module-default.js similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/commonjs-module-default.js rename to code/lib/core-server/src/utils/__search-files-tests__/src/commonjs-module-default.js diff --git a/code/addons/controls/src/utils/__tests__/src/commonjs-module.js b/code/lib/core-server/src/utils/__search-files-tests__/src/commonjs-module.js similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/commonjs-module.js rename to code/lib/core-server/src/utils/__search-files-tests__/src/commonjs-module.js diff --git a/code/addons/controls/src/utils/__tests__/src/es-module.js b/code/lib/core-server/src/utils/__search-files-tests__/src/es-module.js similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/es-module.js rename to code/lib/core-server/src/utils/__search-files-tests__/src/es-module.js diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.cjs b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.cjs similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/file-extensions/extension.cjs rename to code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.cjs diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.cts b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.cts similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/file-extensions/extension.cts rename to code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.cts diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.js b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.js similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/file-extensions/extension.js rename to code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.js diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.jsx b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.jsx similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/file-extensions/extension.jsx rename to code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.jsx diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.mjs b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.mjs similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/file-extensions/extension.mjs rename to code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.mjs diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.mts b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.mts similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/file-extensions/extension.mts rename to code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.mts diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.ts b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.ts similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/file-extensions/extension.ts rename to code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.ts diff --git a/code/addons/controls/src/utils/__tests__/src/file-extensions/extension.tsx b/code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.tsx similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/file-extensions/extension.tsx rename to code/lib/core-server/src/utils/__search-files-tests__/src/file-extensions/extension.tsx diff --git a/code/addons/controls/src/utils/__tests__/src/ignored.js b/code/lib/core-server/src/utils/__search-files-tests__/src/ignored.js similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/ignored.js rename to code/lib/core-server/src/utils/__search-files-tests__/src/ignored.js diff --git a/code/addons/controls/src/utils/__tests__/src/no-export.js b/code/lib/core-server/src/utils/__search-files-tests__/src/no-export.js similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/no-export.js rename to code/lib/core-server/src/utils/__search-files-tests__/src/no-export.js diff --git a/code/addons/controls/src/utils/__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 similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/node_modules/file-in-common.js rename to code/lib/core-server/src/utils/__search-files-tests__/src/node_modules/file-in-common.js diff --git a/code/addons/controls/src/utils/__tests__/src/tests/some.spec.ts b/code/lib/core-server/src/utils/__search-files-tests__/src/tests/some.spec.ts similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/tests/some.spec.ts rename to code/lib/core-server/src/utils/__search-files-tests__/src/tests/some.spec.ts diff --git a/code/addons/controls/src/utils/__tests__/src/tests/some.test.ts b/code/lib/core-server/src/utils/__search-files-tests__/src/tests/some.test.ts similarity index 100% rename from code/addons/controls/src/utils/__tests__/src/tests/some.test.ts rename to code/lib/core-server/src/utils/__search-files-tests__/src/tests/some.test.ts diff --git a/code/addons/controls/src/utils/parser/generic-parser.test.ts b/code/lib/core-server/src/utils/parser/generic-parser.test.ts similarity index 100% rename from code/addons/controls/src/utils/parser/generic-parser.test.ts rename to code/lib/core-server/src/utils/parser/generic-parser.test.ts diff --git a/code/addons/controls/src/utils/parser/generic-parser.ts b/code/lib/core-server/src/utils/parser/generic-parser.ts similarity index 100% rename from code/addons/controls/src/utils/parser/generic-parser.ts rename to code/lib/core-server/src/utils/parser/generic-parser.ts diff --git a/code/addons/controls/src/utils/parser/index.ts b/code/lib/core-server/src/utils/parser/index.ts similarity index 100% rename from code/addons/controls/src/utils/parser/index.ts rename to code/lib/core-server/src/utils/parser/index.ts diff --git a/code/addons/controls/src/utils/parser/types.ts b/code/lib/core-server/src/utils/parser/types.ts similarity index 100% rename from code/addons/controls/src/utils/parser/types.ts rename to code/lib/core-server/src/utils/parser/types.ts diff --git a/code/addons/controls/src/utils/search-files.test.ts b/code/lib/core-server/src/utils/search-files.test.ts similarity index 100% rename from code/addons/controls/src/utils/search-files.test.ts rename to code/lib/core-server/src/utils/search-files.test.ts diff --git a/code/addons/controls/src/utils/search-files.ts b/code/lib/core-server/src/utils/search-files.ts similarity index 100% rename from code/addons/controls/src/utils/search-files.ts rename to code/lib/core-server/src/utils/search-files.ts diff --git a/code/yarn.lock b/code/yarn.lock index 7b3b1ceb2a00..388606f61c43 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -4790,15 +4790,11 @@ __metadata: "@storybook/blocks": "workspace:*" "@storybook/client-logger": "workspace:*" "@storybook/components": "workspace:*" - "@storybook/core-common": "workspace:*" "@storybook/manager-api": "workspace:*" "@storybook/node-logger": "workspace:*" "@storybook/preview-api": "workspace:*" "@storybook/theming": "workspace:*" "@storybook/types": "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" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" @@ -5601,9 +5597,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" From e67928a4a2c9d2b346c5f68049537e0f5132daab Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 9 Apr 2024 13:02:32 +0200 Subject: [PATCH 55/78] Adjust gitignore --- .../lib/core-server/src/utils/__search-files-tests__/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/core-server/src/utils/__search-files-tests__/.gitignore b/code/lib/core-server/src/utils/__search-files-tests__/.gitignore index 736e8ae58ad8..7e4e8f40e4f1 100644 --- a/code/lib/core-server/src/utils/__search-files-tests__/.gitignore +++ b/code/lib/core-server/src/utils/__search-files-tests__/.gitignore @@ -1 +1 @@ -!node_modules \ No newline at end of file +src/ignored.js \ No newline at end of file From 7f5b3b6c26682863c6900725c49ee8a3ba5ca4d8 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 9 Apr 2024 13:19:38 +0200 Subject: [PATCH 56/78] Fix tests and finalize --- code/lib/core-events/src/index.ts | 2 +- .../lib/core-server/src/presets/common-preset.ts | 3 +++ .../server-channel/file-search-channel.test.ts | 5 +++-- .../src/utils/parser/generic-parser.test.ts | 2 +- .../core-server/src/utils/search-files.test.ts | 16 ++++++++-------- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/code/lib/core-events/src/index.ts b/code/lib/core-events/src/index.ts index 112a6f99ac23..82978a994d07 100644 --- a/code/lib/core-events/src/index.ts +++ b/code/lib/core-events/src/index.ts @@ -89,8 +89,8 @@ export const { CURRENT_STORY_WAS_SET, DOCS_PREPARED, DOCS_RENDERED, - FILE_COMPONENT_SEARCH_RESULT, FILE_COMPONENT_SEARCH, + FILE_COMPONENT_SEARCH_RESULT, FORCE_RE_RENDER, FORCE_REMOUNT, GLOBALS_UPDATED, 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 index 33c2e234329b..e967910dd6c7 100644 --- 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 @@ -1,9 +1,10 @@ import type { ChannelTransport } from '@storybook/channels'; import { Channel } from '@storybook/channels'; import { FILE_COMPONENT_SEARCH, FILE_COMPONENT_SEARCH_RESULT } from '@storybook/core-events'; -import { initFileSearchChannel } from './file-search-channel'; import { beforeEach, describe, expect, vi, it } from 'vitest'; +import { initFileSearchChannel } from './file-search-channel'; + const mocks = vi.hoisted(() => { return { searchFiles: vi.fn(), @@ -24,7 +25,7 @@ vi.mock('@storybook/core-common', async (importOriginal) => { extractProperRendererNameFromFramework: vi.fn().mockResolvedValue('react'), getProjectRoot: vi .fn() - .mockReturnValue(require('path').join(__dirname, '..', 'utils', '__tests__')), + .mockReturnValue(require('path').join(__dirname, '..', 'utils', '__search-files-tests__')), }; }); 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 index df450fd797df..6d3ff96e15b0 100644 --- a/code/lib/core-server/src/utils/parser/generic-parser.test.ts +++ b/code/lib/core-server/src/utils/parser/generic-parser.test.ts @@ -5,7 +5,7 @@ import fs from 'fs'; const genericParser = new GenericParser(); -const TEST_DIR = path.join(__dirname, '..', '__tests__'); +const TEST_DIR = path.join(__dirname, '..', '__search-files-tests__'); describe('generic-parser', () => { it('should correctly return exports from CommonJS files', async () => { diff --git a/code/lib/core-server/src/utils/search-files.test.ts b/code/lib/core-server/src/utils/search-files.test.ts index 54e32858a63e..6aaec136df74 100644 --- a/code/lib/core-server/src/utils/search-files.test.ts +++ b/code/lib/core-server/src/utils/search-files.test.ts @@ -6,7 +6,7 @@ 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, '__tests__'), + cwd: path.join(__dirname, '__search-files-tests__'), }); expect(files).toEqual(['src/commonjs-module-default.js', 'src/commonjs-module.js']); @@ -15,7 +15,7 @@ describe('search-files', () => { 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, '__tests__'), + cwd: path.join(__dirname, '__search-files-tests__'), }); expect(files).toEqual(['src/commonjs-module.js', 'src/es-module.js']); @@ -24,7 +24,7 @@ describe('search-files', () => { 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, '__tests__'), + cwd: path.join(__dirname, '__search-files-tests__'), }); expect(files).toEqual([ @@ -42,7 +42,7 @@ describe('search-files', () => { it('should ignore files that do not have the allowed extensions', async (t) => { const files = await searchFiles({ searchQuery: 'asset', - cwd: path.join(__dirname, '__tests__'), + cwd: path.join(__dirname, '__search-files-tests__'), }); expect(files).toEqual([]); @@ -51,7 +51,7 @@ describe('search-files', () => { it('should ignore test files (*.spec.*, *.test.*)', async (t) => { const files = await searchFiles({ searchQuery: 'tests', - cwd: path.join(__dirname, '__tests__'), + cwd: path.join(__dirname, '__search-files-tests__'), }); expect(files).toEqual([]); @@ -60,7 +60,7 @@ describe('search-files', () => { it('should work with glob search patterns', async (t) => { const files = await searchFiles({ searchQuery: '**/commonjs-module.js', - cwd: path.join(__dirname, '__tests__'), + cwd: path.join(__dirname, '__search-files-tests__'), }); expect(files).toEqual(['src/commonjs-module.js']); @@ -69,7 +69,7 @@ describe('search-files', () => { it('should ignore node_modules', async (t) => { const files = await searchFiles({ searchQuery: 'file-in-common.js', - cwd: path.join(__dirname, '__tests__'), + cwd: path.join(__dirname, '__search-files-tests__'), }); expect(files).toEqual([]); @@ -79,7 +79,7 @@ describe('search-files', () => { await expect(() => searchFiles({ searchQuery: '../**/*', - cwd: path.join(__dirname, '__tests__'), + cwd: path.join(__dirname, '__search-files-tests__'), }) ).rejects.toThrowError(); }); From 314112af20d11a9fad82b38b486b5dc2890698b0 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 9 Apr 2024 13:20:09 +0200 Subject: [PATCH 57/78] Adjust eslintignore --- code/.eslintignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/.eslintignore b/code/.eslintignore index bcc715b171a8..5ced53c96672 100644 --- a/code/.eslintignore +++ b/code/.eslintignore @@ -18,4 +18,4 @@ ember-output !.eslintrc-markdown.js !.storybook lib/core-common/templates/base-preview-head.html -addons/controls/src/utils/__tests__ \ No newline at end of file +lib/core-server/src/utils/__search-files-tests__ \ No newline at end of file From 81ce3d48bdf52bd31e8d403caf470078c22dd6bb Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 9 Apr 2024 13:21:43 +0200 Subject: [PATCH 58/78] Update manager global exports --- code/ui/manager/src/globals/exports.ts | 2 ++ 1 file changed, 2 insertions(+) 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', From 423d404bd4ed25900c8fe8c93d9cbd45427e7f5a Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 9 Apr 2024 13:42:49 +0200 Subject: [PATCH 59/78] Remove leftovers --- code/addons/controls/package.json | 1 + code/addons/controls/src/constants.ts | 2 -- code/addons/essentials/package.json | 4 ---- code/ui/manager/src/globals/exports.ts | 2 ++ 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/code/addons/controls/package.json b/code/addons/controls/package.json index d0ae7fea7e3f..d13ea6560843 100644 --- a/code/addons/controls/package.json +++ b/code/addons/controls/package.json @@ -58,6 +58,7 @@ "devDependencies": { "@storybook/client-logger": "workspace:*", "@storybook/components": "workspace:*", + "@storybook/core-common": "workspace:*", "@storybook/manager-api": "workspace:*", "@storybook/node-logger": "workspace:*", "@storybook/preview-api": "workspace:*", diff --git a/code/addons/controls/src/constants.ts b/code/addons/controls/src/constants.ts index fc0f91e21ec8..a2360dfff87c 100644 --- a/code/addons/controls/src/constants.ts +++ b/code/addons/controls/src/constants.ts @@ -1,4 +1,2 @@ export const ADDON_ID = 'addon-controls' as const; export const PARAM_KEY = 'controls' as const; -export const FILE_COMPONENT_SEARCH = 'file-component-search' as const; -export const FILE_COMPONENT_SEARCH_RESULT = 'file-component-search-result' as const; diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json index e4056091d3bd..2d22b284ac3d 100644 --- a/code/addons/essentials/package.json +++ b/code/addons/essentials/package.json @@ -59,7 +59,6 @@ "require": "./dist/measure/preview.js" }, "./measure/manager": "./dist/measure/manager.js", - "./preset": "./dist/preset.js", "./outline/preview": { "types": "./dist/outline/preview.d.ts", "import": "./dist/outline/preview.mjs", @@ -99,12 +98,10 @@ "@storybook/addon-outline": "workspace:*", "@storybook/addon-toolbars": "workspace:*", "@storybook/addon-viewport": "workspace:*", - "@storybook/channels": "workspace:*", "@storybook/core-common": "workspace:*", "@storybook/manager-api": "workspace:*", "@storybook/node-logger": "workspace:*", "@storybook/preview-api": "workspace:*", - "@storybook/types": "workspace:*", "ts-dedent": "^2.0.0" }, "devDependencies": { @@ -116,7 +113,6 @@ "bundler": { "nodeEntries": [ "./src/index.ts", - "./src/preset.ts", "./src/docs/preset.ts", "./src/docs/mdx-react-shim.ts" ], diff --git a/code/ui/manager/src/globals/exports.ts b/code/ui/manager/src/globals/exports.ts index 794741a69e8b..d3da898e844c 100644 --- a/code/ui/manager/src/globals/exports.ts +++ b/code/ui/manager/src/globals/exports.ts @@ -133,6 +133,8 @@ export default { 'CHANNEL_CREATED', 'CHANNEL_WS_DISCONNECT', 'CONFIG_ERROR', + 'CREATE_NEW_STORYFILE', + 'CREATE_NEW_STORYFILE_RESULT', 'CURRENT_STORY_WAS_SET', 'DOCS_PREPARED', 'DOCS_RENDERED', From b2d3149d150f77e1a84660a73f38717f5d622ecc Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 9 Apr 2024 13:50:11 +0200 Subject: [PATCH 60/78] Update lock file --- code/yarn.lock | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code/yarn.lock b/code/yarn.lock index 388606f61c43..7be8a5f1fd96 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -4790,6 +4790,7 @@ __metadata: "@storybook/blocks": "workspace:*" "@storybook/client-logger": "workspace:*" "@storybook/components": "workspace:*" + "@storybook/core-common": "workspace:*" "@storybook/manager-api": "workspace:*" "@storybook/node-logger": "workspace:*" "@storybook/preview-api": "workspace:*" @@ -4870,12 +4871,10 @@ __metadata: "@storybook/addon-outline": "workspace:*" "@storybook/addon-toolbars": "workspace:*" "@storybook/addon-viewport": "workspace:*" - "@storybook/channels": "workspace:*" "@storybook/core-common": "workspace:*" "@storybook/manager-api": "workspace:*" "@storybook/node-logger": "workspace:*" "@storybook/preview-api": "workspace:*" - "@storybook/types": "workspace:*" ts-dedent: "npm:^2.0.0" typescript: "npm:^5.3.2" languageName: unknown From 997551de2ea4915d982a850a4ebe7875cfd0e652 Mon Sep 17 00:00:00 2001 From: jonniebigodes Date: Tue, 9 Apr 2024 12:51:39 +0100 Subject: [PATCH 61/78] Docs: MDX minor fixes --- docs/faq.md | 21 ++++++++++++------- ...storybook-automigration-mdx-legacy.npm.mdx | 3 --- ...torybook-automigration-mdx-legacy.pnpm.mdx | 3 --- ...torybook-automigration-mdx-legacy.yarn.mdx | 3 --- docs/writing-docs/mdx.md | 14 +++++-------- 5 files changed, 19 insertions(+), 25 deletions(-) delete mode 100644 docs/snippets/common/storybook-automigration-mdx-legacy.npm.mdx delete mode 100644 docs/snippets/common/storybook-automigration-mdx-legacy.pnpm.mdx delete mode 100644 docs/snippets/common/storybook-automigration-mdx-legacy.yarn.mdx diff --git a/docs/faq.md b/docs/faq.md index aa597d0f4f57..49783da06df3 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -347,15 +347,17 @@ Vue 2 entered [End of Life](https://v2.vuejs.org/lts/) (EOL) on December 31, 202 -## Why aren't my code blocks highlighted with Storybook MDX +## Why aren't my code blocks highlighted with Storybook MDX? -Out of the box, Storybook provides syntax highlighting for a set of languages (e.g., Javascript, Markdown, CSS, HTML, Typescript, GraphQL) you can use with your code blocks. Currently, there's a known limitation when you try and register a custom language to get syntax highlighting. We're working on a fix for this And will update this section once it's available. +Out of the box, Storybook provides syntax highlighting for a set of languages (e.g., Javascript, Markdown, CSS, HTML, Typescript, GraphQL) you can use with your code blocks. Currently, there's a known limitation when you try to register a custom language to get syntax highlighting. We're working on a fix for this and will update this section once it's available. -## Why aren't my MDX stories working in Storybook? +## Why aren't my MDX styles working in Storybook? -MDX can be picky about how your code is formatted with line breaks. This is especially true with code blocks. For example, this will break: +Writing documentation with MDX can be troublesome, especially regarding how your code is formatted when using line breaks with code blocks. For example, this will break: -``` + + +```mdx - ``` + + But this will work: -``` + + +```mdx ``` + + See the following [issue](https://github.com/mdx-js/mdx/issues/1945) for more information. ## Why are my mocked GraphQL queries failing with Storybook's MSW addon? diff --git a/docs/snippets/common/storybook-automigration-mdx-legacy.npm.mdx b/docs/snippets/common/storybook-automigration-mdx-legacy.npm.mdx deleted file mode 100644 index b54577026015..000000000000 --- a/docs/snippets/common/storybook-automigration-mdx-legacy.npm.mdx +++ /dev/null @@ -1,3 +0,0 @@ -```shell -npx storybook@latest automigrate mdx1to2 -``` diff --git a/docs/snippets/common/storybook-automigration-mdx-legacy.pnpm.mdx b/docs/snippets/common/storybook-automigration-mdx-legacy.pnpm.mdx deleted file mode 100644 index 4c17825bf1a8..000000000000 --- a/docs/snippets/common/storybook-automigration-mdx-legacy.pnpm.mdx +++ /dev/null @@ -1,3 +0,0 @@ -```shell -pnpm dlx storybook@latest automigrate mdx1to2 -``` diff --git a/docs/snippets/common/storybook-automigration-mdx-legacy.yarn.mdx b/docs/snippets/common/storybook-automigration-mdx-legacy.yarn.mdx deleted file mode 100644 index 277d34f73467..000000000000 --- a/docs/snippets/common/storybook-automigration-mdx-legacy.yarn.mdx +++ /dev/null @@ -1,3 +0,0 @@ -```shell -yarn dlx storybook@latest automigrate mdx1to2 -``` diff --git a/docs/writing-docs/mdx.md b/docs/writing-docs/mdx.md index d40ae287025c..2dd8058f3706 100644 --- a/docs/writing-docs/mdx.md +++ b/docs/writing-docs/mdx.md @@ -121,7 +121,7 @@ The `Meta` block defines where the document will be placed in the sidebar. In th -MDX supports standard markdown ([”commonmark”](https://commonmark.org/)) by default and can be extended to support [GitHub-flavored markdown (GFM)](https://github.github.com/gfm) and other extensions (see [Breaking changes](#breaking-changes), below). +MDX supports standard markdown (["commonmark"](https://commonmark.org/)) by default and can be extended to support [GitHub Flavored Markdown (GFM)](https://github.github.com/gfm/) and other extensions (see the [Troubleshooting section](#troubleshooting) to learn more about some of the current limitations). @@ -325,11 +325,7 @@ By applying this pattern with the Controls addon, all anchors will be ignored in ### Markdown tables aren't rendering correctly -As of MDX 2, GFM is no longer included by default: - -“We turned off GFM features in MDX by default. GFM extends CommonMark to add autolink literals, footnotes, strikethrough, tables, and task lists. If you do want these features, you can use a plugin. How to do so is described in [our guide on GFM](https://mdxjs.com/guides/gfm/).” - -In Storybook, you can apply MDX options, including plugins, in the main configuration file: +If you're extending your documentation to include specific features (e.g., tables, footnotes), you may run into some issues rendering them correctly using the current MDX version supported by Storybook. We recommend enabling the [`remark-gfm`](https://github.com/remarkjs/remark-gfm) plugin in your configuration file (i.e., [`.storybook/main.js|ts`](../configure/index.md)) to render them correctly. @@ -344,7 +340,7 @@ paths={[ -The [`remark-gfm`](https://github.com/remarkjs/remark-gfm) package isn't provided by default during migration. We recommend installing it as a development dependency if you use its features. +The [`remark-gfm`](https://github.com/remarkjs/remark-gfm) package is not included by default with Storybook and must be installed separately as a development dependency. To learn more about how to use it and the other breaking changes introduced by MDX, refer to the [GFM guide](https://mdxjs.com/guides/gfm/) and the [migration guide](https://mdxjs.com/migrating/v2/) provided by the MDX team for more information. @@ -354,11 +350,11 @@ As Storybook relies on [MDX 3](https://mdxjs.com/) to render documentation, some #### Storybook doesn't create documentation for my component stories -If you run into a situation where Storybook is not able to detect and render the documentation for your component stories, it may be due to a misconfiguration in your Storybook. Check your configuration file (i.e., `.storybook/main.js|ts`) and ensure the `stories` configuration element provides the correct path to your stories location(e.g., `../src/**/*.stories.@(js|jsx|mjs|ts|tsx)`). +If you run into a situation where Storybook is not able to detect and render the documentation for your component stories, it may be due to a misconfiguration in your Storybook. Check your configuration file (i.e., `.storybook/main.js|ts`) and ensure the `stories` configuration element provides the correct path to your stories location (e.g., `../src/**/*.stories.@(js|jsx|mjs|ts|tsx)`). ### The migration seems flaky and keeps failing -By default, running the [migration command](#automigration) will try and migrate all existing MDX files in your project according to the MDX 2 specification. However, this might not always be possible, and you might run into issues during the migration. To help you troubleshoot those issues, we've prepared some recommendations that might help you. +By default, running the [migration](../configure/upgrading.md) command will prompt you to update the existing MDX files in your project according to the MDX version supported by Storybook. However, this might be a disruptive process, specifically if you're upgrading from a previous version of Storybook where you were using the legacy MDX format. To help you troubleshoot those issues, we've prepared some recommendations that might help you. Start by running the following command inside your project directory: From 4d055fb0230c2d09308079e8c15affc5af143c12 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 9 Apr 2024 14:22:09 +0200 Subject: [PATCH 62/78] Update exports --- code/ui/manager/src/globals/exports.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/ui/manager/src/globals/exports.ts b/code/ui/manager/src/globals/exports.ts index d3da898e844c..794741a69e8b 100644 --- a/code/ui/manager/src/globals/exports.ts +++ b/code/ui/manager/src/globals/exports.ts @@ -133,8 +133,6 @@ export default { 'CHANNEL_CREATED', 'CHANNEL_WS_DISCONNECT', 'CONFIG_ERROR', - 'CREATE_NEW_STORYFILE', - 'CREATE_NEW_STORYFILE_RESULT', 'CURRENT_STORY_WAS_SET', 'DOCS_PREPARED', 'DOCS_RENDERED', From 726a80a34a06700a64a8eafea135c3711dc14f4e Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 9 Apr 2024 17:37:49 +0200 Subject: [PATCH 63/78] Make sure test packages attaches name to arg spies --- code/addons/interactions/src/preview.ts | 53 +---------------------- code/lib/test/src/index.ts | 56 ++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 54 deletions(-) diff --git a/code/addons/interactions/src/preview.ts b/code/addons/interactions/src/preview.ts index 6e70166629aa..5ef2520b6b71 100644 --- a/code/addons/interactions/src/preview.ts +++ b/code/addons/interactions/src/preview.ts @@ -1,11 +1,4 @@ -import type { - ArgsEnhancer, - PlayFunction, - PlayFunctionContext, - Renderer, - StepLabel, -} from '@storybook/types'; -import { fn, isMockFunction } from '@storybook/test'; +import type { PlayFunction, PlayFunctionContext, StepLabel } from '@storybook/types'; import { instrument } from '@storybook/instrumenter'; export const { step: runStep } = instrument( @@ -16,50 +9,6 @@ export const { step: runStep } = instrument( { intercept: true } ); -export const traverseArgs = (value: unknown, depth = 0, key?: string): unknown => { - // Make sure to not get in infinite loops with self referencing args - if (depth > 5) return value; - if (value == null) return value; - if (isMockFunction(value)) { - // Makes sure we get the arg name in the interactions panel - if (key) value.mockName(key); - return value; - } - - // wrap explicit actions in a spy - if ( - typeof value === 'function' && - 'isAction' in value && - value.isAction && - !('implicit' in value && value.implicit) - ) { - const mock = fn(value as any); - if (key) mock.mockName(key); - return mock; - } - - if (Array.isArray(value)) { - depth++; - return value.map((item) => traverseArgs(item, depth)); - } - - if (typeof value === 'object' && value.constructor === Object) { - depth++; - for (const [k, v] of Object.entries(value)) { - if (Object.getOwnPropertyDescriptor(value, k).writable) { - // We have to mutate the original object for this to survive HMR. - (value as Record)[k] = traverseArgs(v, depth, k); - } - } - return value; - } - return value; -}; - -const wrapActionsInSpyFns: ArgsEnhancer = ({ initialArgs }) => traverseArgs(initialArgs); - -export const argsEnhancers = [wrapActionsInSpyFns]; - export const parameters = { throwPlayFunctionExceptions: false, }; diff --git a/code/lib/test/src/index.ts b/code/lib/test/src/index.ts index 3ddf1fcd5687..1dcd78c457b8 100644 --- a/code/lib/test/src/index.ts +++ b/code/lib/test/src/index.ts @@ -3,7 +3,15 @@ import { type LoaderFunction } from '@storybook/csf'; import chai from 'chai'; import { global } from '@storybook/global'; import { expect as rawExpect } from './expect'; -import { clearAllMocks, onMockCall, resetAllMocks, restoreAllMocks } from './spy'; +import { + clearAllMocks, + fn, + isMockFunction, + onMockCall, + resetAllMocks, + restoreAllMocks, +} from './spy'; +import type { Renderer } from '@storybook/types'; export * from './spy'; @@ -36,8 +44,52 @@ const resetAllMocksLoader: LoaderFunction = ({ parameters }) => { } }; +export const traverseArgs = (value: unknown, depth = 0, key?: string): unknown => { + // Make sure to not get in infinite loops with self referencing args + if (depth > 5) return value; + if (value == null) return value; + if (isMockFunction(value)) { + // Makes sure we get the arg name in the interactions panel + if (key) value.mockName(key); + return value; + } + + // wrap explicit actions in a spy + if ( + typeof value === 'function' && + 'isAction' in value && + value.isAction && + !('implicit' in value && value.implicit) + ) { + const mock = fn(value as any); + if (key) mock.mockName(key); + return mock; + } + + if (Array.isArray(value)) { + depth++; + return value.map((item) => traverseArgs(item, depth)); + } + + if (typeof value === 'object' && value.constructor === Object) { + depth++; + for (const [k, v] of Object.entries(value)) { + if (Object.getOwnPropertyDescriptor(value, k)?.writable) { + // We have to mutate the original object for this to survive HMR. + (value as Record)[k] = traverseArgs(v, depth, k); + } + } + return value; + } + return value; +}; + +const nameSpiesAndWrapActionsInSpies: LoaderFunction = ({ initialArgs }) => { + traverseArgs(initialArgs); +}; + // We are using this as a default Storybook loader, when the test package is used. This avoids the need for optional peer dependency workarounds. // eslint-disable-next-line no-underscore-dangle -(global as any).__STORYBOOK_TEST_LOADERS__ = [resetAllMocksLoader]; +(global as any).__STORYBOOK_TEST_LOADERS__ = [resetAllMocksLoader, nameSpiesAndWrapActionsInSpies]; // eslint-disable-next-line no-underscore-dangle (global as any).__STORYBOOK_TEST_ON_MOCK_CALL__ = onMockCall; From 412b52583812369b9ec04f16087510cd04a6e9f9 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 9 Apr 2024 20:13:10 +0200 Subject: [PATCH 64/78] Move test --- code/addons/interactions/src/preview.test.ts | 42 -------------------- code/lib/test/src/index.test.ts | 42 +++++++++++++++++++- 2 files changed, 40 insertions(+), 44 deletions(-) delete mode 100644 code/addons/interactions/src/preview.test.ts diff --git a/code/addons/interactions/src/preview.test.ts b/code/addons/interactions/src/preview.test.ts deleted file mode 100644 index 5cfec9d19d03..000000000000 --- a/code/addons/interactions/src/preview.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import { fn, isMockFunction } from '@storybook/test'; -import { action } from '@storybook/addon-actions'; - -import { traverseArgs } from './preview'; - -describe('traverseArgs', () => { - const args = { - deep: { - deeper: { - fnKey: fn(), - actionKey: action('name'), - }, - }, - arg2: Object.freeze({ frozen: true }), - }; - - expect(args.deep.deeper.fnKey.getMockName()).toEqual('spy'); - - const traversed = traverseArgs(args) as typeof args; - - test('The same structure is maintained', () => - expect(traversed).toEqual({ - deep: { - deeper: { - fnKey: args.deep.deeper.fnKey, - actionKey: args.deep.deeper.actionKey, - }, - }, - // We don't mutate frozen objects, but we do insert them back in the tree - arg2: args.arg2, - })); - - test('The mock name is mutated to be the arg key', () => - expect(traversed.deep.deeper.fnKey.getMockName()).toEqual('fnKey')); - - const actionFn = traversed.deep.deeper.actionKey; - - test('Actions are wrapped in a spy', () => expect(isMockFunction(actionFn)).toBeTruthy()); - test('The spy of the action is also matching the arg key ', () => - expect(isMockFunction(actionFn) && actionFn.getMockName()).toEqual('actionKey')); -}); diff --git a/code/lib/test/src/index.test.ts b/code/lib/test/src/index.test.ts index bed58592c1d6..70f1df02b0f1 100644 --- a/code/lib/test/src/index.test.ts +++ b/code/lib/test/src/index.test.ts @@ -1,8 +1,46 @@ -import { it } from 'vitest'; -import { expect, fn } from '@storybook/test'; +import { describe, it, test } from 'vitest'; +import { expect, fn, isMockFunction, traverseArgs } from '@storybook/test'; +import { action } from '@storybook/addon-actions/src'; it('storybook expect and fn can be used in vitest test', () => { const spy = fn(); spy(1); expect(spy).toHaveBeenCalledWith(1); }); + +describe('traverseArgs', () => { + const args = { + deep: { + deeper: { + fnKey: fn(), + actionKey: action('name'), + }, + }, + arg2: Object.freeze({ frozen: true }), + }; + + expect(args.deep.deeper.fnKey.getMockName()).toEqual('spy'); + + const traversed = traverseArgs(args) as typeof args; + + test('The same structure is maintained', () => + expect(traversed).toEqual({ + deep: { + deeper: { + fnKey: args.deep.deeper.fnKey, + actionKey: args.deep.deeper.actionKey, + }, + }, + // We don't mutate frozen objects, but we do insert them back in the tree + arg2: args.arg2, + })); + + test('The mock name is mutated to be the arg key', () => + expect(traversed.deep.deeper.fnKey.getMockName()).toEqual('fnKey')); + + const actionFn = traversed.deep.deeper.actionKey; + + test('Actions are wrapped in a spy', () => expect(isMockFunction(actionFn)).toBeTruthy()); + test('The spy of the action is also matching the arg key ', () => + expect(isMockFunction(actionFn) && actionFn.getMockName()).toEqual('actionKey')); +}); From f3d14529eda905eaf3c213dc99af5736b5398969 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 9 Apr 2024 20:22:49 +0200 Subject: [PATCH 65/78] Resolve review --- code/lib/test/src/spy.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/code/lib/test/src/spy.ts b/code/lib/test/src/spy.ts index a8fa2b935b1c..81f5c71eda71 100644 --- a/code/lib/test/src/spy.ts +++ b/code/lib/test/src/spy.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-shadow */ -import type { Mock, MockInstance } from '@vitest/spy'; +import type { MockInstance } from '@vitest/spy'; import { spyOn as vitestSpyOn, isMockFunction, @@ -27,20 +27,17 @@ export function onMockCall(callback: Listener): () => void { }; } -// @ts-expect-error TS is hard you know +// @ts-expect-error Make sure we export the exact same type as @vitest/spy export const spyOn: typeof vitestSpyOn = (...args) => { const mock = vitestSpyOn(...(args as Parameters)); return reactiveMock(mock); }; -export function fn(): Mock; -export function fn( - implementation: (...args: TArgs) => R -): Mock; -export function fn(implementation?: (...args: TArgs) => R) { +// @ts-expect-error Make sure we export the exact same type as @vitest/spy +export const fn: typeof vitestFn = (implementation) => { const mock = implementation ? vitestFn(implementation) : vitestFn(); return reactiveMock(mock); -} +}; function reactiveMock(mock: MockInstance) { const reactive = listenWhenCalled(mock); From a78474717c4fb52edbc8c3e8e6b84c1e5218eca9 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 9 Apr 2024 20:40:43 +0200 Subject: [PATCH 66/78] Fix test --- .../actions/template/stories/spies.stories.ts | 25 +++++++++++++++++++ code/e2e-tests/addon-actions.spec.ts | 22 ++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 code/addons/actions/template/stories/spies.stories.ts diff --git a/code/addons/actions/template/stories/spies.stories.ts b/code/addons/actions/template/stories/spies.stories.ts new file mode 100644 index 000000000000..dd1147305793 --- /dev/null +++ b/code/addons/actions/template/stories/spies.stories.ts @@ -0,0 +1,25 @@ +import { global as globalThis } from '@storybook/global'; +import { withActions } from '@storybook/addon-actions/decorator'; +import { spyOn } from '@storybook/test'; + +export default { + component: globalThis.Components.Button, + loaders() { + spyOn(console, 'log').mockName('console.log'); + }, + args: { + label: 'Button', + }, + parameters: { + chromatic: { disable: true }, + }, +}; + +export const ShowSpyOnInActions = { + args: { + onClick: () => { + console.log('first'); + console.log('second'); + }, + }, +}; diff --git a/code/e2e-tests/addon-actions.spec.ts b/code/e2e-tests/addon-actions.spec.ts index 3b93599e81ad..a67aa8599ce9 100644 --- a/code/e2e-tests/addon-actions.spec.ts +++ b/code/e2e-tests/addon-actions.spec.ts @@ -26,4 +26,26 @@ test.describe('addon-actions', () => { }); await expect(logItem).toBeVisible(); }); + + test('should show spies', async ({ page }) => { + test.skip( + templateName.includes('svelte') && templateName.includes('prerelease'), + 'Svelte 5 prerelase does not support automatic actions with our current example components yet' + ); + await page.goto(storybookUrl); + const sbPage = new SbPage(page); + sbPage.waitUntilLoaded(); + + await sbPage.navigateToStory('addons/actions/spies', 'show-spy-on-in-actions'); + + const root = sbPage.previewRoot(); + const button = root.locator('button', { hasText: 'Button' }); + await button.click(); + + await sbPage.viewAddonPanel('Actions'); + const logItem = await page.locator('#storybook-panel-root #panel-tab-content', { + hasText: 'console.log', + }); + await expect(logItem).toBeVisible(); + }); }); From 840070f063c8077f73e7daec10688e67168766ae Mon Sep 17 00:00:00 2001 From: Eric Berndt Date: Tue, 9 Apr 2024 19:32:40 +0000 Subject: [PATCH 67/78] add to cli-options docs --- docs/api/cli-options.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api/cli-options.md b/docs/api/cli-options.md index 24c0c5d8b002..a1f8c4369fff 100644 --- a/docs/api/cli-options.md +++ b/docs/api/cli-options.md @@ -132,6 +132,7 @@ Options include: | Option | Description | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `-h`, `--help` | Output usage information
`storybook add --help` | +| `-c`, `--config-dir` | Directory where to load Storybook configurations from
`storybook migrate --config-dir .storybook` | | `--package-manager` | Sets the package manager to use when installing the addon.
Available package managers include `npm`, `yarn`, and `pnpm`
`storybook add [addon] --package-manager pnpm` | | `-s`, `--skip-postinstall` | Skips post-install configuration. Used only when you need to configure the addon yourself
`storybook add [addon] --skip-postinstall` | | `--debug` | Outputs more logs in the CLI to assist debugging
`storybook add --debug` | From bf1fe1062530544283257b6dc20ff8b32f51953a Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 10 Apr 2024 07:23:25 +0200 Subject: [PATCH 68/78] Fix test --- code/addons/actions/template/stories/spies.stories.ts | 1 - code/lib/test/src/index.test.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/code/addons/actions/template/stories/spies.stories.ts b/code/addons/actions/template/stories/spies.stories.ts index dd1147305793..824494bda1c9 100644 --- a/code/addons/actions/template/stories/spies.stories.ts +++ b/code/addons/actions/template/stories/spies.stories.ts @@ -1,5 +1,4 @@ import { global as globalThis } from '@storybook/global'; -import { withActions } from '@storybook/addon-actions/decorator'; import { spyOn } from '@storybook/test'; export default { diff --git a/code/lib/test/src/index.test.ts b/code/lib/test/src/index.test.ts index 70f1df02b0f1..87f5b2206418 100644 --- a/code/lib/test/src/index.test.ts +++ b/code/lib/test/src/index.test.ts @@ -1,6 +1,6 @@ import { describe, it, test } from 'vitest'; import { expect, fn, isMockFunction, traverseArgs } from '@storybook/test'; -import { action } from '@storybook/addon-actions/src'; +import { action } from '@storybook/addon-actions'; it('storybook expect and fn can be used in vitest test', () => { const spy = fn(); From e98dfb857aa6eb246dc534d06f139304cd835666 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 10 Apr 2024 07:29:18 +0200 Subject: [PATCH 69/78] Fix test --- .../react/src/__test__/portable-stories.test.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/code/renderers/react/src/__test__/portable-stories.test.tsx b/code/renderers/react/src/__test__/portable-stories.test.tsx index 6c3c38055065..e204eeb0a653 100644 --- a/code/renderers/react/src/__test__/portable-stories.test.tsx +++ b/code/renderers/react/src/__test__/portable-stories.test.tsx @@ -4,8 +4,6 @@ import { vi, it, expect, afterEach, describe } from 'vitest'; import { render, screen, cleanup } from '@testing-library/react'; import { addons } from '@storybook/preview-api'; -import * as addonInteractionsPreview from '@storybook/addon-interactions/preview'; - import * as addonActionsPreview from '@storybook/addon-actions/preview'; import type { Meta } from '@storybook/react'; import { expectTypeOf } from 'expect-type'; @@ -90,9 +88,9 @@ describe('projectAnnotations', () => { expect(buttonElement).not.toBeNull(); }); - it('has spies when addon-interactions annotations are added', async () => { - //@ts-expect-error TODO investigate - const Story = composeStory(stories.WithActionArg, stories.default, addonInteractionsPreview); + it('has spies when the test loader is loaded', async () => { + const Story = composeStory(stories.WithActionArg, stories.default); + await Story.load(); expect(vi.mocked(Story.args.someActionArg!).mock).toBeDefined(); const { container } = render(); From 176dbee017f60d87196671e1f44265cbb1e64868 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 10 Apr 2024 07:32:36 +0200 Subject: [PATCH 70/78] Fix test --- code/renderers/react/src/__test__/portable-stories.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/renderers/react/src/__test__/portable-stories.test.tsx b/code/renderers/react/src/__test__/portable-stories.test.tsx index e204eeb0a653..aab2585743d5 100644 --- a/code/renderers/react/src/__test__/portable-stories.test.tsx +++ b/code/renderers/react/src/__test__/portable-stories.test.tsx @@ -88,7 +88,7 @@ describe('projectAnnotations', () => { expect(buttonElement).not.toBeNull(); }); - it('has spies when the test loader is loaded', async () => { + it('explicit action are spies when the test loader is loaded', async () => { const Story = composeStory(stories.WithActionArg, stories.default); await Story.load(); expect(vi.mocked(Story.args.someActionArg!).mock).toBeDefined(); From f9cb4199ee2a7b01349c95ba74d463a14df57002 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 10 Apr 2024 10:26:37 +0200 Subject: [PATCH 71/78] Make listeners a set --- code/lib/test/src/spy.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/code/lib/test/src/spy.ts b/code/lib/test/src/spy.ts index 81f5c71eda71..6a97a19c390e 100644 --- a/code/lib/test/src/spy.ts +++ b/code/lib/test/src/spy.ts @@ -18,13 +18,11 @@ export type * from '@vitest/spy'; export { isMockFunction, mocks }; type Listener = (mock: MockInstance, args: unknown[]) => void; -let listeners: Listener[] = []; +const listeners = new Set(); export function onMockCall(callback: Listener): () => void { - listeners = [...listeners, callback]; - return () => { - listeners = listeners.filter((listener) => listener !== callback); - }; + listeners.add(callback); + return () => void listeners.delete(callback); } // @ts-expect-error Make sure we export the exact same type as @vitest/spy From ce192c2945b29f2a05d44f04b56226197bda8a34 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 10 Apr 2024 10:42:28 +0200 Subject: [PATCH 72/78] resolve review comment https://github.com/storybookjs/storybook/pull/26766#pullrequestreview-1990988907 --- code/lib/cli/src/automigrate/fixes/vta.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/code/lib/cli/src/automigrate/fixes/vta.ts b/code/lib/cli/src/automigrate/fixes/vta.ts index b641513b55a7..3bb301ac8d80 100644 --- a/code/lib/cli/src/automigrate/fixes/vta.ts +++ b/code/lib/cli/src/automigrate/fixes/vta.ts @@ -41,12 +41,9 @@ export const vta: Fix = { async run({ packageManager, dryRun, mainConfigPath, skipInstall }) { if (!dryRun) { const packageJson = await packageManager.retrievePackageJson(); - const versionToInstall = getStorybookVersionSpecifier( - await packageManager.retrievePackageJson() - ); await packageManager.addDependencies( { installAsDevDependencies: true, skipInstall, packageJson }, - [`@storybook/addon-mdx-gfm@${versionToInstall}`] + [`@chromatic-com/storybook@^1`] ); await updateMainConfig({ mainConfigPath, dryRun: !!dryRun }, async (main) => { From 1a619602ee09d6002284660c88a28edf39d33fb3 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 10 Apr 2024 10:44:16 +0200 Subject: [PATCH 73/78] resolve review comment https://github.com/storybookjs/storybook/pull/26766#pullrequestreview-1989730532 --- code/lib/cli/src/automigrate/fixes/vta.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/code/lib/cli/src/automigrate/fixes/vta.ts b/code/lib/cli/src/automigrate/fixes/vta.ts index 3bb301ac8d80..07daef3c9a31 100644 --- a/code/lib/cli/src/automigrate/fixes/vta.ts +++ b/code/lib/cli/src/automigrate/fixes/vta.ts @@ -1,5 +1,5 @@ import { dedent } from 'ts-dedent'; -import { updateMainConfig } from '../helpers/mainConfigFile'; +import { getAddonNames, updateMainConfig } from '../helpers/mainConfigFile'; import type { Fix } from '../types'; import { getStorybookVersionSpecifier } from '../../helpers'; @@ -15,10 +15,8 @@ export const vta: Fix = { versionRange: ['<8.0.7', '>=8.0.7'], async check({ mainConfig }) { - const hadAddonInstalled = !!mainConfig?.addons?.find((addon) => - typeof addon === 'string' - ? addon.includes('@chromatic-com/storybook') - : addon.name.includes('@chromatic-com/storybook') + const hadAddonInstalled = !!getAddonNames(mainConfig).find((addon) => + addon.includes('@chromatic-com/storybook') ); // @ts-expect-error (user might be upgrading from an older version that still had it) From 374520ea38c6b3cbe4cf04b891f000193d25d830 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 10 Apr 2024 10:44:58 +0200 Subject: [PATCH 74/78] resolve review comment https://github.com/storybookjs/storybook/pull/26766#pullrequestreview-1989726124 --- code/lib/cli/src/automigrate/fixes/vta.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/lib/cli/src/automigrate/fixes/vta.ts b/code/lib/cli/src/automigrate/fixes/vta.ts index 07daef3c9a31..ce582793f3a0 100644 --- a/code/lib/cli/src/automigrate/fixes/vta.ts +++ b/code/lib/cli/src/automigrate/fixes/vta.ts @@ -19,8 +19,6 @@ export const vta: Fix = { addon.includes('@chromatic-com/storybook') ); - // @ts-expect-error (user might be upgrading from an older version that still had it) - const usesMDX1 = mainConfig?.features?.legacyMdx1 === true || false; const skip = hadAddonInstalled; if (skip) { From c9c9a2e76e30cc6c26f01865dae1c8a1b1cc380b Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 10 Apr 2024 10:47:10 +0200 Subject: [PATCH 75/78] Add text provided by @joevaugh4n --- code/lib/cli/src/automigrate/fixes/vta.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/code/lib/cli/src/automigrate/fixes/vta.ts b/code/lib/cli/src/automigrate/fixes/vta.ts index ce582793f3a0..776019ff0f41 100644 --- a/code/lib/cli/src/automigrate/fixes/vta.ts +++ b/code/lib/cli/src/automigrate/fixes/vta.ts @@ -1,7 +1,6 @@ import { dedent } from 'ts-dedent'; import { getAddonNames, updateMainConfig } from '../helpers/mainConfigFile'; import type { Fix } from '../types'; -import { getStorybookVersionSpecifier } from '../../helpers'; const logger = console; @@ -30,7 +29,11 @@ export const vta: Fix = { prompt() { return dedent` - We've detected that you're not yet using the Visual Testing Addon. Would you like to add it? + New to Storybook 8: Storybook's Visual Tests addon helps you catch unintentional changes/bugs in your stories. The addon is powered by Chromatic, a cloud-based testing tool developed by Storybook's core team. + + Learn more: storybook.js.org/docs/writing-tests/visual-testing + + Install Visual Tests addon in your project? `; }, From 3b8bc279cc57bb8f10767cf3f183e1fbef7161ca Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 10 Apr 2024 16:12:16 +0200 Subject: [PATCH 76/78] Fix review comments --- code/lib/cli/src/automigrate/fixes/vta.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/code/lib/cli/src/automigrate/fixes/vta.ts b/code/lib/cli/src/automigrate/fixes/vta.ts index 776019ff0f41..00e911174619 100644 --- a/code/lib/cli/src/automigrate/fixes/vta.ts +++ b/code/lib/cli/src/automigrate/fixes/vta.ts @@ -1,6 +1,7 @@ import { dedent } from 'ts-dedent'; import { getAddonNames, updateMainConfig } from '../helpers/mainConfigFile'; import type { Fix } from '../types'; +import chalk from 'chalk'; const logger = console; @@ -9,12 +10,12 @@ interface Options {} /** */ export const vta: Fix = { - id: 'visual-testing-addon', + id: 'visual-tests-addon', versionRange: ['<8.0.7', '>=8.0.7'], async check({ mainConfig }) { - const hadAddonInstalled = !!getAddonNames(mainConfig).find((addon) => + const hadAddonInstalled = getAddonNames(mainConfig).some((addon) => addon.includes('@chromatic-com/storybook') ); @@ -31,7 +32,7 @@ export const vta: Fix = { return dedent` New to Storybook 8: Storybook's Visual Tests addon helps you catch unintentional changes/bugs in your stories. The addon is powered by Chromatic, a cloud-based testing tool developed by Storybook's core team. - Learn more: storybook.js.org/docs/writing-tests/visual-testing + Learn more: ${chalk.yellow('storybook.js.org/docs/writing-tests/visual-testing')} Install Visual Tests addon in your project? `; @@ -47,9 +48,7 @@ export const vta: Fix = { await updateMainConfig({ mainConfigPath, dryRun: !!dryRun }, async (main) => { logger.info(`✅ Adding "@chromatic-com/storybook" addon`); - if (!dryRun) { - main.appendValueToArray(['addons'], '@chromatic-dom/storybook'); - } + main.appendValueToArray(['addons'], '@chromatic-dom/storybook'); }); } }, From 8a3e90903337d31b32453014dac7eaec60bd4780 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 10 Apr 2024 16:13:50 +0200 Subject: [PATCH 77/78] cleanup --- code/lib/cli/src/automigrate/fixes/vta.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/cli/src/automigrate/fixes/vta.ts b/code/lib/cli/src/automigrate/fixes/vta.ts index 00e911174619..c86f957298a8 100644 --- a/code/lib/cli/src/automigrate/fixes/vta.ts +++ b/code/lib/cli/src/automigrate/fixes/vta.ts @@ -1,7 +1,7 @@ import { dedent } from 'ts-dedent'; +import chalk from 'chalk'; import { getAddonNames, updateMainConfig } from '../helpers/mainConfigFile'; import type { Fix } from '../types'; -import chalk from 'chalk'; const logger = console; From 7e29280d2dda5434762f5e534fad6e46f233117a Mon Sep 17 00:00:00 2001 From: storybook-bot <32066757+storybook-bot@users.noreply.github.com> Date: Wed, 10 Apr 2024 15:43:43 +0000 Subject: [PATCH 78/78] Write changelog for 8.1.0-alpha.7 [skip ci] --- CHANGELOG.prerelease.md | 9 +++++++++ code/package.json | 3 ++- docs/versions/next.json | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index ada76261b3cb..5d6baeac2136 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,12 @@ +## 8.1.0-alpha.7 + +- CLI: Add --config-dir flag to add command - [#26771](https://github.com/storybookjs/storybook/pull/26771), thanks @eric-blue! +- CLI: Add Visual Tests addon install auto-migration when upgrading to 8.0.x - [#26766](https://github.com/storybookjs/storybook/pull/26766), thanks @ndelangen! +- Controls: Add Channels API to search for files in the project root - [#26726](https://github.com/storybookjs/storybook/pull/26726), thanks @valentinpalkovic! +- Test: Make spies reactive so that they can be logged by addon-actions - [#26740](https://github.com/storybookjs/storybook/pull/26740), thanks @kasperpeulen! +- Vue: Disable controls for events, slots, and expose - [#26751](https://github.com/storybookjs/storybook/pull/26751), thanks @shilman! +- Webpack: Bump webpack-dev-middleware to patch high security issue - [#26655](https://github.com/storybookjs/storybook/pull/26655), thanks @jwilliams-met! + ## 8.1.0-alpha.6 - CLI: Add --config-dir flag to migrate command - [#26721](https://github.com/storybookjs/storybook/pull/26721), thanks @yannbf! diff --git a/code/package.json b/code/package.json index adca4665bd72..553d8999b1a4 100644 --- a/code/package.json +++ b/code/package.json @@ -299,5 +299,6 @@ "Dependency Upgrades" ] ] - } + }, + "deferredNextVersion": "8.1.0-alpha.7" } diff --git a/docs/versions/next.json b/docs/versions/next.json index a2ba413b8e56..2f8990d20967 100644 --- a/docs/versions/next.json +++ b/docs/versions/next.json @@ -1 +1 @@ -{"version":"8.1.0-alpha.6","info":{"plain":"- CLI: Add --config-dir flag to migrate command - [#26721](https://github.com/storybookjs/storybook/pull/26721), thanks @yannbf!\n- Core: Add `duration` and `onClick` support to Notification API and improve Notification UI - [#26696](https://github.com/storybookjs/storybook/pull/26696), thanks @ghengeveld!\n- Dependency: Bump es-module-lexer - [#26737](https://github.com/storybookjs/storybook/pull/26737), thanks @valentinpalkovic!\n- Dependency: Update globby dependency - [#26733](https://github.com/storybookjs/storybook/pull/26733), thanks @valentinpalkovic!\n- Dependency: Update postcss-loader in Next.js framework - [#26707](https://github.com/storybookjs/storybook/pull/26707), thanks @valentinpalkovic!\n- Next.js: Fix next/font usage on Windows machines - [#26700](https://github.com/storybookjs/storybook/pull/26700), thanks @valentinpalkovic!\n- Webpack: Fix sourcemap generation in webpack react-docgen-loader - [#26676](https://github.com/storybookjs/storybook/pull/26676), thanks @valentinpalkovic!"}} +{"version":"8.1.0-alpha.7","info":{"plain":"- CLI: Add --config-dir flag to add command - [#26771](https://github.com/storybookjs/storybook/pull/26771), thanks @eric-blue!\n- CLI: Add Visual Tests addon install auto-migration when upgrading to 8.0.x - [#26766](https://github.com/storybookjs/storybook/pull/26766), thanks @ndelangen!\n- Controls: Add Channels API to search for files in the project root - [#26726](https://github.com/storybookjs/storybook/pull/26726), thanks @valentinpalkovic!\n- Test: Make spies reactive so that they can be logged by addon-actions - [#26740](https://github.com/storybookjs/storybook/pull/26740), thanks @kasperpeulen!\n- Vue: Disable controls for events, slots, and expose - [#26751](https://github.com/storybookjs/storybook/pull/26751), thanks @shilman!\n- Webpack: Bump webpack-dev-middleware to patch high security issue - [#26655](https://github.com/storybookjs/storybook/pull/26655), thanks @jwilliams-met!"}}