diff --git a/package.json b/package.json index 6f7d9b635872..0f54bee167db 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "react-dom": "^19.2.5", "rimraf": "^3.0.2", "sharp": "^0.34.5", - "strip-ansi": "^6.0.1", + "strip-ansi": "^7.2.0", "stylelint": "^17.12.0", "stylelint-config-standard": "^40.0.0", "syncpack": "^14.3.1", diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 644511d7d881..d1d80a5289a3 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -1324,9 +1324,6 @@ declare module '@theme/NavbarItem/DefaultNavbarItem' { readonly position?: 'left' | 'right'; }; - // TODO Docusaurus v4, remove old type name - export type DesktopOrMobileNavBarItemProps = DefaultNavbarItemProps; - export interface Props extends DefaultNavbarItemProps { readonly mobile?: boolean; } @@ -1380,9 +1377,6 @@ declare module '@theme/NavbarItem/DropdownNavbarItem' { readonly className?: string; }; - // TODO Docusaurus v4, remove old type name - export type DesktopOrMobileNavBarItemProps = DropdownNavbarItemProps; - export interface Props extends DropdownNavbarItemProps { readonly mobile?: boolean; } diff --git a/packages/docusaurus-theme-classic/src/theme/CodeBlock/Content/index.tsx b/packages/docusaurus-theme-classic/src/theme/CodeBlock/Content/index.tsx index ffc45fc87b12..7d73a11f812d 100644 --- a/packages/docusaurus-theme-classic/src/theme/CodeBlock/Content/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/CodeBlock/Content/index.tsx @@ -15,20 +15,17 @@ import Line from '@theme/CodeBlock/Line'; import styles from './styles.module.css'; -// TODO Docusaurus v4: remove useless forwardRef -const Pre = React.forwardRef>( - function Pre(props, ref) { - return ( -
-    );
-  },
-);
+function Pre({className, ref, ...props}: ComponentProps<'pre'>) {
+  return (
+    
+  );
+}
 
 function Code(props: ComponentProps<'code'>) {
   const {metadata} = useCodeBlockContext();
diff --git a/packages/docusaurus-theme-classic/src/theme/ContentVisibility/Unlisted/index.tsx b/packages/docusaurus-theme-classic/src/theme/ContentVisibility/Unlisted/index.tsx
index 2a116bff5289..14d0e81fbd72 100644
--- a/packages/docusaurus-theme-classic/src/theme/ContentVisibility/Unlisted/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/ContentVisibility/Unlisted/index.tsx
@@ -7,15 +7,23 @@
 
 import React, {type ReactNode} from 'react';
 import clsx from 'clsx';
+import Head from '@docusaurus/Head';
 import {
   ThemeClassNames,
   UnlistedBannerTitle,
   UnlistedBannerMessage,
-  UnlistedMetadata,
 } from '@docusaurus/theme-common';
 import Admonition from '@theme/Admonition';
 import type {Props} from '@theme/ContentVisibility/Unlisted';
 
+function UnlistedMetadata(): ReactNode {
+  return (
+    
+      
+    
+  );
+}
+
 function UnlistedBanner({className}: Props) {
   return (
     
+      inert={inert}>
       {children}
     
   );
diff --git a/packages/docusaurus-theme-common/package.json b/packages/docusaurus-theme-common/package.json
index 01b3028a4911..cde98294dc37 100644
--- a/packages/docusaurus-theme-common/package.json
+++ b/packages/docusaurus-theme-common/package.json
@@ -51,7 +51,6 @@
     "lodash": "^4.17.21"
   },
   "peerDependencies": {
-    "@docusaurus/plugin-content-docs": "*",
     "react": "^19.2.0",
     "react-dom": "^19.2.0"
   },
diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts
index 66083cee973c..1d239ec048c6 100644
--- a/packages/docusaurus-theme-common/src/index.ts
+++ b/packages/docusaurus-theme-common/src/index.ts
@@ -5,40 +5,6 @@
  * LICENSE file in the root directory of this source tree.
  */
 
-import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
-import {DEFAULT_SEARCH_TAG} from './utils/searchUtils';
-
-// TODO Docusaurus v4: remove these workarounds as a breaking change
-//  and remove docs plugin peerDeps in theme-common/package.json
-//  This is public API surface that we need to keep for v3
-//  See https://github.com/facebook/docusaurus/pull/10316
-export function useCurrentSidebarCategory(...args: unknown[]): unknown {
-  // eslint-disable-next-line @typescript-eslint/no-require-imports
-  return require('@docusaurus/plugin-content-docs/client').useCurrentSidebarCategory(
-    ...args,
-  );
-}
-export function filterDocCardListItems(...args: unknown[]): unknown {
-  // eslint-disable-next-line @typescript-eslint/no-require-imports
-  return require('@docusaurus/plugin-content-docs/client').filterDocCardListItems(
-    ...args,
-  );
-}
-export function useDocsPreferredVersion(...args: unknown[]): unknown {
-  // eslint-disable-next-line @typescript-eslint/no-require-imports
-  return require('@docusaurus/plugin-content-docs/client').useDocsPreferredVersion(
-    ...args,
-  );
-}
-export function useContextualSearchFilters() {
-  const {i18n} = useDocusaurusContext();
-  const docsTags =
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    require('@docusaurus/plugin-content-docs/client').useDocsContextualSearchTags();
-  const tags = [DEFAULT_SEARCH_TAG, ...docsTags];
-  return {locale: i18n.currentLocale, tags};
-}
-
 /*
  * APIs to document
  */
@@ -138,7 +104,6 @@ export {
 export {
   UnlistedBannerTitle,
   UnlistedBannerMessage,
-  UnlistedMetadata,
   DraftBannerTitle,
   DraftBannerMessage,
 } from './translations/contentVisibilityTranslations';
diff --git a/packages/docusaurus-theme-common/src/internal.ts b/packages/docusaurus-theme-common/src/internal.ts
index 02c5776173d1..a12f43e8eb4e 100644
--- a/packages/docusaurus-theme-common/src/internal.ts
+++ b/packages/docusaurus-theme-common/src/internal.ts
@@ -44,14 +44,6 @@ export {
   getPrismCssVariables,
   CodeBlockContextProvider,
   useCodeBlockContext,
-
-  // TODO Docusaurus v4: remove, only kept for internal retro-compatibility
-  //  See https://github.com/facebook/docusaurus/pull/11153
-  parseCodeBlockTitle,
-  parseClassNameLanguage as parseLanguage,
-  parseLines,
-  getLineNumbersStart,
-  containsLineNumbers,
 } from './utils/codeBlockUtils';
 
 export {DEFAULT_SEARCH_TAG} from './utils/searchUtils';
diff --git a/packages/docusaurus-theme-common/src/translations/contentVisibilityTranslations.tsx b/packages/docusaurus-theme-common/src/translations/contentVisibilityTranslations.tsx
index a08bbd062243..703473640b12 100644
--- a/packages/docusaurus-theme-common/src/translations/contentVisibilityTranslations.tsx
+++ b/packages/docusaurus-theme-common/src/translations/contentVisibilityTranslations.tsx
@@ -7,7 +7,6 @@
 
 import React, {type ReactNode} from 'react';
 import Translate from '@docusaurus/Translate';
-import Head from '@docusaurus/Head';
 
 export function UnlistedBannerTitle(): ReactNode {
   return (
@@ -30,16 +29,6 @@ export function UnlistedBannerMessage(): ReactNode {
   );
 }
 
-// TODO Docusaurus v4 breaking change (since it's v3 public theme-common API :/)
-//  Move this to theme/ContentVisibility/Unlisted
-export function UnlistedMetadata(): ReactNode {
-  return (
-    
-      
-    
-  );
-}
-
 export function DraftBannerTitle(): ReactNode {
   return (
      {
       rest: 'Hello World 😀',
     });
   });
+
+  it('does not extract single digit', () => {
+    expect(extractLeadingEmoji('1 Hello World')).toEqual({
+      emoji: null,
+      rest: '1 Hello World',
+    });
+  });
+
+  it('does not extract multiple digits', () => {
+    expect(extractLeadingEmoji('11 Hello World')).toEqual({
+      emoji: null,
+      rest: '11 Hello World',
+    });
+  });
+
+  it('extracts real keycap emoji (digit + VS16 + combining keycap)', () => {
+    // 1️⃣ is digit "1" followed by U+FE0F (Variation Selector-16) and
+    // U+20E3 (Combining Enclosing Keycap) — a multi-codepoint grapheme
+    // that should still be detected as emoji
+    expect(extractLeadingEmoji('1\u{FE0F}\u{20E3} Something')).toEqual({
+      emoji: '1️⃣',
+      rest: ' Something',
+    });
+  });
+
+  it('extracts VS16 emoji - ⚠️ (text-default + variation selector)', () => {
+    // ⚠ (U+26A0) has Emoji=true, Emoji_Presentation=false.  With VS16
+    // (U+FE0F) it becomes a multi-codepoint grapheme that IS an emoji
+    expect(extractLeadingEmoji('⚠\u{FE0F} Warning')).toEqual({
+      emoji: '⚠\u{FE0F}',
+      rest: ' Warning',
+    });
+  });
+
+  it('does not detect text-presentation symbols - ©', () => {
+    expect(extractLeadingEmoji('© 2026')).toEqual({
+      emoji: null,
+      rest: '© 2026',
+    });
+  });
+
+  it('does detect ©️', () => {
+    expect(extractLeadingEmoji('©️ 2026')).toEqual({
+      emoji: '©️',
+      rest: ' 2026',
+    });
+  });
+
+  it('does not detect text-presentation symbols - ™', () => {
+    expect(extractLeadingEmoji('™ Brand')).toEqual({
+      emoji: null,
+      rest: '™ Brand',
+    });
+  });
+
+  it('does not detect text-presentation symbols - ♠', () => {
+    expect(extractLeadingEmoji('♠ Cards')).toEqual({
+      emoji: null,
+      rest: '♠ Cards',
+    });
+  });
 });
diff --git a/packages/docusaurus-theme-common/src/utils/codeBlockUtils.tsx b/packages/docusaurus-theme-common/src/utils/codeBlockUtils.tsx
index fc18e4d44b57..914fc5de3557 100644
--- a/packages/docusaurus-theme-common/src/utils/codeBlockUtils.tsx
+++ b/packages/docusaurus-theme-common/src/utils/codeBlockUtils.tsx
@@ -188,12 +188,6 @@ export function getLineNumbersStart({
   return getMetaLineNumbersStart(metastring);
 }
 
-// TODO Docusaurus v4: remove, only kept for internal retro-compatibility
-//  See https://github.com/facebook/docusaurus/pull/11153
-export function containsLineNumbers(metastring?: string): boolean {
-  return Boolean(metastring?.includes('showLineNumbers'));
-}
-
 type ParseCodeLinesParam = {
   /**
    * The full metastring, as received from MDX. Line ranges declared here
diff --git a/packages/docusaurus-theme-common/src/utils/emojiUtils.ts b/packages/docusaurus-theme-common/src/utils/emojiUtils.ts
index c66cbfb3fa01..cfa1cbde81c0 100644
--- a/packages/docusaurus-theme-common/src/utils/emojiUtils.ts
+++ b/packages/docusaurus-theme-common/src/utils/emojiUtils.ts
@@ -29,11 +29,10 @@ export function extractLeadingEmoji(input: string): {
     return {emoji: null, rest: input};
   }
 
-  // Leading grapheme contains an emoji (covers flags/ZWJ/skin tones)
-  if (
-    !/\p{Extended_Pictographic}/u.test(grapheme) &&
-    !/\p{Emoji}/u.test(grapheme)
-  ) {
+  // Leading grapheme contains an emoji
+  // Covers flags/ZWJ/skin tones, excludes digits
+  // See https://github.com/facebook/docusaurus/pull/12072
+  if (!/^\p{RGI_Emoji}$/v.test(grapheme)) {
     return {emoji: null, rest: input};
   }
 
diff --git a/packages/docusaurus-theme-mermaid/src/client/index.ts b/packages/docusaurus-theme-mermaid/src/client/index.ts
index ebb57f4ace43..8113e8e8fd1f 100644
--- a/packages/docusaurus-theme-mermaid/src/client/index.ts
+++ b/packages/docusaurus-theme-mermaid/src/client/index.ts
@@ -5,7 +5,7 @@
  * LICENSE file in the root directory of this source tree.
  */
 
-import {useState, useEffect, useMemo} from 'react';
+import {useState, useEffect, useMemo, useId} from 'react';
 import {useColorMode, useThemeConfig} from '@docusaurus/theme-common';
 import {loadMermaid} from './loadMermaid';
 
@@ -32,19 +32,9 @@ export function useMermaidConfig(): MermaidConfig {
   );
 }
 
+// Random client-only id, we don't care much but mermaid want an id so...
 function useMermaidId(): string {
-  /*
-  Random client-only id, we don't care much but mermaid want an id so...
-  Note: Mermaid doesn't like values provided by Rect.useId() and throws
-  */
-
-  // TODO Docusaurus v4: check if useId() now works
-  //  It could work thanks to https://github.com/facebook/react/pull/32001
-  // return useId(); // tried that, doesn't work ('#d:re:' is not a valid selector.)
-
-  return useState(
-    () => `mermaid-svg-${Math.round(Math.random() * 10000000)}`,
-  )[0];
+  return useId();
 }
 
 async function renderMermaid({
diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts
index 815b4bbc9070..ff097e3e6265 100644
--- a/packages/docusaurus-utils/src/index.ts
+++ b/packages/docusaurus-utils/src/index.ts
@@ -24,13 +24,6 @@ export {
   WEBPACK_URL_LOADER_LIMIT,
 } from './constants';
 export {generate, readOutputHTMLFile} from './emitUtils';
-export {
-  // TODO Docusaurus v4: remove these legacy exports,
-  //  they are only kept for retro-compatibility
-  getFileCommitDate,
-  FileNotTrackedError,
-  GitNotFoundError,
-} from './vcs/gitUtils';
 export {
   mergeTranslations,
   updateTranslationFileMessages,
diff --git a/packages/docusaurus/src/client/exports/Link.tsx b/packages/docusaurus/src/client/exports/Link.tsx
index e294ecff7b70..1fab775967ee 100644
--- a/packages/docusaurus/src/client/exports/Link.tsx
+++ b/packages/docusaurus/src/client/exports/Link.tsx
@@ -28,19 +28,16 @@ import type {Props} from '@docusaurus/Link';
 // like "introduction" to "/baseUrl/introduction" => bad behavior to fix
 const shouldAddBaseUrlAutomatically = (to: string) => to.startsWith('/');
 
-function Link(
-  {
-    isNavLink,
-    to,
-    href,
-    activeClassName,
-    isActive,
-    'data-noBrokenLinkCheck': noBrokenLinkCheck,
-    autoAddBaseUrl = true,
-    ...props
-  }: Props,
-  forwardedRef: React.ForwardedRef,
-): ReactNode {
+function Link({
+  isNavLink,
+  to,
+  href,
+  activeClassName,
+  isActive,
+  'data-noBrokenLinkCheck': noBrokenLinkCheck,
+  autoAddBaseUrl = true,
+  ...props
+}: Props): ReactNode {
   const {siteConfig} = useDocusaurusContext();
   const {trailingSlash, baseUrl} = siteConfig;
   const router = siteConfig.future.experimental_router;
@@ -48,7 +45,7 @@ function Link(
   const brokenLinks = useBrokenLinks();
   const innerRef = useRef(null);
 
-  useImperativeHandle(forwardedRef, () => innerRef.current!);
+  useImperativeHandle(props.ref, () => innerRef.current!);
 
   // IMPORTANT: using to or href should not change anything
   // For example, MDX links will ALWAYS give us the href props
@@ -207,4 +204,4 @@ function Link(
   );
 }
 
-export default React.forwardRef(Link);
+export default Link;
diff --git a/packages/docusaurus/src/commands/utils/openBrowser/openBrowser.ts b/packages/docusaurus/src/commands/utils/openBrowser/openBrowser.ts
index 0660005a739c..e59e9d043227 100644
--- a/packages/docusaurus/src/commands/utils/openBrowser/openBrowser.ts
+++ b/packages/docusaurus/src/commands/utils/openBrowser/openBrowser.ts
@@ -72,16 +72,11 @@ async function tryOpenWithAppleScript({
         '|',
       )})$"`;
 
-      const result = await Promise
-        // TODO Docusaurus v4: use Promise.try()
-        // See why here https://github.com/facebook/docusaurus/issues/11204#issuecomment-3073480330
-        .resolve()
-        .then(() => execPromise(command))
-        .catch(() => {
-          // Ignore all errors
-          // In particular grep errors when macOS user has no Chromium-based browser open
-          // See https://github.com/facebook/docusaurus/issues/11204
-        });
+      const result = await Promise.try(() => execPromise(command)).catch(() => {
+        // Ignore all errors
+        // In particular grep errors when macOS user has no Chromium-based browser open
+        // See https://github.com/facebook/docusaurus/issues/11204
+      });
       if (!result) {
         return [];
       }
diff --git a/packages/docusaurus/src/server/i18n.ts b/packages/docusaurus/src/server/i18n.ts
index 81cd3728e8aa..959600b69448 100644
--- a/packages/docusaurus/src/server/i18n.ts
+++ b/packages/docusaurus/src/server/i18n.ts
@@ -79,11 +79,7 @@ function getDefaultDirection(localeStr: string) {
   const locale = new Intl.Locale(localeStr);
   // see https://github.com/tc39/proposal-intl-locale-info
   // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getTextInfo
-  // Node 18.0 implements a former version of the getTextInfo() proposal
-  // TODO Docusaurus v4: remove the fallback to locale.textInfo
-  // @ts-expect-error: The TC39 proposal was updated
-  const textInto = locale.getTextInfo?.() ?? locale.textInfo;
-  return textInto.direction ?? 'ltr';
+  return locale.getTextInfo().direction ?? 'ltr';
 }
 
 export function getDefaultLocaleConfig(
diff --git a/packages/docusaurus/src/ssg/ssgEnv.ts b/packages/docusaurus/src/ssg/ssgEnv.ts
index 8927bb7c71dd..a3fc88ca5465 100644
--- a/packages/docusaurus/src/ssg/ssgEnv.ts
+++ b/packages/docusaurus/src/ssg/ssgEnv.ts
@@ -7,9 +7,8 @@
 
 // Secret way to set SSR plugin async concurrency option
 // Waiting for feedback before documenting this officially?
-// TODO Docusaurus v4, rename SSR => SSG
-export const SSGConcurrency = process.env.DOCUSAURUS_SSR_CONCURRENCY
-  ? parseInt(process.env.DOCUSAURUS_SSR_CONCURRENCY, 10)
+export const SSGConcurrency = process.env.DOCUSAURUS_SSG_CONCURRENCY
+  ? parseInt(process.env.DOCUSAURUS_SSG_CONCURRENCY, 10)
   : // Not easy to define a reasonable option default
     // Will still be better than Infinity
     // See also https://github.com/sindresorhus/p-map/issues/24
diff --git a/packages/docusaurus/src/ssg/ssgGlobalResult.ts b/packages/docusaurus/src/ssg/ssgGlobalResult.ts
index d23c092b98cb..cdf671951f31 100644
--- a/packages/docusaurus/src/ssg/ssgGlobalResult.ts
+++ b/packages/docusaurus/src/ssg/ssgGlobalResult.ts
@@ -25,22 +25,11 @@ function printSSGWarnings(results: SSGSuccess[]): void {
     return;
   }
 
-  const ignoredWarnings: string[] = [
-    // TODO Docusaurus v4: remove with React 19 upgrade
-    //  React 18 emit NULL chars, and minifier detects it
-    //  see https://github.com/facebook/docusaurus/issues/9985
-    'Unexpected null character',
-  ];
-
-  const keepWarning = (warning: string) => {
-    return !ignoredWarnings.some((iw) => warning.includes(iw));
-  };
-
   const resultsWithWarnings = results
     .map((success) => {
       return {
         ...success,
-        warnings: success.result.warnings.filter(keepWarning),
+        warnings: success.result.warnings,
       };
     })
     .filter((result) => result.warnings.length > 0);
diff --git a/project-words.txt b/project-words.txt
index 600eddac630a..06190d4312f4 100644
--- a/project-words.txt
+++ b/project-words.txt
@@ -133,6 +133,8 @@ Kaszubowski
 Katex
 katex
 Kato
+Keycap
+keycap
 Keytar
 keytar
 Kinsta
diff --git a/website/_dogfooding/_docs tests/tests/category-links/with-category-name-doc/with-category-name-doc.mdx b/website/_dogfooding/_docs tests/tests/category-links/with-category-name-doc/with-category-name-doc.mdx
index 67c89fc762c0..1febccc90d39 100644
--- a/website/_dogfooding/_docs tests/tests/category-links/with-category-name-doc/with-category-name-doc.mdx	
+++ b/website/_dogfooding/_docs tests/tests/category-links/with-category-name-doc/with-category-name-doc.mdx	
@@ -16,7 +16,7 @@ It is also possible to render the card items for that category thanks to MDX:
 
 ```mdx-code-block
 import DocCardList from '@theme/DocCardList';
-import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
+import {useCurrentSidebarCategory} from '@docusaurus/plugin-content-docs/client';
 
 
 ```
diff --git a/website/_dogfooding/_docs tests/tests/sidebar-frontmatter/index.mdx b/website/_dogfooding/_docs tests/tests/sidebar-frontmatter/index.mdx
index 3116679e175a..8dbf3762d5f6 100644
--- a/website/_dogfooding/_docs tests/tests/sidebar-frontmatter/index.mdx	
+++ b/website/_dogfooding/_docs tests/tests/sidebar-frontmatter/index.mdx	
@@ -7,7 +7,7 @@ sidebar_class_name: 'dogfood_sidebar_class_name_test'
 
 ```mdx-code-block
 import Link from '@docusaurus/Link';
-import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
+import {useCurrentSidebarCategory} from '@docusaurus/plugin-content-docs/client';
 import DocCardList from '@theme/DocCardList';
 
 
diff --git a/website/src/components/APITable/index.tsx b/website/src/components/APITable/index.tsx
index 29ad3e57881f..3c780740eeb3 100644
--- a/website/src/components/APITable/index.tsx
+++ b/website/src/components/APITable/index.tsx
@@ -42,13 +42,15 @@ function getRowName(node: ReactElement): string {
   return curNode as string;
 }
 
-function APITableRow(
-  {
-    name,
-    children,
-  }: {name: string | undefined; children: ReactElement>},
-  ref: React.ForwardedRef,
-) {
+function APITableRow({
+  name,
+  children,
+  ref,
+}: {
+  name: string | undefined;
+  children: ReactElement>;
+  ref: React.Ref;
+}) {
   const entryName = getRowName(children);
   const id = name ? `${name}-${entryName}` : entryName;
   const anchor = `#${id}`;
@@ -79,8 +81,6 @@ function APITableRow(
   );
 }
 
-const APITableRowComp = React.forwardRef(APITableRow);
-
 /*
  * Note: this is not a quite robust component since it makes a lot of
  * assumptions about how the children looks; however, those assumptions
@@ -104,9 +104,9 @@ export default function APITable({children, name}: Props): ReactNode {
     // @ts-expect-error: TODO fix typing
     tbody.props.children,
     (row: ReactElement>) => (
-      
+      
         {row}
-      
+      
     ),
   );
 
diff --git a/yarn.lock b/yarn.lock
index 01a0bc2f3fa4..9e7a63025a30 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -17350,7 +17350,7 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1:
   dependencies:
     ansi-regex "^5.0.1"
 
-strip-ansi@^7.0.1, strip-ansi@^7.1.0, strip-ansi@^7.1.2:
+strip-ansi@^7.0.1, strip-ansi@^7.1.0, strip-ansi@^7.1.2, strip-ansi@^7.2.0:
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.2.0.tgz#d22a269522836a627af8d04b5c3fd2c7fa3e32e3"
   integrity sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==