Merge pull request #28886 from storybookjs/norbert/linting-part3

Linting: Jsdoc & Bracket style consistency
This commit is contained in:
Norbert de Langen 2024-08-15 11:58:44 +02:00 committed by GitHub
commit 010e68f96a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
575 changed files with 5785 additions and 4227 deletions

View File

@ -16,7 +16,7 @@ import {
import { DocsContext } from '@storybook/blocks';
import { global } from '@storybook/global';
import type { Decorator, ReactRenderer } from '@storybook/react';
import type { Decorator, Loader, ReactRenderer } from '@storybook/react';
import { MINIMAL_VIEWPORTS } from '@storybook/addon-viewport';
@ -95,7 +95,6 @@ const StackContainer = ({ children, layout }) => (
height: '100%',
display: 'flex',
flexDirection: 'column',
// margin: layout === 'fullscreen' ? 0 : '-1rem',
}}
>
<style dangerouslySetInnerHTML={{ __html: 'html, body, #storybook-root { height: 100%; }' }} />
@ -124,15 +123,21 @@ const preview = (window as any).__STORYBOOK_PREVIEW__ as PreviewWeb<ReactRendere
const channel = (window as any).__STORYBOOK_ADDONS_CHANNEL__ as Channel;
export const loaders = [
/**
* This loader adds a DocsContext to the story, which is required for the most Blocks to work.
* A story will specify which stories they need in the index with:
* This loader adds a DocsContext to the story, which is required for the most Blocks to work. A
* story will specify which stories they need in the index with:
*
* ```ts
* parameters: {
* relativeCsfPaths: ['../stories/MyStory.stories.tsx'], // relative to the story
* relativeCsfPaths: ['../stories/MyStory.stories.tsx'], // relative to the story
* }
* ```
*
* The DocsContext will then be added via the decorator below.
*/
async ({ parameters: { relativeCsfPaths, attached = true } }) => {
if (!relativeCsfPaths) return {};
if (!relativeCsfPaths) {
return {};
}
const csfFiles = await Promise.all(
(relativeCsfPaths as string[]).map(async (blocksRelativePath) => {
const projectRelativePath = `./lib/blocks/src/${blocksRelativePath.replace(
@ -185,7 +190,8 @@ export const decorators = [
<Story />
),
/**
* This decorator renders the stories side-by-side, stacked or default based on the theme switcher in the toolbar
* This decorator renders the stories side-by-side, stacked or default based on the theme switcher
* in the toolbar
*/
(StoryFn, { globals, playFunction, args, storyGlobals, parameters }) => {
let theme = globals.sb_theme;
@ -267,9 +273,9 @@ export const decorators = [
}
},
/**
* This decorator shows the current state of the arg named in the
* parameters.withRawArg property, by updating the arg in the onChange function
* this also means that the arg will sync with the control panel
* This decorator shows the current state of the arg named in the parameters.withRawArg property,
* by updating the arg in the onChange function this also means that the arg will sync with the
* control panel
*
* If parameters.withRawArg is not set, this decorator will do nothing
*/

View File

@ -15,10 +15,7 @@ let activeStoryId: string | undefined;
const defaultParameters = { config: {}, options: {} };
/**
* Handle A11yContext events.
* Because the event are sent without manual check, we split calls
*/
/** Handle A11yContext events. Because the event are sent without manual check, we split calls */
const handleRequest = async (storyId: string, input: A11yParameters | null) => {
if (!input?.manual) {
await run(storyId, input ?? defaultParameters);

View File

@ -105,7 +105,9 @@ export const A11yContextProvider: React.FC<React.PropsWithChildren<A11yContextPr
}
}, [active, handleClearHighlights, emit, storyEntry]);
if (!active) return null;
if (!active) {
return null;
}
return (
<A11yContext.Provider

View File

@ -11,8 +11,8 @@ const isInInitialArgs = (name: string, initialArgs: Args) =>
typeof initialArgs[name] === 'undefined' && !(name in initialArgs);
/**
* Automatically add action args for argTypes whose name
* matches a regex, such as `^on.*` for react-style `onClick` etc.
* Automatically add action args for argTypes whose name matches a regex, such as `^on.*` for
* react-style `onClick` etc.
*/
export const inferActionsFromArgTypesRegex: ArgsEnhancer<Renderer> = (context) => {
@ -39,9 +39,7 @@ export const inferActionsFromArgTypesRegex: ArgsEnhancer<Renderer> = (context) =
}, {} as Args);
};
/**
* Add action args for list of strings.
*/
/** Add action args for list of strings. */
export const addActionsFromArgTypes: ArgsEnhancer<Renderer> = (context) => {
const {
initialArgs,

View File

@ -48,7 +48,11 @@ export const ActionLogger = ({ actions, onClear }: ActionLoggerProps) => {
useEffect(() => {
// Scroll to bottom, when the action panel was already scrolled down
if (wasAtBottom) wrapperRef.current.scrollTop = wrapperRef.current.scrollHeight;
// Scroll to bottom, when the action panel was already scrolled down
if (wasAtBottom) {
wrapperRef.current.scrollTop = wrapperRef.current.scrollHeight;
}
}, [wasAtBottom, actions.length]);
return (

View File

@ -12,7 +12,10 @@ const logActionsWhenMockCalled: LoaderFunction = (context) => {
const {
parameters: { actions },
} = context;
if (actions?.disable) return;
if (actions?.disable) {
return;
}
if (
!subscribed &&
@ -22,7 +25,10 @@ const logActionsWhenMockCalled: LoaderFunction = (context) => {
const onMockCall = global.__STORYBOOK_TEST_ON_MOCK_CALL__ as typeof onMockCallType;
onMockCall((mock, args) => {
const name = mock.getMockName();
if (name === 'spy') return;
if (name === 'spy') {
return;
}
// TODO: Make this a configurable API in 8.2
if (

View File

@ -14,7 +14,10 @@ import { config } from './configureActions';
type SyntheticEvent = any; // import('react').SyntheticEvent;
const findProto = (obj: unknown, callback: (proto: any) => boolean): Function | null => {
const proto = Object.getPrototypeOf(obj);
if (!proto || callback(proto)) return proto;
if (!proto || callback(proto)) {
return proto;
}
return findProto(proto, callback);
};
const isReactSyntheticEvent = (e: unknown): e is SyntheticEvent =>

View File

@ -61,16 +61,21 @@ export const ControlsPanel = ({ saveStory, createStory }: ControlsPanelProps) =>
// If the story is prepared, then show the args table
// and reset the loading states
useEffect(() => {
if (previewInitialized) setIsLoading(false);
if (previewInitialized) {
setIsLoading(false);
}
}, [previewInitialized]);
const hasControls = Object.values(rows).some((arg) => arg?.control);
const withPresetColors = Object.entries(rows).reduce((acc, [key, arg]) => {
const control = arg?.control;
if (typeof control !== 'object' || control?.type !== 'color' || control?.presetColors)
if (typeof control !== 'object' || control?.type !== 'color' || control?.presetColors) {
acc[key] = arg;
else acc[key] = { ...arg, control: { ...control, presetColors } };
} else {
acc[key] = { ...arg, control: { ...control, presetColors } };
}
return acc;
}, {} as ArgTypes);

View File

@ -105,7 +105,9 @@ export const SaveStory = ({ saveStory, createStory, resetArgs }: SaveStoryProps)
const [errorMessage, setErrorMessage] = React.useState(null);
const onSaveStory = async () => {
if (saving) return;
if (saving) {
return;
}
setSaving(true);
await saveStory().catch(() => {});
setSaving(false);
@ -125,7 +127,10 @@ export const SaveStory = ({ saveStory, createStory, resetArgs }: SaveStoryProps)
};
const onSubmitForm = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (saving) return;
if (saving) {
return;
}
try {
setErrorMessage(null);
setSaving(true);

View File

@ -41,7 +41,9 @@ function Title() {
const stringifyArgs = (args: Record<string, any>) =>
JSON.stringify(args, (_, value) => {
if (typeof value === 'function') return '__sb_empty_function_arg__';
if (typeof value === 'function') {
return '__sb_empty_function_arg__';
}
return value;
});
@ -50,7 +52,10 @@ addons.register(ADDON_ID, (api) => {
const saveStory = async () => {
const data = api.getCurrentStoryData();
if (data.type !== 'story') throw new Error('Not a story');
if (data.type !== 'story') {
throw new Error('Not a story');
}
try {
const response = await experimental_requestResponse<
@ -60,7 +65,9 @@ addons.register(ADDON_ID, (api) => {
// Only send updated args
args: stringifyArgs(
Object.entries(data.args || {}).reduce<Args>((acc, [key, value]) => {
if (!deepEqual(value, data.initialArgs?.[key])) acc[key] = value;
if (!deepEqual(value, data.initialArgs?.[key])) {
acc[key] = value;
}
return acc;
}, {})
),
@ -98,7 +105,10 @@ addons.register(ADDON_ID, (api) => {
const createStory = async (name: string) => {
const data = api.getCurrentStoryData();
if (data.type !== 'story') throw new Error('Not a story');
if (data.type !== 'story') {
throw new Error('Not a story');
}
const response = await experimental_requestResponse<
SaveStoryRequestPayload,
@ -146,9 +156,14 @@ addons.register(ADDON_ID, (api) => {
});
channel.on(SAVE_STORY_RESPONSE, (data: ResponseData<SaveStoryResponsePayload>) => {
if (!data.success) return;
if (!data.success) {
return;
}
const story = api.getCurrentStoryData();
if (story.type !== 'story') return;
if (story.type !== 'story') {
return;
}
api.resetStoryArgs(story);
if (data.payload.newStoryId) {

View File

@ -28,7 +28,9 @@ export async function mdxPlugin(options: Options): Promise<Plugin> {
name: 'storybook:mdx-plugin',
enforce: 'pre',
async transform(src, id) {
if (!filter(id)) return undefined;
if (!filter(id)) {
return undefined;
}
const mdxLoaderOptions: CompileOptions = await presets.apply('mdxLoaderOptions', {
...mdxPluginOptions,

View File

@ -11,9 +11,8 @@ import rehypeSlug from 'rehype-slug';
import type { CompileOptions } from './compiler';
/**
* Get the resolvedReact preset, which points either to
* the user's react dependencies or the react dependencies shipped with addon-docs
* if the user has not installed react explicitly.
* Get the resolvedReact preset, which points either to the user's react dependencies or the react
* dependencies shipped with addon-docs if the user has not installed react explicitly.
*/
const getResolvedReact = async (options: Options) => {
const resolvedReact = (await options.presets.apply('resolvedReact', {})) as any;
@ -66,10 +65,13 @@ async function webpack(
let alias;
/** Add aliases for `@storybook/addon-docs` & `@storybook/blocks`
* These must be singletons to avoid multiple instances of react & emotion being loaded, both would cause the components to fail to render.
/**
* Add aliases for `@storybook/addon-docs` & `@storybook/blocks` These must be singletons to avoid
* multiple instances of react & emotion being loaded, both would cause the components to fail to
* render.
*
* In the future the `@storybook/theming` and `@storybook/components` can be removed, as they should be singletons in the future due to the peerDependency on `storybook` package.
* In the future the `@storybook/theming` and `@storybook/components` can be removed, as they
* should be singletons in the future due to the peerDependency on `storybook` package.
*/
const cliPath = dirname(require.resolve('storybook/package.json'));
const themingPath = join(cliPath, 'core', 'theming', 'index.js');
@ -193,10 +195,14 @@ export const viteFinal = async (config: any, options: Options) => {
...(isAbsolute(reactDom) && { 'react-dom/server': `${reactDom}/server.browser.js` }),
'react-dom': reactDom,
'@mdx-js/react': mdx,
/** Add aliases for `@storybook/addon-docs` & `@storybook/blocks`
* These must be singletons to avoid multiple instances of react & emotion being loaded, both would cause the components to fail to render.
/**
* Add aliases for `@storybook/addon-docs` & `@storybook/blocks` These must be singletons
* to avoid multiple instances of react & emotion being loaded, both would cause the
* components to fail to render.
*
* In the future the `@storybook/theming` and `@storybook/components` can be removed, as they should be singletons in the future due to the peerDependency on `storybook` package.
* In the future the `@storybook/theming` and `@storybook/components` can be removed, as
* they should be singletons in the future due to the peerDependency on `storybook`
* package.
*/
'@storybook/theming/create': themingCreatePath,
'@storybook/theming': themingPath,
@ -224,11 +230,10 @@ const webpackX = webpack as any;
const docsX = docs as any;
/**
* If the user has not installed react explicitly in their project,
* the resolvedReact preset will not be set.
* We then set it here in addon-docs to use addon-docs's react version that always exists.
* This is just a fallback that never overrides the existing preset,
* but ensures that there is always a resolved react.
* If the user has not installed react explicitly in their project, the resolvedReact preset will
* not be set. We then set it here in addon-docs to use addon-docs's react version that always
* exists. This is just a fallback that never overrides the existing preset, but ensures that there
* is always a resolved react.
*/
export const resolvedReact = async (existing: any) => ({
react: existing?.react ?? dirname(require.resolve('react/package.json')),

View File

@ -5,15 +5,18 @@ import * as ReactDomServer from 'react-dom/server';
import { expect, within } from '@storybook/test';
/**
* This component is used to display the resolved version of React and its related packages.
* As long as `@storybook/addon-docs` is installed, `react` and `react-dom` should be available to import from and should resolve to the same version.
* This component is used to display the resolved version of React and its related packages. As long
* as `@storybook/addon-docs` is installed, `react` and `react-dom` should be available to import
* from and should resolve to the same version.
*
* The autodocs here ensures that it also works in the generated documentation.
*
* - See the [MDX docs](/docs/addons-docs-docs2-resolvedreact--mdx) for how it resolves in MDX.
* - See the [Story](/story/addons-docs-docs2-resolvedreact--story) for how it resolves in the actual story.
* - See the [Story](/story/addons-docs-docs2-resolvedreact--story) for how it resolves in the actual
* story.
*
* **Note: There appears to be a bug in the _production_ build of `react-dom`, where it reports version `18.2.0-next-9e3b772b8-20220608` while in fact version `18.2.0` is installed.**
* **Note: There appears to be a bug in the _production_ build of `react-dom`, where it reports
* version `18.2.0-next-9e3b772b8-20220608` while in fact version `18.2.0` is installed.**
*/
export default {
title: 'Docs2/ResolvedReact',

View File

@ -8,24 +8,18 @@ export default {
parameters: { chromatic: { disable: true } },
};
/**
* A basic button
*/
/** A basic button */
export const Basic = {
args: { label: 'Basic' },
};
/**
* Won't show up in DocsPage
*/
/** Won't show up in DocsPage */
export const Disabled = {
args: { label: 'Disabled in DocsPage' },
parameters: { docs: { disable: true } },
};
/**
* Another button, just to show multiple stories
*/
/** Another button, just to show multiple stories */
export const Another = {
args: { label: 'Another' },
parameters: {

View File

@ -7,9 +7,7 @@ export default {
parameters: { chromatic: { disable: true } },
};
/**
* A story that throws
*/
/** A story that throws */
export const ErrorStory = {
decorators: [
() => {

View File

@ -14,7 +14,7 @@
```js
import { setCustomElementsManifest } from '@storybook/web-components';
import customElements from '../custom-elements.json';
setCustomElementsManifest(customElements);
```

View File

@ -6,52 +6,60 @@ import { logger } from 'storybook/internal/node-logger';
interface PresetOptions {
/**
* Allow to use @storybook/addon-actions
* @see https://storybook.js.org/addons/@storybook/addon-actions
*
* @default true
* @see https://storybook.js.org/addons/@storybook/addon-actions
*/
actions?: boolean;
/**
* Allow to use @storybook/addon-backgrounds
* @see https://storybook.js.org/addons/@storybook/addon-backgrounds
*
* @default true
* @see https://storybook.js.org/addons/@storybook/addon-backgrounds
*/
backgrounds?: boolean;
configDir: string;
/**
* Allow to use @storybook/addon-controls
* @see https://storybook.js.org/addons/@storybook/addon-controls
*
* @default true
* @see https://storybook.js.org/addons/@storybook/addon-controls
*/
controls?: boolean;
/**
* Allow to use @storybook/addon-docs
* @see https://storybook.js.org/addons/@storybook/addon-docs
*
* @default true
* @see https://storybook.js.org/addons/@storybook/addon-docs
*/
docs?: boolean;
/**
* Allow to use @storybook/addon-measure
* @see https://storybook.js.org/addons/@storybook/addon-measure
*
* @default true
* @see https://storybook.js.org/addons/@storybook/addon-measure
*/
measure?: boolean;
/**
* Allow to use @storybook/addon-outline
* @see https://storybook.js.org/addons/@storybook/addon-outline
*
* @default true
* @see https://storybook.js.org/addons/@storybook/addon-outline
*/
outline?: boolean;
themes?: boolean;
/**
* Allow to use @storybook/addon-toolbars
* @see https://storybook.js.org/addons/@storybook/addon-toolbars
*
* @default true
* @see https://storybook.js.org/addons/@storybook/addon-toolbars
*/
toolbars?: boolean;
/**
* Allow to use @storybook/addon-viewport
* @see https://storybook.js.org/addons/@storybook/addon-viewport
*
* @default true
* @see https://storybook.js.org/addons/@storybook/addon-viewport
*/
viewport?: boolean;
}

View File

@ -17,7 +17,7 @@ const highlightStyle = (color = '#FF4785', style: OutlineStyle = 'dashed') => `
`;
interface HighlightInfo {
/** html selector of the element */
/** HTML selector of the element */
elements: string[];
color: string;
style: OutlineStyle;

View File

@ -123,7 +123,10 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
([end]: any) => setScrollTarget(end.isIntersecting ? undefined : end.target),
{ root: global.document.querySelector('#panel-tab-content') }
);
if (endRef.current) observer.observe(endRef.current);
if (endRef.current) {
observer.observe(endRef.current);
}
}
return () => observer?.disconnect();
}, []);

View File

@ -40,7 +40,9 @@ export const Empty = () => {
return () => clearTimeout(load);
}, []);
if (isLoading) return null;
if (isLoading) {
return null;
}
return (
<EmptyTabContent

View File

@ -165,7 +165,9 @@ export const Interaction = ({
const [isHovered, setIsHovered] = React.useState(false);
const isInteractive = !controlStates.goto || !call.interceptable || !!call.ancestors.length;
if (isHidden) return null;
if (isHidden) {
return null;
}
return (
<RowContainer call={call} pausedAt={pausedAt}>

View File

@ -60,7 +60,9 @@ export const Passing: Story = {
interactions: getInteractions(CallStates.DONE),
},
play: async ({ args, canvasElement }) => {
if (isChromatic()) return;
if (isChromatic()) {
return;
}
const canvas = within(canvasElement);
await waitFor(async () => {

View File

@ -6,9 +6,15 @@ import { Node } from './MethodCall';
const getParams = (line: string, fromIndex = 0): string => {
for (let i = fromIndex, depth = 1; i < line.length; i += 1) {
if (line[i] === '(') depth += 1;
else if (line[i] === ')') depth -= 1;
if (depth === 0) return line.slice(fromIndex, i);
if (line[i] === '(') {
depth += 1;
} else if (line[i] === ')') {
depth -= 1;
}
if (depth === 0) {
return line.slice(fromIndex, i);
}
}
return '';
};

View File

@ -69,7 +69,9 @@ const useThemeColors = () => {
const special = /[^A-Z0-9]/i;
const trimEnd = /[\s.,…]+$/gm;
const ellipsize = (string: string, maxlength: number): string => {
if (string.length <= maxlength) return string;
if (string.length <= maxlength) {
return string;
}
for (let i = maxlength - 1; i >= 0; i -= 1) {
if (special.test(string[i]) && i > 10) {
return `${string.slice(0, i).replace(trimEnd, '')}`;
@ -102,10 +104,7 @@ export const Node = ({
}: {
value: any;
nested?: boolean;
/**
* Shows an object inspector instead of just printing the object.
* Only available for Objects
*/
/** Shows an object inspector instead of just printing the object. Only available for Objects */
showObjectInspector?: boolean;
callsById?: Map<Call['id'], Call>;
[props: string]: any;
@ -422,7 +421,9 @@ export const MethodCall = ({
callsById: Map<Call['id'], Call>;
}) => {
// Call might be undefined during initial render, can be safely ignored.
if (!call) return null;
if (!call) {
return null;
}
if (call.method === 'step' && call.path.length === 0) {
return <StepNode label={call.args[0]} />;

View File

@ -1,7 +1,4 @@
/**
* Interaction Testing Theme
* PLACEHOLDER until SB is updated <3
*/
/** Interaction Testing Theme PLACEHOLDER until SB is updated <3 */
interface Colors {
pure?: {
gray?: any;

View File

@ -73,7 +73,9 @@ const colorizeText: (msg: string, type: string) => MsgElement[] = (msg: string,
const getConvertedText: (msg: string) => MsgElement[] = (msg: string) => {
let elementArray: MsgElement[] = [];
if (!msg) return elementArray;
if (!msg) {
return elementArray;
}
const splitText = msg.split(/\[2m/).join('').split(/\[22m/);

View File

@ -40,8 +40,13 @@ function roundedRect(context: CanvasRenderingContext2D, { x, y, w, h, r }: Round
x = x - w / 2;
y = y - h / 2;
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
if (w < 2 * r) {
r = w / 2;
}
if (h < 2 * r) {
r = h / 2;
}
context.beginPath();
context.moveTo(x + r, y);
@ -81,10 +86,8 @@ function positionCoordinate(
}
/**
* Offset the label based on how many layers appear before it
* For example:
* margin labels will shift further outwards if there are
* padding labels
* Offset the label based on how many layers appear before it For example: margin labels will shift
* further outwards if there are padding labels
*/
function offset(
type: LabelType,

View File

@ -1,6 +1,4 @@
/**
* Based on https://gist.github.com/awestbro/e668c12662ad354f02a413205b65fce7
*/
/** Based on https://gist.github.com/awestbro/e668c12662ad354f02a413205b65fce7 */
import { global } from '@storybook/global';
import { draw } from './canvas';

View File

@ -85,7 +85,10 @@ export default function Onboarding({ api }: { api: API }) {
(storyId: string) => {
try {
const { id, refId } = api.getCurrentStoryData() || {};
if (id !== storyId || refId !== undefined) api.selectStory(storyId);
if (id !== storyId || refId !== undefined) {
api.selectStory(storyId);
}
} catch (e) {}
},
[api]
@ -132,17 +135,30 @@ export default function Onboarding({ api }: { api: API }) {
useEffect(() => {
setStep((current) => {
if (['1:Intro', '5:StoryCreated', '6:FinishedOnboarding'].includes(current)) return current;
if (createNewStoryForm) return '4:CreateStory';
if (saveFromControls) return '3:SaveFromControls';
if (primaryControl) return '2:Controls';
if (['1:Intro', '5:StoryCreated', '6:FinishedOnboarding'].includes(current)) {
return current;
}
if (createNewStoryForm) {
return '4:CreateStory';
}
if (saveFromControls) {
return '3:SaveFromControls';
}
if (primaryControl) {
return '2:Controls';
}
return '1:Intro';
});
}, [createNewStoryForm, primaryControl, saveFromControls]);
useEffect(() => {
return api.on(SAVE_STORY_RESPONSE, ({ payload, success }) => {
if (!success || !payload?.newStoryName) return;
if (!success || !payload?.newStoryName) {
return;
}
setCreatedStory(payload);
setShowConfetti(true);
setStep('5:StoryCreated');

View File

@ -20,22 +20,49 @@ const StyledButton = styled.button<{ variant: ButtonProps['variant'] }>`
justify-content: center;
padding: 0 0.75rem;
background: ${({ theme, variant }) => {
if (variant === 'primary') return theme.color.secondary;
if (variant === 'secondary') return theme.color.lighter;
if (variant === 'outline') return 'transparent';
if (variant === 'white') return theme.color.lightest;
if (variant === 'primary') {
return theme.color.secondary;
}
if (variant === 'secondary') {
return theme.color.lighter;
}
if (variant === 'outline') {
return 'transparent';
}
if (variant === 'white') {
return theme.color.lightest;
}
return theme.color.secondary;
}};
color: ${({ theme, variant }) => {
if (variant === 'primary') return theme.color.lightest;
if (variant === 'secondary') return theme.darkest;
if (variant === 'outline') return theme.darkest;
if (variant === 'white') return theme.color.secondary;
if (variant === 'primary') {
return theme.color.lightest;
}
if (variant === 'secondary') {
return theme.darkest;
}
if (variant === 'outline') {
return theme.darkest;
}
if (variant === 'white') {
return theme.color.secondary;
}
return theme.color.lightest;
}};
box-shadow: ${({ variant }) => {
if (variant === 'secondary') return '#D9E8F2 0 0 0 1px inset';
if (variant === 'outline') return '#D9E8F2 0 0 0 1px inset';
if (variant === 'secondary') {
return '#D9E8F2 0 0 0 1px inset';
}
if (variant === 'outline') {
return '#D9E8F2 0 0 0 1px inset';
}
return 'none';
}};
height: 32px;
@ -49,27 +76,60 @@ const StyledButton = styled.button<{ variant: ButtonProps['variant'] }>`
&:hover {
background-color: ${({ theme, variant }) => {
if (variant === 'primary') return '#0b94eb';
if (variant === 'secondary') return '#eef4f9';
if (variant === 'outline') return 'transparent';
if (variant === 'white') return theme.color.lightest;
if (variant === 'primary') {
return '#0b94eb';
}
if (variant === 'secondary') {
return '#eef4f9';
}
if (variant === 'outline') {
return 'transparent';
}
if (variant === 'white') {
return theme.color.lightest;
}
return '#0b94eb';
}};
color: ${({ theme, variant }) => {
if (variant === 'primary') return theme.color.lightest;
if (variant === 'secondary') return theme.darkest;
if (variant === 'outline') return theme.darkest;
if (variant === 'white') return theme.color.darkest;
if (variant === 'primary') {
return theme.color.lightest;
}
if (variant === 'secondary') {
return theme.darkest;
}
if (variant === 'outline') {
return theme.darkest;
}
if (variant === 'white') {
return theme.color.darkest;
}
return theme.color.lightest;
}};
}
&:focus {
box-shadow: ${({ variant }) => {
if (variant === 'primary') return 'inset 0 0 0 1px rgba(0, 0, 0, 0.2)';
if (variant === 'secondary') return 'inset 0 0 0 1px #0b94eb';
if (variant === 'outline') return 'inset 0 0 0 1px #0b94eb';
if (variant === 'white') return 'none';
if (variant === 'primary') {
return 'inset 0 0 0 1px rgba(0, 0, 0, 0.2)';
}
if (variant === 'secondary') {
return 'inset 0 0 0 1px #0b94eb';
}
if (variant === 'outline') {
return 'inset 0 0 0 1px #0b94eb';
}
if (variant === 'white') {
return 'none';
}
return 'inset 0 0 0 2px rgba(0, 0, 0, 0.1)';
}};
}

View File

@ -26,15 +26,23 @@ export function GuidedTour({
let timeout: NodeJS.Timeout;
setStepIndex((current) => {
const index = steps.findIndex(({ key }) => key === step);
if (index === -1) return null;
if (index === current) return current;
if (index === -1) {
return null;
}
if (index === current) {
return current;
}
timeout = setTimeout(setStepIndex, 500, index);
return null;
});
return () => clearTimeout(timeout);
}, [step, steps]);
if (stepIndex === null) return null;
if (stepIndex === null) {
return null;
}
return (
<Joyride
@ -46,8 +54,13 @@ export function GuidedTour({
disableOverlayClose
disableScrolling
callback={(data: CallBackProps) => {
if (data.action === ACTIONS.CLOSE) onClose();
if (data.action === ACTIONS.NEXT && data.index === data.size - 1) onComplete();
if (data.action === ACTIONS.CLOSE) {
onClose();
}
if (data.action === ACTIONS.NEXT && data.index === data.size - 1) {
onComplete();
}
}}
floaterProps={{
disableAnimation: true,

View File

@ -117,7 +117,10 @@ export const Tooltip: FC<TooltipProps> = ({
document.head.appendChild(style);
return () => {
const styleElement = document.querySelector('#sb-onboarding-arrow-style');
if (styleElement) styleElement.remove();
if (styleElement) {
styleElement.remove();
}
};
}, []);

View File

@ -194,7 +194,9 @@ export const SplashScreen = ({ onDismiss, duration = 6000 }: SplashScreenProps)
}, [onDismiss]);
useEffect(() => {
if (!duration) return;
if (!duration) {
return;
}
const framelength = 1000 / 50; // 50 frames per second
const increment = 100 / (duration / framelength); // 0-100% at 20ms intervals
const interval = setInterval(() => setProgress((prev) => prev + increment), framelength);
@ -202,7 +204,9 @@ export const SplashScreen = ({ onDismiss, duration = 6000 }: SplashScreenProps)
}, [duration]);
useEffect(() => {
if (ready) dismiss();
if (ready) {
dismiss();
}
}, [ready, dismiss]);
return (

View File

@ -5,7 +5,6 @@ import type { ThemeParameters } from '../constants';
import { DEFAULT_THEME_PARAMETERS, GLOBAL_KEY, PARAM_KEY, THEMING_EVENTS } from '../constants';
/**
*
* @param StoryContext
* @returns The global theme name set for your stories
*/

View File

@ -8,9 +8,7 @@ import type { ToolbarArgType } from '../types';
import { normalizeArgType } from '../utils/normalize-toolbar-arg-type';
import { ToolbarMenuList } from './ToolbarMenuList';
/**
* A smart component for handling manager-preview interactions.
*/
/** A smart component for handling manager-preview interactions. */
export const ToolbarManager: FC = () => {
const globalTypes = useGlobalTypes();
const globalIds = Object.keys(globalTypes).filter((id) => !!globalTypes[id].toolbar);

View File

@ -1,25 +1,27 @@
export type UserOptions = {
/**
* The directory where the Storybook configuration is located, relative to the vitest configuration file.
* If not provided, the plugin will use '.storybook' in the current working directory.
* @default '.storybook'
* The directory where the Storybook configuration is located, relative to the vitest
* configuration file. If not provided, the plugin will use '.storybook' in the current working
* directory.
*
* @default '.storybook'
*/
configDir?: string;
/**
* Optional script to run Storybook.
* If provided, Vitest will start Storybook using this script when ran in watch mode.
* @default undefined
* Optional script to run Storybook. If provided, Vitest will start Storybook using this script
* when ran in watch mode.
*
* @default undefined
*/
storybookScript?: string;
/**
* The URL where Storybook is hosted.
* This is used to provide a link to the story in the test output on failures.
* @default 'http://localhost:6006'
* The URL where Storybook is hosted. This is used to provide a link to the story in the test
* output on failures.
*
* @default 'http://localhost:6006'
*/
storybookUrl?: string;
/**
* Tags to include, exclude, or skip. These tags are defined as annotations in your story or meta.
*/
/** Tags to include, exclude, or skip. These tags are defined as annotations in your story or meta. */
tags?: {
include?: string[];
exclude?: string[];

View File

@ -16,7 +16,10 @@ interface ViewportsParam {
export const setViewport = async (viewportsParam: ViewportsParam = {} as ViewportsParam) => {
const defaultViewport = viewportsParam.defaultViewport;
if (!page || !globalThis.__vitest_browser__ || !defaultViewport) return null;
if (!page || !globalThis.__vitest_browser__ || !defaultViewport) {
return null;
}
const viewports = {
...INITIAL_VIEWPORTS,

View File

@ -5,13 +5,14 @@ import type { Options } from 'storybook/internal/types';
import { listStories } from './list-stories';
/**
* This file is largely based on https://github.com/storybookjs/storybook/blob/d1195cbd0c61687f1720fefdb772e2f490a46584/lib/core-common/src/utils/to-importFn.ts
* This file is largely based on
* https://github.com/storybookjs/storybook/blob/d1195cbd0c61687f1720fefdb772e2f490a46584/lib/core-common/src/utils/to-importFn.ts
*/
/**
* Paths get passed either with no leading './' - e.g. `src/Foo.stories.js`,
* or with a leading `../` (etc), e.g. `../src/Foo.stories.js`.
* We want to deal in importPaths relative to the working dir, so we normalize
* Paths get passed either with no leading './' - e.g. `src/Foo.stories.js`, or with a leading `../`
* (etc), e.g. `../src/Foo.stories.js`. We want to deal in importPaths relative to the working dir,
* so we normalize
*/
function toImportPath(relativePath: string) {
return relativePath.startsWith('../') ? relativePath : `./${relativePath}`;
@ -19,9 +20,10 @@ function toImportPath(relativePath: string) {
/**
* This function takes an array of stories and creates a mapping between the stories' relative paths
* to the working directory and their dynamic imports. The import is done in an asynchronous function
* to delay loading. It then creates a function, `importFn(path)`, which resolves a path to an import
* function and this is called by Storybook to fetch a story dynamically when needed.
* to the working directory and their dynamic imports. The import is done in an asynchronous
* function to delay loading. It then creates a function, `importFn(path)`, which resolves a path to
* an import function and this is called by Storybook to fetch a story dynamically when needed.
*
* @param stories An array of absolute story paths.
*/
async function toImportFn(stories: string[]) {

View File

@ -59,9 +59,11 @@ export async function generateModernIframeScriptCode(options: Options, projectRo
};
/**
* This code is largely taken from https://github.com/storybookjs/storybook/blob/d1195cbd0c61687f1720fefdb772e2f490a46584/builders/builder-webpack4/src/preview/virtualModuleModernEntry.js.handlebars
* Some small tweaks were made to `getProjectAnnotations` (since `import()` needs to be resolved asynchronously)
* and the HMR implementation has been tweaked to work with Vite.
* This code is largely taken from
* https://github.com/storybookjs/storybook/blob/d1195cbd0c61687f1720fefdb772e2f490a46584/builders/builder-webpack4/src/preview/virtualModuleModernEntry.js.handlebars
* Some small tweaks were made to `getProjectAnnotations` (since `import()` needs to be resolved
* asynchronously) and the HMR implementation has been tweaked to work with Vite.
*
* @todo Inline variable and remove `noinspection`
*/
const code = `

View File

@ -16,8 +16,8 @@ const allowedEnvVariables = [
];
/**
* Customized version of stringifyProcessEnvs from @storybook/core-common which
* uses import.meta.env instead of process.env and checks for allowed variables.
* Customized version of stringifyProcessEnvs from @storybook/core-common which uses import.meta.env
* instead of process.env and checks for allowed variables.
*/
export function stringifyProcessEnvs(raw: Builder_EnvsRaw, envPrefix: ViteConfig['envPrefix']) {
const updatedRaw: Builder_EnvsRaw = {};

View File

@ -111,7 +111,8 @@ const INCLUDE_CANDIDATES = [
];
/**
* Helper function which allows us to `filter` with an async predicate. Uses Promise.all for performance.
* Helper function which allows us to `filter` with an async predicate. Uses Promise.all for
* performance.
*/
const asyncFilter = async (arr: string[], predicate: (val: string) => Promise<boolean>) =>
Promise.all(arr.map(predicate)).then((results) => arr.filter((_v, index) => results[index]));

View File

@ -18,23 +18,24 @@ const replacementMap = new Map([
]);
/**
* This plugin swaps out imports of pre-bundled storybook preview modules for destructured from global
* variables that are added in runtime.js.
* This plugin swaps out imports of pre-bundled storybook preview modules for destructured from
* global variables that are added in runtime.js.
*
* For instance:
* @example
*
* ```js
* import { useMemo as useMemo2, useEffect as useEffect2 } from "@storybook/preview-api";
* import { useMemo as useMemo2, useEffect as useEffect2 } from '@storybook/preview-api';
* ```
*
* becomes
* Becomes
*
* ```js
* const { useMemo: useMemo2, useEffect: useEffect2 } = __STORYBOOK_MODULE_PREVIEW_API__;
* ```
*
* It is based on existing plugins like https://github.com/crcong/vite-plugin-externals
* and https://github.com/eight04/rollup-plugin-external-globals, but simplified to meet our simple needs.
* It is based on existing plugins like https://github.com/crcong/vite-plugin-externals and
* https://github.com/eight04/rollup-plugin-external-globals, but simplified to meet our simple
* needs.
*/
export async function externalGlobalsPlugin(externals: Record<string, string>) {
await init;
@ -72,7 +73,10 @@ export async function externalGlobalsPlugin(externals: Record<string, string>) {
// Replace imports with variables destructured from global scope
async transform(code: string, id: string) {
const globalsList = Object.keys(externals);
if (globalsList.every((glob) => !code.includes(glob))) return undefined;
if (globalsList.every((glob) => !code.includes(glob))) {
return undefined;
}
const [imports] = parse(code);
const src = new MagicString(code);

View File

@ -12,7 +12,9 @@ export async function injectExportOrderPlugin() {
// This should only run after the typescript has been transpiled
enforce: 'post',
async transform(code: string, id: string) {
if (!filter(id)) return undefined;
if (!filter(id)) {
return undefined;
}
// TODO: Maybe convert `injectExportOrderPlugin` to function that returns object,
// and run `await init;` once and then call `parse()` without `await`,

View File

@ -2,8 +2,8 @@ import MagicString from 'magic-string';
import type { Plugin } from 'vite';
/**
* This plugin removes HMR `accept` calls in story files. Stories should not be treated
* as hmr boundaries, but vite has a bug which causes them to be treated as boundaries
* This plugin removes HMR `accept` calls in story files. Stories should not be treated as hmr
* boundaries, but vite has a bug which causes them to be treated as boundaries
* (https://github.com/vitejs/vite/issues/9869).
*/
export async function stripStoryHMRBoundary(): Promise<Plugin> {
@ -14,7 +14,9 @@ export async function stripStoryHMRBoundary(): Promise<Plugin> {
name: 'storybook:strip-hmr-boundary-plugin',
enforce: 'post',
async transform(src: string, id: string) {
if (!filter(id)) return undefined;
if (!filter(id)) {
return undefined;
}
const s = new MagicString(src);
s.replace(/import\.meta\.hot\.accept\(\);/, '');

View File

@ -25,15 +25,14 @@ type WebpackStatsPluginOptions = {
};
/**
* Strips off query params added by rollup/vite to ids, to make paths compatible for comparison with git.
* Strips off query params added by rollup/vite to ids, to make paths compatible for comparison with
* git.
*/
function stripQueryParams(filePath: string): string {
return filePath.split('?')[0];
}
/**
* We only care about user code, not node_modules, vite files, or (most) virtual files.
*/
/** We only care about user code, not node_modules, vite files, or (most) virtual files. */
function isUserCode(moduleName: string) {
return Boolean(
moduleName &&
@ -48,9 +47,7 @@ function isUserCode(moduleName: string) {
export type WebpackStatsPlugin = Plugin & { storybookGetStats: () => BuilderStats };
export function pluginWebpackStats({ workingDir }: WebpackStatsPluginOptions): WebpackStatsPlugin {
/**
* Convert an absolute path name to a path relative to the vite root, with a starting `./`
*/
/** Convert an absolute path name to a path relative to the vite root, with a starting `./` */
function normalize(filename: string) {
// Do not try to resolve virtual files
if (filename.startsWith('/virtual:')) {
@ -64,16 +61,12 @@ export function pluginWebpackStats({ workingDir }: WebpackStatsPluginOptions): W
}
}
/**
* Helper to create Reason objects out of a list of string paths
*/
/** Helper to create Reason objects out of a list of string paths */
function createReasons(importers?: readonly string[]): Reason[] {
return (importers || []).map((i) => ({ moduleName: normalize(i) }));
}
/**
* Helper function to build a `Module` given a filename and list of files that import it
*/
/** Helper function to build a `Module` given a filename and list of files that import it */
function createStatsMapModule(filename: string, importers?: readonly string[]): Module {
return {
id: filename,

View File

@ -19,8 +19,6 @@ export type StorybookConfigVite = {
};
export type BuilderOptions = {
/**
* Path to vite.config file, relative to CWD.
*/
/** Path to `vite.config` file, relative to `process.cwd()`. */
viteConfigPath?: string;
};

View File

@ -7,8 +7,8 @@ function checkName(plugin: PluginOption, names: string[]) {
}
/**
* Returns true if ANY of the plugins in the array have a name that matches one of the names in the names array.
* Will resolve any promises in the array.
* Returns true if ANY of the plugins in the array have a name that matches one of the names in the
* names array. Will resolve any promises in the array.
*/
export async function hasVitePlugins(plugins: PluginOption[], names: string[]) {
const resolvedPlugins = await Promise.all(plugins);

View File

@ -6,11 +6,10 @@ import type { PreviewAnnotation } from 'storybook/internal/types';
import slash from 'slash';
/**
* Preview annotations can take several forms, and vite needs them to be
* a bit more restrained.
* Preview annotations can take several forms, and vite needs them to be a bit more restrained.
*
* For node_modules, we want bare imports (so vite can process them),
* and for files in the user's source, we want URLs absolute relative to project root.
* For node_modules, we want bare imports (so vite can process them), and for files in the user's
* source, we want URLs absolute relative to project root.
*/
export function processPreviewAnnotation(path: PreviewAnnotation | undefined, projectRoot: string) {
// If entry is an object, take the first, which is the

View File

@ -1,9 +1,6 @@
import type { PluginOption } from 'vite';
/**
* Recursively removes all plugins with the names given
* Resolves async plugins
*/
/** Recursively removes all plugins with the names given Resolves async plugins */
export const withoutVitePlugins = async (
plugins: PluginOption[] = [],
namesToRemove: string[]

View File

@ -106,8 +106,8 @@ export const bail: WebpackBuilder['bail'] = async () => {
};
/**
* This function is a generator so that we can abort it mid process
* in case of failure coming from other processes e.g. preview builder
* This function is a generator so that we can abort it mid process in case of failure coming from
* other processes e.g. preview builder
*
* I am sorry for making you read about generators today :')
*/
@ -231,8 +231,8 @@ function getWebpackStats({ config, stats }: { config: Configuration; stats: Stat
}
/**
* This function is a generator so that we can abort it mid process
* in case of failure coming from other processes e.g. manager builder
* This function is a generator so that we can abort it mid process in case of failure coming from
* other processes e.g. manager builder
*
* I am sorry for making you read about generators today :')
*/

View File

@ -10,26 +10,20 @@ import type { Configuration, Stats } from 'webpack';
type TypeScriptOptionsBase = Partial<WebpackTypescriptOptions>;
/**
* Options for TypeScript usage within Storybook.
*/
/** Options for TypeScript usage within Storybook. */
export interface TypescriptOptions extends TypeScriptOptionsBase {
/**
* Configures `fork-ts-checker-webpack-plugin`
*/
/** Configures `fork-ts-checker-webpack-plugin` */
checkOptions?: ConstructorParameters<typeof ForkTsCheckerWebpackPlugin>[0];
}
export interface StorybookConfigWebpack extends Omit<StorybookConfig, 'webpack' | 'webpackFinal'> {
/**
* Modify or return a custom Webpack config after the Storybook's default configuration
* has run (mostly used by addons).
* Modify or return a custom Webpack config after the Storybook's default configuration has run
* (mostly used by addons).
*/
webpack?: (config: Configuration, options: Options) => Configuration | Promise<Configuration>;
/**
* Modify or return a custom Webpack config after every addon has run.
*/
/** Modify or return a custom Webpack config after every addon has run. */
webpackFinal?: (
config: Configuration,
options: Options

View File

@ -13,8 +13,10 @@ export async function generatePackageJsonFile(entries: ReturnType<typeof getEntr
const location = join(cwd, 'package.json');
const pkgJson = await readJSON(location);
/** Re-create the `exports` field in `code/core/package.json`
* This way we only need to update the `./scripts/entries.ts` file to ensure all things we create actually exist and are mapped to the correct path.
/**
* Re-create the `exports` field in `code/core/package.json` This way we only need to update the
* `./scripts/entries.ts` file to ensure all things we create actually exist and are mapped to the
* correct path.
*/
pkgJson.exports = entries.reduce<Record<string, Record<string, string>>>((acc, entry) => {
let main = './' + slash(relative(cwd, entry.file).replace('src', 'dist'));
@ -47,8 +49,11 @@ export async function generatePackageJsonFile(entries: ReturnType<typeof getEntr
// Add the package.json file to the exports, so we can use it to `require.resolve` the package's root easily
pkgJson.exports['./package.json'] = './package.json';
/** Add the `typesVersion` field to `code/core/package.json`, to make typescript respect and find the correct type annotation files, even when not configured with `"moduleResolution": "Bundler"`
* If we even decide to only support `"moduleResolution": "Bundler"`, we should be able to remove this part, but that would be a breaking change.
/**
* Add the `typesVersion` field to `code/core/package.json`, to make typescript respect and find
* the correct type annotation files, even when not configured with `"moduleResolution":
* "Bundler"` If we even decide to only support `"moduleResolution": "Bundler"`, we should be able
* to remove this part, but that would be a breaking change.
*/
pkgJson.typesVersions = {
'*': {

View File

@ -20,12 +20,14 @@ async function generateTypesMapperContent(filePath: string) {
}
export async function generateTypesMapperFiles(entries: ReturnType<typeof getEntries>) {
/** Generate the type mapper files, which are used to map the types to the SOURCE location.
* This would be for development builds ONLY, **HOWEVER**:
* During a production build we ALSO run this, because we want to generate a `d.ts` file for each entry in parallel.
* By generating these files (in parallel) first, we can then ensure we can compile the actual type definitions in parallel.
* This is because the type definitions have interdependencies between them.
* These interdependencies are MEGA complex, and this simplified approach immensely is the only way to ensure we can compile them in parallel.
/**
* Generate the type mapper files, which are used to map the types to the SOURCE location. This
* would be for development builds ONLY, **HOWEVER**: During a production build we ALSO run this,
* because we want to generate a `d.ts` file for each entry in parallel. By generating these files
* (in parallel) first, we can then ensure we can compile the actual type definitions in parallel.
* This is because the type definitions have interdependencies between them. These
* interdependencies are MEGA complex, and this simplified approach immensely is the only way to
* ensure we can compile them in parallel.
*/
const all = entries.filter((e) => e.dts).map((e) => e.file);

View File

@ -5,9 +5,10 @@ import { dedent } from '../../../../scripts/prepare/tools';
export async function modifyThemeTypes() {
/**
* This is a unique hack (pre-existing the CPC project) because the only way to set a custom Theme interface with emotion, is by module enhancement.
* This is not an option for us, because we pre-bundle emotion in.
* The little hack work to ensure the `Theme` export is overloaded with our `StorybookTheme` interface. (in both development and production builds)
* This is a unique hack (pre-existing the CPC project) because the only way to set a custom Theme
* interface with emotion, is by module enhancement. This is not an option for us, because we
* pre-bundle emotion in. The little hack work to ensure the `Theme` export is overloaded with our
* `StorybookTheme` interface. (in both development and production builds)
*/
const target = join(__dirname, '..', '..', 'dist', 'theming', 'index.d.ts');
const contents = await readFile(target, 'utf-8');

View File

@ -312,9 +312,10 @@ async function run() {
await context.dispose();
/**
* I'm leaving this in place, because I want to start utilizing it in the future.
* I'm imagining a github action that shows the bundle analysis in the PR.
* I didn't have the project-scope to make that happen now, but I want expose this very rich useful data accessible, for the next person investigating bundle size issues.
* I'm leaving this in place, because I want to start utilizing it in the future. I'm
* imagining a github action that shows the bundle analysis in the PR. I didn't have the
* project-scope to make that happen now, but I want expose this very rich useful data
* accessible, for the next person investigating bundle size issues.
*/
// if (out.metafile) {

View File

@ -116,8 +116,8 @@ export const executor = {
};
/**
* This function is a generator so that we can abort it mid process
* in case of failure coming from other processes e.g. preview builder
* This function is a generator so that we can abort it mid process in case of failure coming from
* other processes e.g. preview builder
*
* I am sorry for making you read about generators today :')
*/
@ -214,8 +214,8 @@ const starter: StarterFunction = async function* starterGeneratorFn({
};
/**
* This function is a generator so that we can abort it mid process
* in case of failure coming from other processes e.g. preview builder
* This function is a generator so that we can abort it mid process in case of failure coming from
* other processes e.g. preview builder
*
* I am sorry for making you read about generators today :')
*/

View File

@ -21,20 +21,23 @@ const sanitizeFinal = (path: string) => {
};
/**
* Manager entries should be **self-invoking** bits of code.
* They can of-course import from modules, and ESbuild will bundle all of that into a single file.
* But they should not export anything. However this can't be enforced, so what we do is wrap the given file, in a bit of code like this:
* Manager entries should be **self-invoking** bits of code. They can of-course import from modules,
* and ESbuild will bundle all of that into a single file. But they should not export anything.
* However this can't be enforced, so what we do is wrap the given file, in a bit of code like
* this:
*
* ```js
* import '<<file>>';
* ```
*
* That way we are indicating to ESbuild that we do not care about this files exports, and they will be dropped in the bundle.
* That way we are indicating to ESbuild that we do not care about this files exports, and they will
* be dropped in the bundle.
*
* We do all of that so we can wrap a try-catch around the code.
* That would have been invalid syntax had the export statements been left in place.
* We do all of that so we can wrap a try-catch around the code. That would have been invalid syntax
* had the export statements been left in place.
*
* We need to wrap each managerEntry with a try-catch because if we do not, a failing managerEntry can stop execution of other managerEntries.
* We need to wrap each managerEntry with a try-catch because if we do not, a failing managerEntry
* can stop execution of other managerEntries.
*/
export async function wrapManagerEntries(entrypoints: string[], uniqueId?: string) {
return Promise.all(

View File

@ -21,9 +21,11 @@ type Options = Config & {
/**
* Creates a new browser channel instance.
*
* @param {Options} options - The options object.
* @param {Page} options.page - page identifier.
* @param {ChannelTransport[]} [options.extraTransports=[]] - An optional array of extra channel transports.
* @param {Page} options.page - Page identifier.
* @param {ChannelTransport[]} [options.extraTransports=[]] - An optional array of extra channel
* transports. Default is `[]`
* @returns {Channel} - The new channel instance.
*/
export function createBrowserChannel({ page, extraTransports = [] }: Options): Channel {

View File

@ -58,8 +58,9 @@ export class PostMessageTransport implements ChannelTransport {
}
/**
* Sends `event` to the associated window. If the window does not yet exist
* the event will be stored in a buffer and sent when the window exists.
* Sends `event` to the associated window. If the window does not yet exist the event will be
* stored in a buffer and sent when the window exists.
*
* @param event
*/
send(event: ChannelEvent, options?: any): Promise<any> {

View File

@ -107,8 +107,8 @@ export function detectFrameworkPreset(
}
/**
* Attempts to detect which builder to use, by searching for a vite config file or webpack installation.
* If neither are found it will choose the default builder based on the project type.
* Attempts to detect which builder to use, by searching for a vite config file or webpack
* installation. If neither are found it will choose the default builder based on the project type.
*
* @returns CoreBuilder
*/

View File

@ -51,9 +51,17 @@ export async function extractEslintInfo(packageManager: JsPackageManager): Promi
}
export const normalizeExtends = (existingExtends: any): string[] => {
if (!existingExtends) return [];
if (typeof existingExtends === 'string') return [existingExtends];
if (Array.isArray(existingExtends)) return existingExtends;
if (!existingExtends) {
return [];
}
if (typeof existingExtends === 'string') {
return [existingExtends];
}
if (Array.isArray(existingExtends)) {
return existingExtends;
}
throw new Error(`Invalid eslint extends ${existingExtends}`);
};

View File

@ -50,17 +50,25 @@ export const writeFileAsJson = (jsonPath: string, content: unknown) => {
};
/**
* Detect if any babel dependencies need to be added to the project
* This is currently used by react-native generator
* @param {Object} packageJson The current package.json so we can inspect its contents
* @returns {Array} Contains the packages and versions that need to be installed
* Detect if any babel dependencies need to be added to the project This is currently used by
* react-native generator
*
* @example
* const babelDependencies = await getBabelDependencies(packageManager, npmOptions, packageJson);
* // you can then spread the result when using installDependencies
*
* ```ts
* const babelDependencies = await getBabelDependencies(
* packageManager,
* npmOptions,
* packageJson
* ); // you can then spread the result when using installDependencies
* installDependencies(npmOptions, [
* `@storybook/react@${storybookVersion}`,
* ...babelDependencies,
* ]);
* ```
*
* @param {Object} packageJson The current package.json so we can inspect its contents
* @returns {Array} Contains the packages and versions that need to be installed
*/
export async function getBabelDependencies(
packageManager: JsPackageManager,
@ -133,9 +141,7 @@ type CopyTemplateFilesOptions = {
destination?: string;
};
/**
* @deprecated Please use `frameworkToRenderer` from `@storybook/core-common` instead
*/
/** @deprecated Please use `frameworkToRenderer` from `@storybook/core-common` instead */
export const frameworkToRenderer = CoreFrameworkToRenderer;
export const frameworkToDefaultBuilder: Record<SupportedFrameworks, CoreBuilder> = {
@ -224,8 +230,11 @@ export async function copyTemplateFiles({
if (commonAssetsDir) {
let rendererType = frameworkToRenderer[renderer] || 'react';
// This is only used for docs links and the docs site uses `vue` for both `vue` & `vue3` renderers
if (rendererType === 'vue3') rendererType = 'vue';
if (rendererType === 'vue3') {
rendererType = 'vue';
}
await adjustTemplate(join(destinationPath, 'Configure.mdx'), { renderer: rendererType });
}
}

View File

@ -26,9 +26,7 @@ export const externalFrameworks: ExternalFramework[] = [
{ name: 'solid', frameworks: ['storybook-solidjs-vite'], renderer: 'storybook-solidjs' },
];
/**
* @deprecated Please use `SupportedFrameworks` from `@storybook/types` instead
*/
/** @deprecated Please use `SupportedFrameworks` from `@storybook/types` instead */
export type SupportedRenderers = CoreSupportedFrameworks;
export const SUPPORTED_RENDERERS: SupportedRenderers[] = [
@ -103,7 +101,7 @@ export type TemplateMatcher = {
export type TemplateConfiguration = {
preset: ProjectType;
/** will be checked both against dependencies and devDependencies */
/** Will be checked both against dependencies and devDependencies */
dependencies?: string[] | { [dependency: string]: (version: string) => boolean };
peerDependencies?: string[] | { [dependency: string]: (version: string) => boolean };
files?: string[];
@ -113,9 +111,9 @@ export type TemplateConfiguration = {
/**
* Configuration to match a storybook preset template.
*
* This has to be an array sorted in order of specificity/priority.
* Reason: both REACT and WEBPACK_REACT have react as dependency,
* therefore WEBPACK_REACT has to come first, as it's more specific.
* This has to be an array sorted in order of specificity/priority. Reason: both REACT and
* WEBPACK_REACT have react as dependency, therefore WEBPACK_REACT has to come first, as it's more
* specific.
*/
export const supportedTemplates: TemplateConfiguration[] = [
{

View File

@ -57,7 +57,9 @@ const logged = new Set();
export const once =
(type: keyof typeof logger) =>
(message: any, ...rest: any[]) => {
if (logged.has(message)) return undefined;
if (logged.has(message)) {
return undefined;
}
logged.add(message);
return logger[type](message, ...rest);
};

View File

@ -24,7 +24,7 @@ type StorybookPackage = keyof typeof storybookPackagesVersions;
* Extract package name and version from input
*
* @param pkg A string like `@storybook/cli`, `react` or `react@^16`
* @return A tuple of 2 elements: [packageName, packageVersion]
* @returns A tuple of 2 elements: [packageName, packageVersion]
*/
export function getPackageDetails(pkg: string): [string, string?] {
const idx = pkg.lastIndexOf('@');
@ -57,9 +57,7 @@ export abstract class JsPackageManager {
basePath?: string
): Promise<PackageJson | null>;
/**
* Get the INSTALLED version of a package from the package.json file
*/
/** Get the INSTALLED version of a package from the package.json file */
async getPackageVersion(packageName: string, basePath = this.cwd): Promise<string | null> {
const packageJSON = await this.getPackageJSON(packageName, basePath);
return packageJSON ? packageJSON.version ?? null : null;
@ -69,9 +67,11 @@ export abstract class JsPackageManager {
this.cwd = options?.cwd || process.cwd();
}
/** Detect whether Storybook gets initialized in a monorepository/workspace environment
* The cwd doesn't have to be the root of the monorepo, it can be a subdirectory
* @returns true, if Storybook is initialized inside a monorepository/workspace
/**
* Detect whether Storybook gets initialized in a mono-repository/workspace environment The cwd
* doesn't have to be the root of the monorepo, it can be a subdirectory
*
* @returns `true`, if Storybook is initialized inside a mono-repository/workspace
*/
public isStorybookInMonorepo() {
let cwd = process.cwd();
@ -113,9 +113,7 @@ export abstract class JsPackageManager {
return false;
}
/**
* Install dependencies listed in `package.json`
*/
/** Install dependencies listed in `package.json` */
public async installDependencies() {
logger.log('Installing dependencies...');
logger.log();
@ -172,8 +170,8 @@ export abstract class JsPackageManager {
}
/**
* Read the `package.json` file available in the directory the command was call from
* If there is no `package.json` it will create one.
* Read the `package.json` file available in the directory the command was call from If there is
* no `package.json` it will create one.
*/
public async retrievePackageJson(): Promise<PackageJsonWithDepsAndDevDeps> {
let packageJson;
@ -215,15 +213,20 @@ export abstract class JsPackageManager {
/**
* Add dependencies to a project using `yarn add` or `npm install`.
*
* @param {Object} options contains `skipInstall`, `packageJson` and `installAsDevDependencies` which we use to determine how we install packages.
* @param {Array} dependencies contains a list of packages to add.
* @example
*
* ```ts
* addDependencies(options, [
* `@storybook/react@${storybookVersion}`,
* `@storybook/addon-actions@${actionsVersion}`,
* `@storybook/addon-links@${linksVersion}`,
* `@storybook/preview-api@${addonsVersion}`,
* ]);
* ```
*
* @param {Object} options Contains `skipInstall`, `packageJson` and `installAsDevDependencies`
* which we use to determine how we install packages.
* @param {Array} dependencies Contains a list of packages to add.
*/
public async addDependencies(
options: {
@ -270,13 +273,15 @@ export abstract class JsPackageManager {
/**
* Remove dependencies from a project using `yarn remove` or `npm uninstall`.
*
* @param {Object} options contains `skipInstall`, `packageJson` and `installAsDevDependencies` which we use to determine how we install packages.
* @param {Array} dependencies contains a list of packages to remove.
* @example
* removeDependencies(options, [
* `@storybook/react`,
* `@storybook/addon-actions`,
* ]);
*
* ```ts
* removeDependencies(options, [`@storybook/react`, `@storybook/addon-actions`]);
* ```
*
* @param {Object} options Contains `skipInstall`, `packageJson` and `installAsDevDependencies`
* which we use to determine how we install packages.
* @param {Array} dependencies Contains a list of packages to remove.
*/
public async removeDependencies(
options: {
@ -315,10 +320,11 @@ export abstract class JsPackageManager {
/**
* Return an array of strings matching following format: `<package_name>@<package_latest_version>`
*
* For packages in the storybook monorepo, when the latest version is equal to the version of the current CLI
* the version is not added to the string.
* For packages in the storybook monorepo, when the latest version is equal to the version of the
* current CLI the version is not added to the string.
*
* When a package is in the monorepo, and the version is not equal to the CLI version, the version is taken from the versions.ts file and added to the string.
* When a package is in the monorepo, and the version is not equal to the CLI version, the version
* is taken from the versions.ts file and added to the string.
*
* @param packages
*/
@ -343,8 +349,8 @@ export abstract class JsPackageManager {
}
/**
* Return an array of string standing for the latest version of the input packages.
* To be able to identify which version goes with which package the order of the input array is keep.
* Return an array of string standing for the latest version of the input packages. To be able to
* identify which version goes with which package the order of the input array is keep.
*
* @param packageNames
*/
@ -357,10 +363,11 @@ export abstract class JsPackageManager {
}
/**
* Return the latest version of the input package available on npmjs registry.
* If constraint are provided it return the latest version matching the constraints.
* Return the latest version of the input package available on npmjs registry. If constraint are
* provided it return the latest version matching the constraints.
*
* For `@storybook/*` packages the latest version is retrieved from `cli/src/versions.json` file directly
* For `@storybook/*` packages the latest version is retrieved from `cli/src/versions.json` file
* directly
*
* @param packageName The name of the package
* @param constraint A valid semver constraint, example: '1.x || >=2.5.0 || 5.0.0 - 7.2.3'
@ -393,8 +400,8 @@ export abstract class JsPackageManager {
}
/**
* Get the latest version of the package available on npmjs.com.
* If constraint is set then it returns a version satisfying it, otherwise the latest version available is returned.
* Get the latest version of the package available on npmjs.com. If constraint is set then it
* returns a version satisfying it, otherwise the latest version available is returned.
*
* @param packageName Name of the package
* @param constraint Version range to use to constraint the returned version
@ -526,9 +533,7 @@ export abstract class JsPackageManager {
}
}
/**
* Returns the installed (within node_modules or pnp zip) version of a specified package
*/
/** Returns the installed (within node_modules or pnp zip) version of a specified package */
public async getInstalledVersion(packageName: string): Promise<string | null> {
const installations = await this.findInstallations([packageName]);
if (!installations) {

View File

@ -88,9 +88,7 @@ export class JsPackageManagerFactory {
throw new Error('Unable to find a usable package manager within NPM, PNPM, Yarn and Yarn 2');
}
/**
* Look up map of package manager proxies by name
*/
/** Look up map of package manager proxies by name */
private static PROXY_MAP: Record<PackageManagerName, PackageManagerProxy> = {
npm: NPMProxy,
pnpm: PNPMProxy,
@ -99,8 +97,8 @@ export class JsPackageManagerFactory {
};
/**
* Infer the package manager based on the command the user is running.
* Each package manager sets the `npm_config_user_agent` environment variable with its name and version e.g. "npm/7.24.0"
* Infer the package manager based on the command the user is running. Each package manager sets
* the `npm_config_user_agent` environment variable with its name and version e.g. "npm/7.24.0"
* Which is really useful when invoking commands via npx/pnpx/yarn create/etc.
*/
private static inferPackageManagerFromUserAgent(): PackageManagerName | undefined {

View File

@ -264,7 +264,6 @@ export class NPMProxy extends JsPackageManager {
}
/**
*
* @param input The output of `npm ls --json`
* @param pattern A list of package names to filter the result. * can be used as a placeholder
*/

View File

@ -68,17 +68,12 @@ function resolvePresetFunction<T = any>(
* Parse an addon into either a managerEntries or a preset. Throw on invalid input.
*
* Valid inputs:
* - '@storybook/addon-actions/manager'
* => { type: 'virtual', item }
*
* - '@storybook/addon-docs/preset'
* => { type: 'presets', item }
*
* - '@storybook/addon-docs'
* => { type: 'presets', item: '@storybook/addon-docs/preset' }
*
* - { name: '@storybook/addon-docs(/preset)?', options: { ... } }
* => { type: 'presets', item: { name: '@storybook/addon-docs/preset', options } }
* - `'@storybook/addon-actions/manager' => { type: 'virtual', item }`
* - `'@storybook/addon-docs/preset' => { type: 'presets', item }`
* - `'@storybook/addon-docs' => { type: 'presets', item: '@storybook/addon-docs/preset' }`
* - `{ name: '@storybook/addon-docs(/preset)?', options: { } } => { type: 'presets', item: { name:
* '@storybook/addon-docs/preset', options } }`
*/
export const resolveAddonName = (
@ -111,16 +106,19 @@ export const resolveAddonName = (
}
const checkExists = (exportName: string) => {
if (resolve(`${name}${exportName}`)) return `${name}${exportName}`;
if (resolve(`${name}${exportName}`)) {
return `${name}${exportName}`;
}
return undefined;
};
// This is used to maintain back-compat with community addons that do not
// re-export their sub-addons but reference the sub-addon name directly.
// We need to turn it into an absolute path so that webpack can
// serve it up correctly when yarn pnp or pnpm is being used.
// Vite will be broken in such cases, because it does not process absolute paths,
// and it will try to import from the bare import, breaking in pnp/pnpm.
/**
* This is used to maintain back-compat with community addons that do not re-export their
* sub-addons but reference the sub-addon name directly. We need to turn it into an absolute path
* so that webpack can serve it up correctly when yarn pnp or pnpm is being used. Vite will be
* broken in such cases, because it does not process absolute paths, and it will try to import
* from the bare import, breaking in pnp/pnpm.
*/
const absolutizeExport = (exportName: string, preferMJS: boolean) => {
const found = resolve(`${name}${exportName}`);

View File

@ -4,6 +4,8 @@ export class HandledError extends Error {
constructor(error: unknown) {
super(String(error));
if (typeof error !== 'string') this.cause = error;
if (typeof error !== 'string') {
this.cause = error;
}
}
}

View File

@ -25,7 +25,9 @@ const essentialAddons = [
const pkgName = (entry: CoreCommon_AddonEntry): string => {
if (typeof entry === 'string') {
if (entry.includes('node_modules')) return entry;
if (entry.includes('node_modules')) {
return entry;
}
return `@storybook/addon-${entry}`;
}
return (entry as CoreCommon_OptionsEntry).name;

View File

@ -26,8 +26,14 @@ const isCorrectOrder = (
const essentialsIndex = addons.findIndex(predicateFor('@storybook/addon-essentials'));
let beforeIndex = addons.findIndex(predicateFor(before.name));
let afterIndex = addons.findIndex(predicateFor(after.name));
if (beforeIndex === -1 && before.inEssentials) beforeIndex = essentialsIndex;
if (afterIndex === -1 && after.inEssentials) afterIndex = essentialsIndex;
if (beforeIndex === -1 && before.inEssentials) {
beforeIndex = essentialsIndex;
}
if (afterIndex === -1 && after.inEssentials) {
afterIndex = essentialsIndex;
}
return beforeIndex !== -1 && afterIndex !== -1 && beforeIndex <= afterIndex;
};

View File

@ -24,22 +24,22 @@ export async function temporaryDirectory({ prefix = '' } = {}) {
export type FileOptions = MergeExclusive<
{
/**
File extension.
Mutually exclusive with the `name` option.
_You usually won't need this option. Specify it only when actually needed._
*/
* File extension.
*
* Mutually exclusive with the `name` option.
*
* _You usually won't need this option. Specify it only when actually needed._
*/
readonly extension?: string;
},
{
/**
Filename.
Mutually exclusive with the `extension` option.
_You usually won't need this option. Specify it only when actually needed._
*/
* Filename.
*
* Mutually exclusive with the `extension` option.
*
* _You usually won't need this option. Specify it only when actually needed._
*/
readonly name?: string;
}
>;
@ -67,10 +67,10 @@ export function parseList(str: string): string[] {
}
/**
* Given a package manager, returns the coerced version of Storybook.
* It tries to find renderer packages in the project and returns the coerced version of the first one found.
* Example:
* If @storybook/react version 8.0.0-alpha.14 is installed, it returns the coerced version 8.0.0
* Given a package manager, returns the coerced version of Storybook. It tries to find renderer
* packages in the project and returns the coerced version of the first one found. Example: If
*
* @storybook/react version 8.0.0-alpha.14 is installed, it returns the coerced version 8.0.0
*/
export async function getCoercedStorybookVersion(packageManager: JsPackageManager) {
const packages = (
@ -96,37 +96,38 @@ export function getEnvConfig(program: Record<string, any>, configEnv: Record<str
}
/**
* Given a file name, creates an object with utilities to manage a log file.
* It creates a temporary log file which you can manage with the returned functions.
* You can then decide whether to move the log file to the users project, or remove it.
* Given a file name, creates an object with utilities to manage a log file. It creates a temporary
* log file which you can manage with the returned functions. You can then decide whether to move
* the log file to the users project, or remove it.
*
* @example
* ```
* const { logStream, moveLogFile, removeLogFile, clearLogFile, readLogFile } = await createLogStream('my-log-file.log');
*
* // SCENARIO 1:
* // you can write custom messages to generate a log file
* logStream.write('my log message');
* await moveLogFile();
* ```ts
* const { logStream, moveLogFile, removeLogFile, clearLogFile, readLogFile } =
* await createLogStream('my-log-file.log');
*
* // SCENARIO 2:
* // or you can pass it to stdio and capture the output of that command
* try {
* await this.executeCommand({
* command: 'pnpm',
* args: ['info', packageName, ...args],
* // do not output to the user, and send stdio and stderr to log file
* stdio: ['ignore', logStream, logStream]
* });
* } catch (err) {
* // do something with the log file content
* const output = await readLogFile();
* // move the log file to the users project
* await moveLogFile();
* }
* // success, no need to keep the log file
* await removeLogFile();
* // SCENARIO 1:
* // you can write custom messages to generate a log file
* logStream.write('my log message');
* await moveLogFile();
*
* // SCENARIO 2:
* // or you can pass it to stdio and capture the output of that command
* try {
* await this.executeCommand({
* command: 'pnpm',
* args: ['info', packageName, ...args],
* // do not output to the user, and send stdio and stderr to log file
* stdio: ['ignore', logStream, logStream],
* });
* } catch (err) {
* // do something with the log file content
* const output = await readLogFile();
* // move the log file to the users project
* await moveLogFile();
* }
* // success, no need to keep the log file
* await removeLogFile();
* ```
*/
export const createLogStream = async (

View File

@ -36,8 +36,9 @@ const getPrettier = async (): Promise<
};
/**
* Format the content of a file using prettier.
* If prettier is not available in the user's project, it will fallback to use editorconfig settings if available and formats the file by a prettier-fallback.
* Format the content of a file using prettier. If prettier is not available in the user's project,
* it will fallback to use editorconfig settings if available and formats the file by a
* prettier-fallback.
*/
export async function formatFileContent(filePath: string, content: string): Promise<string> {
try {

View File

@ -5,9 +5,7 @@ import { dedent } from 'ts-dedent';
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.
*/
/** Framework can be a string or an object. This utility always returns the string name. */
export async function getFrameworkName(options: Options) {
const framework = await options.presets.apply('framework', '', options);
@ -23,11 +21,15 @@ export async function getFrameworkName(options: Options) {
}
/**
* 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.
* 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'
*
* ```ts
* ExtractProperFrameworkName('/path/to/@storybook/angular'); // => '@storybook/angular'
* extractProperFrameworkName('@third-party/framework'); // => '@third-party/framework'
* ```
*/
export const extractProperFrameworkName = (framework: string) => {
const normalizedPath = normalizePath(framework);

View File

@ -5,8 +5,8 @@ import { extractProperFrameworkName, getFrameworkName } from './get-framework-na
import { frameworkPackages } from './get-storybook-info';
/**
* 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
* 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);
@ -22,12 +22,17 @@ export async function getRendererName(options: Options) {
/**
* Extracts the proper renderer name from the given framework name.
*
* @example
*
* ```ts
* extractProperRendererNameFromFramework('@storybook/react'); // => 'react'
* extractProperRendererNameFromFramework('@storybook/angular'); // => 'angular'
* extractProperRendererNameFromFramework('@third-party/framework'); // => null
* ```
*
* @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);

View File

@ -23,9 +23,7 @@ export const rendererPackages: Record<string, string> = {
'storybook-framework-qwik': 'qwik',
'storybook-solidjs': 'solid',
/**
* @deprecated This is deprecated.
*/
/** @deprecated This is deprecated. */
'@storybook/vue': 'vue',
};
@ -103,7 +101,10 @@ export const getConfigInfo = (packageJson: PackageJson, configDir?: string) => {
const storybookScript = packageJson.scripts?.storybook;
if (storybookScript && !configDir) {
const configParam = getStorybookConfiguration(storybookScript, '-c', '--config-dir');
if (configParam) storybookConfigDir = configParam;
if (configParam) {
storybookConfigDir = configParam;
}
}
return {

View File

@ -1,9 +1,10 @@
/**
* Return a string corresponding to template filled with bindings using following pattern:
* For each (key, value) of `bindings` replace, in template, `{{key}}` by escaped version of `value`
* Return a string corresponding to template filled with bindings using following pattern: For each
* (key, value) of `bindings` replace, in template, `{{key}}` by escaped version of `value`
*
* @param template {String} Template with `{{binding}}`
* @param bindings {Object} key-value object use to fill the template, `{{key}}` will be replaced by `escaped(value)`
* @param bindings {Object} key-value object use to fill the template, `{{key}}` will be replaced by
* `escaped(value)`
* @returns {String} Filled template
*/
export const interpolate = (template: string, bindings: Record<string, string>) => {

View File

@ -2,12 +2,17 @@ import { posix } from 'node:path';
/**
* Normalize a path to use forward slashes and remove .. and .
*
* @example
*
* ```ts
* normalizePath('path/to/../file'); // => 'path/file'
* normalizePath('path/to/./file'); // => 'path/to/file'
* normalizePath('path\\to\\file'); // => 'path/to/file'
* ```
*
* @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 posix.normalize(p.replace(/\\/g, '/'));

View File

@ -60,11 +60,11 @@ export const nodePathsToArray = (nodePath: string) =>
.map((p) => resolve('./', p));
const relativePattern = /^\.{1,2}([/\\]|$)/;
/**
* Ensures that a path starts with `./` or `../`, or is entirely `.` or `..`
*/
/** Ensures that a path starts with `./` or `../`, or is entirely `.` or `..` */
export function normalizeStoryPath(filename: string) {
if (relativePattern.test(filename)) return filename;
if (relativePattern.test(filename)) {
return filename;
}
return `.${sep}${filename}`;
}

View File

@ -1,7 +1,5 @@
import { posix as posixPath, sep } from 'node:path';
/**
* Replaces the path separator with forward slashes
*/
/** Replaces the path separator with forward slashes */
export const posix = (localPath: string, seperator: string = sep) =>
localPath.split(seperator).filter(Boolean).join(posixPath.sep);

View File

@ -11,8 +11,11 @@ const logger = console;
/**
* Remove the given addon package and remove it from main.js
*
* Usage:
* - sb remove @storybook/addon-links
* @example
*
* ```sh
* sb remove @storybook/addon-links
* ```
*/
export async function removeAddon(
addon: string,

View File

@ -4,11 +4,12 @@ import findCacheDirectory from 'find-cache-dir';
/**
* Get the path of the file or directory with input name inside the Storybook cache directory:
* - `node_modules/.cache/storybook/{directoryName}` in a Node.js project or npm package
* - `.cache/storybook/{directoryName}` otherwise
*
* - `node_modules/.cache/storybook/{directoryName}` in a Node.js project or npm package
* - `.cache/storybook/{directoryName}` otherwise
*
* @param fileOrDirectoryName {string} Name of the file or directory
* @return {string} Absolute path to the file or directory
* @returns {string} Absolute path to the file or directory
*/
export function resolvePathInStorybookCache(fileOrDirectoryName: string, sub = 'default'): string {
let cacheDirectory = findCacheDirectory({ name: 'storybook' });

View File

@ -1,6 +1,4 @@
/**
* Mimicking the satisfies operator until we can upgrade to TS4.9
*/
/** Mimicking the satisfies operator until we can upgrade to TS4.9 */
export function satisfies<A>() {
return <T extends A>(x: T) => x;
}

View File

@ -54,22 +54,35 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
ref
) => {
let Comp: 'button' | 'a' | typeof Slot = 'button';
if (props.isLink) Comp = 'a';
if (asChild) Comp = Slot;
if (props.isLink) {
Comp = 'a';
}
if (asChild) {
Comp = Slot;
}
let localVariant = variant;
let localSize = size;
const [isAnimating, setIsAnimating] = useState(false);
const handleClick = (event: SyntheticEvent) => {
if (onClick) onClick(event);
if (animation === 'none') return;
if (onClick) {
onClick(event);
}
if (animation === 'none') {
return;
}
setIsAnimating(true);
};
useEffect(() => {
const timer = setTimeout(() => {
if (isAnimating) setIsAnimating(false);
if (isAnimating) {
setIsAnimating(false);
}
}, 1000);
return () => clearTimeout(timer);
}, [isAnimating]);
@ -146,10 +159,21 @@ const StyledButton = styled('button', {
justifyContent: 'center',
overflow: 'hidden',
padding: (() => {
if (padding === 'small' && size === 'small') return '0 7px';
if (padding === 'small' && size === 'medium') return '0 9px';
if (size === 'small') return '0 10px';
if (size === 'medium') return '0 12px';
if (padding === 'small' && size === 'small') {
return '0 7px';
}
if (padding === 'small' && size === 'medium') {
return '0 9px';
}
if (size === 'small') {
return '0 10px';
}
if (size === 'medium') {
return '0 12px';
}
return 0;
})(),
height: size === 'small' ? '28px' : '32px',
@ -168,9 +192,17 @@ const StyledButton = styled('button', {
fontWeight: theme.typography.weight.bold,
lineHeight: '1',
background: (() => {
if (variant === 'solid') return theme.color.secondary;
if (variant === 'outline') return theme.button.background;
if (variant === 'ghost' && active) return theme.background.hoverable;
if (variant === 'solid') {
return theme.color.secondary;
}
if (variant === 'outline') {
return theme.button.background;
}
if (variant === 'ghost' && active) {
return theme.background.hoverable;
}
return 'transparent';
})(),
...(variant === 'ghost'
@ -179,11 +211,15 @@ const StyledButton = styled('button', {
// It is a temporary solution until we have implemented Theming 2.0.
'.sb-bar &': {
background: (() => {
if (active) return transparentize(0.9, theme.barTextColor);
if (active) {
return transparentize(0.9, theme.barTextColor);
}
return 'transparent';
})(),
color: (() => {
if (active) return theme.barSelectedColor;
if (active) {
return theme.barSelectedColor;
}
return theme.barTextColor;
})(),
'&:hover': {
@ -204,10 +240,21 @@ const StyledButton = styled('button', {
}
: {}),
color: (() => {
if (variant === 'solid') return theme.color.lightest;
if (variant === 'outline') return theme.input.color;
if (variant === 'ghost' && active) return theme.color.secondary;
if (variant === 'ghost') return theme.color.mediumdark;
if (variant === 'solid') {
return theme.color.lightest;
}
if (variant === 'outline') {
return theme.input.color;
}
if (variant === 'ghost' && active) {
return theme.color.secondary;
}
if (variant === 'ghost') {
return theme.color.mediumdark;
}
return theme.input.color;
})(),
boxShadow: variant === 'outline' ? `${theme.button.border} 0 0 0 1px inset` : 'none',
@ -219,10 +266,18 @@ const StyledButton = styled('button', {
color: variant === 'ghost' ? theme.color.secondary : undefined,
background: (() => {
let bgColor = theme.color.secondary;
if (variant === 'solid') bgColor = theme.color.secondary;
if (variant === 'outline') bgColor = theme.button.background;
if (variant === 'ghost') return transparentize(0.86, theme.color.secondary);
if (variant === 'solid') {
bgColor = theme.color.secondary;
}
if (variant === 'outline') {
bgColor = theme.button.background;
}
if (variant === 'ghost') {
return transparentize(0.86, theme.color.secondary);
}
return theme.base === 'light' ? darken(0.02, bgColor) : lighten(0.03, bgColor);
})(),
},
@ -231,10 +286,18 @@ const StyledButton = styled('button', {
color: variant === 'ghost' ? theme.color.secondary : undefined,
background: (() => {
let bgColor = theme.color.secondary;
if (variant === 'solid') bgColor = theme.color.secondary;
if (variant === 'outline') bgColor = theme.button.background;
if (variant === 'ghost') return theme.background.hoverable;
if (variant === 'solid') {
bgColor = theme.color.secondary;
}
if (variant === 'outline') {
bgColor = theme.button.background;
}
if (variant === 'ghost') {
return theme.background.hoverable;
}
return theme.base === 'light' ? darken(0.02, bgColor) : lighten(0.03, bgColor);
})(),
},

View File

@ -119,7 +119,10 @@ export const Loader = ({ progress, error, size, ...props }: LoaderProps) => {
if (progress) {
const { value, modules } = progress;
let { message } = progress;
if (modules) message += ` ${modules.complete} / ${modules.total} modules`;
if (modules) {
message += ` ${modules.complete} / ${modules.total} modules`;
}
return (
<ProgressWrapper
aria-label="Content is loading..."

View File

@ -146,10 +146,7 @@ const IconButtonSkeletonWrapper = styled.div(() => ({
height: 28,
}));
/**
* @deprecated
* This component will be removed in Storybook 9.0
* */
/** @deprecated This component will be removed in Storybook 9.0 */
export const IconButtonSkeleton = () => (
<IconButtonSkeletonWrapper>
<IconPlaceholder />

View File

@ -7,9 +7,9 @@ import { styled } from '@storybook/core/theming';
import TextareaAutoResize from 'react-textarea-autosize';
/**
* these types are copied from `react-textarea-autosize`.
* I copied them because of https://github.com/storybookjs/storybook/issues/18734
* Maybe there's some bug in `tsup` or `react-textarea-autosize`?
* These types are copied from `react-textarea-autosize`. I copied them because of
* https://github.com/storybookjs/storybook/issues/18734 Maybe there's some bug in `tsup` or
* `react-textarea-autosize`?
*/
type TextareaPropsRaw = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
type Style = Omit<NonNullable<TextareaPropsRaw['style']>, 'maxHeight' | 'minHeight'> & {

View File

@ -29,9 +29,9 @@ export interface IconsProps extends ComponentProps<typeof Svg> {
}
/**
* @deprecated No longer used, will be removed in Storybook 9.0
* Please use the `@storybook/icons` package instead.
* */
* @deprecated No longer used, will be removed in Storybook 9.0 Please use the `@storybook/icons`
* package instead.
*/
export const Icons = ({
icon,
useSymbol,
@ -66,9 +66,9 @@ export interface SymbolsProps {
}
/**
* @deprecated No longer used, will be removed in Storybook 9.0
* Please use the `@storybook/icons` package instead.
* */
* @deprecated No longer used, will be removed in Storybook 9.0 Please use the `@storybook/icons`
* package instead.
*/
export const Symbols = memo<SymbolsProps>(function Symbols({ icons: keys = Object.keys(icons) }) {
return (
<Svg

View File

@ -16,7 +16,9 @@ const LazySyntaxHighlighter = lazy(async () => {
languages = [];
}
if (Comp === null) Comp = SyntaxHighlighter;
if (Comp === null) {
Comp = SyntaxHighlighter;
}
return {
default: (props: ComponentProps<typeof SyntaxHighlighter>) => <SyntaxHighlighter {...props} />,

View File

@ -163,8 +163,8 @@ const processLineNumber = (row: any) => {
};
/**
* A custom renderer for handling `span.linenumber` element in each line of code,
* which is enabled by default if no renderer is passed in from the parent component
* A custom renderer for handling `span.linenumber` element in each line of code, which is enabled
* by default if no renderer is passed in from the parent component
*/
const defaultRenderer: SyntaxHighlighterRenderer = ({ rows, stylesheet, useInlineStyles }) => {
return rows.map((node: any, i: number) => {

View File

@ -42,6 +42,7 @@ export interface WithTooltipPureProps
onDoubleClick?: () => void;
/**
* If `true`, a click outside the trigger element closes the tooltip
*
* @default false
*/
closeOnOutsideClick?: boolean;
@ -157,7 +158,9 @@ const WithToolTipState = ({
const [tooltipShown, setTooltipShown] = useState(startOpen);
const onVisibilityChange = useCallback(
(visibility: boolean) => {
if (onChange && onChange(visibility) === false) return;
if (onChange && onChange(visibility) === false) {
return;
}
setTooltipShown(visibility);
},
[onChange]

View File

@ -5,9 +5,8 @@ import { withReset } from './lib/common';
/**
* This is a "local" reset to style subtrees with Storybook styles
*
* We can't style individual elements (e.g. h1, h2, etc.) in here
* because the CSS specificity is too high, so those styles can too
* easily override child elements that are not expecting it.
* We can't style individual elements (e.g. h1, h2, etc.) in here because the CSS specificity is too
* high, so those styles can too easily override child elements that are not expecting it.
*/
export const ResetWrapper = styled.div(withReset);

View File

@ -201,10 +201,10 @@ export const experimental_serverAPI = (extension: Record<string, Function>, opti
};
/**
* If for some reason this config is not applied, the reason is that
* likely there is an addon that does `export core = () => ({ someConfig })`,
* instead of `export core = (existing) => ({ ...existing, someConfig })`,
* just overwriting everything and not merging with the existing values.
* If for some reason this config is not applied, the reason is that likely there is an addon that
* does `export core = () => ({ someConfig })`, instead of `export core = (existing) => ({
* ...existing, someConfig })`, just overwriting everything and not merging with the existing
* values.
*/
export const core = async (existing: CoreConfig, options: Options): Promise<CoreConfig> => ({
...existing,
@ -283,10 +283,10 @@ export const experimental_serverChannel = async (
};
/**
* Try to resolve react and react-dom from the root node_modules of the project
* addon-docs uses this to alias react and react-dom to the project's version when possible
* If the user doesn't have an explicit dependency on react this will return the existing values
* Which will be the versions shipped with addon-docs
* Try to resolve react and react-dom from the root node_modules of the project addon-docs uses this
* to alias react and react-dom to the project's version when possible If the user doesn't have an
* explicit dependency on react this will return the existing values Which will be the versions
* shipped with addon-docs
*/
export const resolvedReact = async (existing: any) => {
try {
@ -300,9 +300,7 @@ export const resolvedReact = async (existing: any) => {
}
};
/**
* Set up `dev-only`, `docs-only`, `test-only` tags out of the box
*/
/** Set up `dev-only`, `docs-only`, `test-only` tags out of the box */
export const tags = async (existing: any) => {
return {
...existing,

View File

@ -26,9 +26,7 @@ export function initCreateNewStoryChannel(
options: Options,
coreOptions: CoreConfig
) {
/**
* Listens for events to create a new storyfile
*/
/** Listens for events to create a new storyfile */
channel.on(
CREATE_NEW_STORYFILE_REQUEST,
async (data: RequestData<CreateNewStoryRequestPayload>) => {

View File

@ -30,9 +30,7 @@ export async function initFileSearchChannel(
options: Options,
coreOptions: CoreConfig
) {
/**
* Listens 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_REQUEST,
async (data: RequestData<FileComponentSearchRequestPayload>) => {
@ -106,9 +104,7 @@ export async function initFileSearchChannel(
error: null,
} satisfies ResponseData<FileComponentSearchResponsePayload>);
} catch (e: any) {
/**
* Emits the search result event with an error message
*/
/** Emits the search result event with an error message */
channel.emit(FILE_COMPONENT_SEARCH_RESPONSE, {
success: false,
id: searchQuery ?? '',

View File

@ -29,7 +29,9 @@ export class MultipleIndexingError extends Error {
constructor(public indexingErrors: IndexingError[]) {
super();
if (this.indexingErrors.length === 0) throw new Error('Unexpected empty error list');
if (this.indexingErrors.length === 0) {
throw new Error('Unexpected empty error list');
}
if (this.indexingErrors.length === 1) {
const [err] = this.indexingErrors;

View File

@ -39,7 +39,7 @@ type StoryIndexEntryWithExtra = StoryIndexEntry & {
};
/** A .mdx file will produce a docs entry */
type DocsCacheEntry = DocsIndexEntry;
/** A *.stories.* file will produce a list of stories and possibly a docs entry */
/** A `_.stories._` file will produce a list of stories and possibly a docs entry */
type StoriesCacheEntry = {
entries: (StoryIndexEntryWithExtra | DocsIndexEntry)[];
dependents: Path[];
@ -76,25 +76,26 @@ const makeAbsolute = (otherImport: Path, normalizedPath: Path, workingDir: Path)
: otherImport;
/**
* The StoryIndexGenerator extracts stories and docs entries for each file matching
* (one or more) stories "specifiers", as defined in main.js.
* The StoryIndexGenerator extracts stories and docs entries for each file matching (one or more)
* stories "specifiers", as defined in main.js.
*
* The output is a set of entries (see above for the types).
*
* Each file is treated as a stories or a (modern) docs file.
*
* A stories file is indexed by an indexer (passed in), which produces a list of stories.
* - If the stories have the `parameters.docsOnly` setting, they are disregarded.
* - If the stories have autodocs enabled, a docs entry is added pointing to the story file.
*
* - If the stories have the `parameters.docsOnly` setting, they are disregarded.
* - If the stories have `autodocs` enabled, a docs entry is added pointing to the story file.
*
* A (modern) docs (.mdx) file is indexed, a docs entry is added.
*
* In the preview, a docs entry with the `autodocs` tag will be rendered
* as a CSF file that exports an MDX template on the `docs.page` parameter, whereas
* other docs entries are rendered as MDX files directly.
* In the preview, a docs entry with the `autodocs` tag will be rendered as a CSF file that exports
* an MDX template on the `docs.page` parameter, whereas other docs entries are rendered as MDX
* files directly.
*
* The entries are "uniq"-ed and sorted. Stories entries are preferred to docs entries and
* MDX docs entries are preferred to CSF templates (with warnings).
* The entries are "uniq"-ed and sorted. Stories entries are preferred to docs entries and MDX docs
* entries are preferred to CSF templates (with warnings).
*/
export class StoryIndexGenerator {
// An internal cache mapping specifiers to a set of path=><set of stories>
@ -172,9 +173,7 @@ export class StoryIndexGenerator {
await this.ensureExtracted({ projectTags });
}
/**
* Run the updater function over all the empty cache entries
*/
/** Run the updater function over all the empty cache entries */
async updateExtracted(
updater: (
specifier: NormalizedStoriesSpecifier,
@ -194,7 +193,9 @@ export class StoryIndexGenerator {
);
return Promise.all(
Object.keys(entry).map(async (absolutePath) => {
if (entry[absolutePath] && !overwrite) return;
if (entry[absolutePath] && !overwrite) {
return;
}
try {
entry[absolutePath] = await updater(specifier, absolutePath, entry[absolutePath]);
@ -249,12 +250,22 @@ export class StoryIndexGenerator {
)}`
);
return Object.values(cache).flatMap((entry): (IndexEntry | ErrorEntry)[] => {
if (!entry) return [];
if (entry.type === 'docs') return [entry];
if (entry.type === 'error') return [entry];
if (!entry) {
return [];
}
if (entry.type === 'docs') {
return [entry];
}
if (entry.type === 'error') {
return [entry];
}
return entry.entries.map((item) => {
if (item.type === 'docs') return item;
if (item.type === 'docs') {
return item;
}
addStats(item.extra.stats, statsSummary);
@ -272,11 +283,17 @@ export class StoryIndexGenerator {
return [...this.specifierToCache.values()].flatMap((cache: SpecifierStoriesCache) =>
Object.entries(cache)
.filter(([fileName, cacheEntry]) => {
// We are only interested in stories cache entries (and assume they've been processed already)
// If we found a match in the cache that's still null or not a stories file,
// it is a docs file and it isn't a dependency / storiesImport.
// See https://github.com/storybookjs/storybook/issues/20958
if (!cacheEntry || cacheEntry.type !== 'stories') return false;
/**
* We are only interested in stories cache entries (and assume they've been processed
* already) If we found a match in the cache that's still null or not a stories file, it
* is a docs file and it isn't a dependency / storiesImport.
*
* @see
* https://github.com/storybookjs/storybook/issues/20958
*/
if (!cacheEntry || cacheEntry.type !== 'stories') {
return false;
}
return !!absoluteImports.find((storyImport) =>
fileName.match(
@ -289,11 +306,10 @@ export class StoryIndexGenerator {
}
/**
* Try to find the component path from a raw import string and return it in
* the same format as `importPath`. Respect tsconfig paths if available.
* Try to find the component path from a raw import string and return it in the same format as
* `importPath`. Respect tsconfig paths if available.
*
* If no such file exists, assume that the import is from a package and
* return the raw
* If no such file exists, assume that the import is from a package and return the raw
*/
resolveComponentPath(
rawComponentPath: Path,
@ -422,7 +438,9 @@ export class StoryIndexGenerator {
const result = await analyze(content);
// Templates are not indexed
if (result.isTemplate) return false;
if (result.isTemplate) {
return false;
}
const absoluteImports = (result.imports as string[]).map((p) =>
makeAbsolute(p, normalizedPath, this.options.workingDir)
@ -535,11 +553,12 @@ export class StoryIndexGenerator {
const changeDocsName = 'Use `<Meta of={} name="Other Name">` to distinguish them.';
// This shouldn't be possible, but double check and use for typing
if (worseEntry.type === 'story')
if (worseEntry.type === 'story') {
throw new IndexingError(`Duplicate stories with id: ${firstEntry.id}`, [
firstEntry.importPath,
secondEntry.importPath,
]);
}
if (betterEntry.type === 'story') {
const worseDescriptor = isMdxEntry(worseEntry)
@ -569,11 +588,12 @@ export class StoryIndexGenerator {
if (
worseEntry.tags?.includes(AUTODOCS_TAG) &&
!(this.options.docs.autodocs === true || projectTags?.includes(AUTODOCS_TAG))
)
) {
throw new IndexingError(
`You created a component docs page for '${worseEntry.title}', but also tagged the CSF file with '${AUTODOCS_TAG}'. This is probably a mistake.`,
[betterEntry.importPath, worseEntry.importPath]
);
}
// Otherwise the existing entry is created by project-level autodocs which is allowed to be overridden.
} else {
@ -613,9 +633,13 @@ export class StoryIndexGenerator {
}
async getIndexAndStats(): Promise<{ storyIndex: StoryIndex; stats: IndexStatsSummary }> {
if (this.lastIndex && this.lastStats)
if (this.lastIndex && this.lastStats) {
return { storyIndex: this.lastIndex, stats: this.lastStats };
if (this.lastError) throw this.lastError;
}
if (this.lastError) {
throw this.lastError;
}
const previewCode = await this.getPreviewCode();
const projectTags = this.getProjectTags(previewCode);
@ -627,8 +651,9 @@ export class StoryIndexGenerator {
try {
const errorEntries = storiesList.filter((entry) => entry.type === 'error');
if (errorEntries.length)
if (errorEntries.length) {
throw new MultipleIndexingError(errorEntries.map((entry) => (entry as ErrorEntry).err));
}
const duplicateErrors: IndexingError[] = [];
const indexEntries: StoryIndex['entries'] = {};
@ -641,10 +666,15 @@ export class StoryIndexGenerator {
indexEntries[entry.id] = entry;
}
} catch (err) {
if (err instanceof IndexingError) duplicateErrors.push(err);
if (err instanceof IndexingError) {
duplicateErrors.push(err);
}
}
});
if (duplicateErrors.length) throw new MultipleIndexingError(duplicateErrors);
if (duplicateErrors.length) {
throw new MultipleIndexingError(duplicateErrors);
}
const sorted = await this.sortStories(
indexEntries,

View File

@ -5,12 +5,12 @@ import type { Path } from '@storybook/core/types';
/**
* Calculate a name to use for a docs entry if not specified. The rule is:
*
* 1. If the name of the MDX file is the "same" as the CSF file
* (e.g. Button.mdx, Button.stories.jsx) use the default name.
* 1. If the name of the MDX file is the "same" as the CSF file (e.g. Button.mdx, Button.stories.jsx)
* use the default name.
* 2. Else use the (ext-less) name of the MDX file
*
* @param mdxImportPath importPath of the MDX file with of={}
* @param csfImportPath importPath of the of CSF file
* @param mdxImportPath ImportPath of the MDX file with of={}
* @param csfImportPath ImportPath of the of CSF file
*/
export function autoName(mdxImportPath: Path, csfImportPath: Path, defaultName: string) {
const mdxBasename = basename(mdxImportPath);
@ -19,7 +19,9 @@ export function autoName(mdxImportPath: Path, csfImportPath: Path, defaultName:
const [mdxFilename] = mdxBasename.split('.');
const [csfFilename] = csfBasename.split('.');
if (mdxFilename === csfFilename) return defaultName;
if (mdxFilename === csfFilename) {
return defaultName;
}
return mdxFilename;
}

View File

@ -32,7 +32,9 @@ export async function copyAllStaticFiles(staticDirs: any[] | undefined, outputDi
filter: (_, dest) => !skipPaths.includes(dest),
});
} catch (e) {
if (e instanceof Error) logger.error(e.message);
if (e instanceof Error) {
logger.error(e.message);
}
process.exit(-1);
}
})

View File

@ -23,7 +23,9 @@ export async function doTelemetry(
} catch (err) {
// If we fail to get the index, treat it as a recoverable error, but send it up to telemetry
// as if we crashed. In the future we will revisit this to send a distinct error
if (!(err instanceof Error)) throw new Error('encountered a non-recoverable error');
if (!(err instanceof Error)) {
throw new Error('encountered a non-recoverable error');
}
sendTelemetryError(err, 'dev', {
cliOptions: options,
presetOptions: { ...options, corePresets: [], overridePresets: [] },

Some files were not shown because too many files have changed in this diff Show More