فهرست منبع

Merge branch 'master' into imprv/ssr-performance

Yuki Takei 1 سال پیش
والد
کامیت
34920f9435

+ 0 - 5
.changeset/tasty-baboons-burn.md

@@ -1,5 +0,0 @@
----
-'@growi/pluginkit': patch
----
-
-Update tsconfig.json module setting

+ 1 - 1
.github/workflows/reusable-app-prod.yml

@@ -213,7 +213,7 @@ jobs:
       fail-fast: false
       matrix:
         # List string expressions that is comma separated ids of tests in "test/cypress/integration"
-        spec-group: ['20', '21', '22', '23', '30', '50']
+        spec-group: ['20', '21', '23', '30', '50']
 
     services:
       mongodb:

+ 1 - 1
apps/app/package.json

@@ -206,7 +206,7 @@
     "url-join": "^4.0.0",
     "usehooks-ts": "^2.6.0",
     "validator": "^13.7.0",
-    "ws": "^8.3.0",
+    "ws": "^8.17.1",
     "xss": "^1.0.14",
     "y-mongodb-provider": "^0.1.10",
     "y-socket.io": "^1.1.3",

+ 36 - 35
apps/app/playwright/20-basic-features/click-page-icons.spec.ts

@@ -1,49 +1,50 @@
 import { test, expect } from '@playwright/test';
 
-test('Successfully Subscribe/Unsubscribe a page', async({ page }) => {
-  await page.goto('/Sandbox');
-  const subscribeButton = page.locator('.btn-subscribe');
+test.describe('Click page icons', () => {
+  test.beforeEach(async({ page }) => {
+    await page.goto('/Sandbox');
+  });
 
-  // Subscribe
-  await subscribeButton.click();
-  await expect(subscribeButton).toHaveClass(/active/);
+  test('Successfully Subscribe/Unsubscribe a page', async({ page }) => {
+    const subscribeButton = page.locator('.btn-subscribe');
 
-  // Unsubscribe
-  await subscribeButton.click();
-  await expect(subscribeButton).not.toHaveClass(/active/);
-});
+    // Subscribe
+    await subscribeButton.click();
+    await expect(subscribeButton).toHaveClass(/active/);
 
-test('Successfully Like/Unlike a page', async({ page }) => {
-  await page.goto('/Sandbox');
-  const likeButton = page.locator('.btn-like').first();
+    // Unsubscribe
+    await subscribeButton.click();
+    await expect(subscribeButton).not.toHaveClass(/active/);
+  });
 
-  // Like
-  await likeButton.click();
-  await expect(likeButton).toHaveClass(/active/);
+  test('Successfully Like/Unlike a page', async({ page }) => {
+    const likeButton = page.locator('.btn-like').first();
 
-  // Unlike
-  await likeButton.click();
-  await expect(likeButton).not.toHaveClass(/active/);
-});
+    // Like
+    await likeButton.click();
+    await expect(likeButton).toHaveClass(/active/);
 
-test('Successfully Bookmark / Unbookmark a page', async({ page }) => {
-  await page.goto('/Sandbox');
-  const bookmarkButton = page.locator('.btn-bookmark').first();
+    // Unlike
+    await likeButton.click();
+    await expect(likeButton).not.toHaveClass(/active/);
+  });
 
-  // Bookmark
-  await bookmarkButton.click();
-  await expect(bookmarkButton).toHaveClass(/active/);
+  test('Successfully Bookmark / Unbookmark a page', async({ page }) => {
+    const bookmarkButton = page.locator('.btn-bookmark').first();
 
-  // Unbookmark
-  await page.locator('.grw-bookmark-folder-menu-item').click();
-  await expect(bookmarkButton).not.toHaveClass(/active/);
-});
+    // Bookmark
+    await bookmarkButton.click();
+    await expect(bookmarkButton).toHaveClass(/active/);
 
-test('Successfully display list of "seen by user"', async({ page }) => {
-  await page.goto('/Sandbox');
+    // Unbookmark
+    await page.locator('.grw-bookmark-folder-menu-item').click();
+    await expect(bookmarkButton).not.toHaveClass(/active/);
+  });
 
-  await page.locator('.btn-seen-user').click();
+  test('Successfully display list of "seen by user"', async({ page }) => {
+    await page.locator('.btn-seen-user').click();
 
-  const imgCount = await page.locator('.user-list-content').locator('img').count();
-  expect(imgCount).toBe(1);
+    const imgCount = await page.locator('.user-list-content').locator('img').count();
+    expect(imgCount).toBe(1);
+  });
 });

+ 47 - 0
apps/app/playwright/20-basic-features/sticky-features.spec.ts

@@ -0,0 +1,47 @@
+import { test, expect } from '@playwright/test';
+
+test.describe('Sticky features', () => {
+  test.beforeEach(async({ page }) => {
+    await page.goto('/');
+  });
+
+  test('Subnavigation displays changes on scroll down and up', async({ page }) => {
+    // Scroll down to trigger sticky effect
+    await page.evaluate(() => window.scrollTo(0, 250));
+    await expect(page.locator('.sticky-outer-wrapper').first()).toHaveClass(/active/);
+
+    // Scroll back to top
+    await page.evaluate(() => window.scrollTo(0, 0));
+    await expect(page.locator('.sticky-outer-wrapper').first()).not.toHaveClass(/active/);
+  });
+
+  test('Subnavigation is not displayed when move to other pages', async({ page }) => {
+    // Scroll down to trigger sticky effect
+    await page.evaluate(() => window.scrollTo(0, 250));
+    await expect(page.locator('.sticky-outer-wrapper').first()).toHaveClass(/active/);
+
+    // Move to /Sandbox page
+    await page.goto('/Sandbox');
+    await expect(page.locator('.sticky-outer-wrapper').first()).not.toHaveClass(/active/);
+  });
+
+  test('Able to click buttons on subnavigation switcher when sticky', async({ page }) => {
+    // Scroll down to trigger sticky effect
+    await page.evaluate(() => window.scrollTo(0, 250));
+    await expect(page.locator('.sticky-outer-wrapper').first()).toHaveClass(/active/);
+
+    // Click editor button
+    await page.getByTestId('editor-button').click();
+    await expect(page.locator('.layout-root')).toHaveClass(/editing/);
+  });
+
+  test('Subnavigation is sticky when on small window', async({ page }) => {
+    // Scroll down to trigger sticky effect
+    await page.evaluate(() => window.scrollTo(0, 500));
+    await expect(page.locator('.sticky-outer-wrapper').first()).toHaveClass(/active/);
+
+    // Set viewport to small size
+    await page.setViewportSize({ width: 600, height: 1024 });
+    await expect(page.getByTestId('grw-contextual-sub-nav').getByTestId('grw-page-editor-mode-manager')).toBeVisible();
+  });
+});

+ 14 - 0
apps/app/playwright/21-basic-features-for-guest/sticky-for-guest.spec.ts

@@ -0,0 +1,14 @@
+import { test, expect } from '@playwright/test';
+
+
+test('Sub navigation sticky changes when scrolling down and up', async({ page }) => {
+  await page.goto('/Sandbox');
+
+  // Sticky
+  await page.evaluate(() => window.scrollTo(0, 250));
+  await expect(page.locator('.sticky-outer-wrapper').first()).toHaveClass(/active/);
+
+  // Not sticky
+  await page.evaluate(() => window.scrollTo(0, 0));
+  await expect(page.locator('.sticky-outer-wrapper').first()).not.toHaveClass(/active/);
+});

+ 37 - 0
apps/app/playwright/22-sharelink/access-to-sharelink.spec.ts

@@ -0,0 +1,37 @@
+import { test, expect } from '@playwright/test';
+
+import { login } from '../utils/Login';
+
+test.describe.serial('Access to sharelink by guest', () => {
+  let createdSharelink: string | null;
+
+  test('Prepare sharelink', async({ page }) => {
+    await page.goto('/Sandbox/Bootstrap5');
+
+    // Create Sharelink
+    await page.getByTestId('open-page-item-control-btn').click();
+    await page.getByTestId('open-page-accessories-modal-btn-with-share-link-management-data-tab').click();
+    await page.getByTestId('btn-sharelink-toggleform').click();
+    await page.getByTestId('btn-sharelink-issue').click();
+
+    // Get ShareLink
+    createdSharelink = await page.getByTestId('share-link').textContent();
+    expect(createdSharelink).toHaveLength(24);
+  });
+
+  test('The sharelink page is successfully loaded', async({ page }) => {
+    await page.goto('/');
+
+    // Logout
+    await page.getByTestId('personal-dropdown-button').click();
+    await expect(page.getByTestId('logout-button')).toBeVisible();
+    await page.getByTestId('logout-button').click();
+    await page.waitForURL('http://localhost:3000/login');
+
+    // Access sharelink
+    await page.goto(`/share/${createdSharelink}`);
+    await expect(page.locator('.page-meta')).toBeVisible();
+
+    await login(page);
+  });
+});

+ 5 - 20
apps/app/playwright/auth.setup.ts

@@ -1,24 +1,9 @@
-import path from 'node:path';
+import { test as setup } from '@playwright/test';
 
-import { test as setup, expect } from '@playwright/test';
-
-const authFile = path.resolve(__dirname, './.auth/admin.json');
+import { login } from './utils/Login';
 
+// Commonised login process for use elsewhere
+// see: https://github.com/microsoft/playwright/issues/22114
 setup('Authenticate as the "admin" user', async({ page }) => {
-  // Perform authentication steps. Replace these actions with your own.
-  await page.goto('/admin');
-
-  const loginForm = await page.$('form#login-form');
-
-  if (loginForm != null) {
-    await page.getByLabel('Username or E-mail').fill('admin');
-    await page.getByLabel('Password').fill('adminadmin');
-    await page.locator('[type=submit]').filter({ hasText: 'Login' }).click();
-  }
-
-  await page.waitForURL('/admin');
-  await expect(page).toHaveTitle(/Wiki Management Homepage/);
-
-  // End of authentication steps.
-  await page.context().storageState({ path: authFile });
+  await login(page);
 });

+ 24 - 0
apps/app/playwright/utils/Login.ts

@@ -0,0 +1,24 @@
+import path from 'node:path';
+
+import { expect, type Page } from '@playwright/test';
+
+const authFile = path.resolve(__dirname, '../.auth/admin.json');
+
+export const login = async(page: Page): Promise<void> => {
+  // Perform authentication steps. Replace these actions with your own.
+  await page.goto('/admin');
+
+  const loginForm = await page.$('form#login-form');
+
+  if (loginForm != null) {
+    await page.getByLabel('Username or E-mail').fill('admin');
+    await page.getByLabel('Password').fill('adminadmin');
+    await page.locator('[type=submit]').filter({ hasText: 'Login' }).click();
+  }
+
+  await page.waitForURL('/admin');
+  await expect(page).toHaveTitle(/Wiki Management Homepage/);
+
+  // End of authentication steps.
+  await page.context().storageState({ path: authFile });
+};

+ 1 - 0
apps/app/playwright/utils/index.ts

@@ -1 +1,2 @@
 export * from './CollapseSidebar';
+export * from './Login';

+ 0 - 1
apps/app/src/components-universal/Layout/RawLayout.tsx

@@ -9,7 +9,6 @@ import { useIsomorphicLayoutEffect } from 'usehooks-ts';
 import { useNextThemes, NextThemesProvider } from '~/stores-universal/use-next-themes';
 import loggerFactory from '~/utils/logger';
 
-
 import styles from './RawLayout.module.scss';
 
 const toastContainerClass = styles['grw-toast-container'] ?? '';

+ 1 - 0
apps/app/src/components/Navbar/PageEditorModeManager.tsx

@@ -109,6 +109,7 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
         role="group"
         aria-label="page-editor-mode-manager"
         id="grw-page-editor-mode-manager"
+        data-testid="grw-page-editor-mode-manager"
       >
         {(isDeviceLargerThanMd || editorMode !== EditorMode.View) && (
           <PageEditorModeButton

+ 1 - 1
apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLinkList.tsx

@@ -25,7 +25,7 @@ const ShareLinkTr = (props: ShareLinkTrProps): JSX.Element => {
   return (
     <tr key={shareLinkId}>
       <td className="d-flex justify-content-between align-items-center">
-        <span>{shareLinkId}</span>
+        <span data-testid="share-link">{shareLinkId}</span>
 
         { isRelatedPageExists && (
           <CopyDropdown

+ 1 - 1
apps/app/src/components/Sidebar/SidebarNav/PersonalDropdown.tsx

@@ -108,7 +108,7 @@ export const PersonalDropdown = (): JSX.Element => {
             </span>
           </DropdownItem>
 
-          <DropdownItem onClick={logoutHandler} className={`my-1 ${styles['personal-dropdown-item']}`}>
+          <DropdownItem data-testid="logout-button" onClick={logoutHandler} className={`my-1 ${styles['personal-dropdown-item']}`}>
             <span className="d-flex align-items-center">
               <span className="item-icon material-symbols-outlined me-2 pb-0 fs-6">logout</span>
               <span className="item-text">{t('Sign out')}</span>

+ 0 - 93
apps/app/test/cypress/e2e/20-basic-features/20-basic-features--sticky-features.cy.ts

@@ -1,93 +0,0 @@
-context('Access to any page', () => {
-  const ssPrefix = 'subnav-';
-
-  beforeEach(() => {
-    // login
-    cy.fixture("user-admin.json").then(user => {
-      cy.login(user.username, user.password);
-    });
-
-    cy.visit('/');
-
-    cy.waitUntilSkeletonDisappear();
-    cy.collapseSidebar(true, true);
-  });
-
-  it('Subnavigation displays changes on scroll down and up', () => {
-    cy.waitUntil(() => {
-      // do
-      // Scroll the window 250px down is enough to trigger sticky effect
-       cy.scrollTo(0, 250);
-      // wait until
-      return cy.get('.sticky-outer-wrapper').should('have.class', 'active');
-    });
-
-    cy.waitUntilSkeletonDisappear();
-    cy.screenshot(`${ssPrefix}visible-on-scroll-down`);
-
-    cy.waitUntil(() => {
-      // do
-      // Scroll the window back to top
-      cy.scrollTo(0, 0);
-      // wait until
-      return cy.get('.sticky-outer-wrapper').should('not.have.class', 'active');
-    });
-
-    cy.screenshot(`${ssPrefix}invisible-on-scroll-top`);
-  });
-
-  it('Subnavigation is not displayed when move to other pages', () => {
-    cy.waitUntil(() => {
-      // do
-      // Scroll the window 250px down is enough to trigger sticky effect
-      cy.scrollTo(0, 250);
-      // wait until
-      return cy.get('.sticky-outer-wrapper').should('have.class', 'active');
-    });
-
-    // Move to /Sandbox page
-    cy.visit('/Sandbox');
-
-    cy.waitUntilSkeletonDisappear();
-    cy.collapseSidebar(true);
-
-    return cy.get('.sticky-outer-wrapper').should('not.have.class', 'active');
-    cy.screenshot(`${ssPrefix}not-visible-on-move-to-other-pages`);
-  });
-
-  it('Able to click buttons on subnavigation switcher when sticky', () => {
-    cy.waitUntil(() => {
-      // do
-      // Scroll the window 250px down is enough to trigger sticky effect
-      cy.scrollTo(0, 250);
-      // wait until
-      return cy.get('.sticky-outer-wrapper').should('have.class', 'active');
-    });
-    cy.waitUntil(() => {
-      cy.getByTestid('grw-contextual-sub-nav').within(() => {
-        cy.getByTestid('editor-button').as('editorButton').should('be.visible');
-        cy.get('@editorButton').click();
-      });
-      return cy.get('.layout-root').then($elem => $elem.hasClass('editing'));
-    });
-    cy.getByTestid('grw-editor-navbar-bottom').should('be.visible');
-    // cy.get('.CodeMirror').should('be.visible');
-    cy.screenshot(`${ssPrefix}open-editor-when-sticky`);
-  });
-
-  it('Subnavigation is sticky when on small window', () => {
-    cy.waitUntil(() => {
-      // do
-      // Scroll the window 500px down
-      cy.scrollTo(0, 500);
-      // wait until
-      return cy.get('.sticky-outer-wrapper').should('have.class', 'active');
-    });
-    cy.waitUntilSkeletonDisappear();
-    cy.viewport(600, 1024);
-    cy.getByTestid('grw-contextual-sub-nav').within(() => {
-      cy.get('#grw-page-editor-mode-manager').should('be.visible');
-    })
-    cy.screenshot(`${ssPrefix}sticky-on-small-window`);
-  });
-});

+ 0 - 30
apps/app/test/cypress/e2e/21-basic-features-for-guest/21-basic-features-for-guest--sticky-for-guest.cy.ts

@@ -1,30 +0,0 @@
-context('Access sticky sub navigation switcher for guest', () => {
-  const ssPrefix = 'access-sticky-by-guest-';
-
-  it('Sub navigation sticky changes when scrolling down and up', () => {
-    cy.visit('/Sandbox');
-    cy.waitUntilSkeletonDisappear();
-    cy.collapseSidebar(true, true);
-
-    // Sticky
-    cy.waitUntil(() => {
-      // do
-      // Scroll page down 250px
-      cy.scrollTo(0, 250);
-      // wait until
-      return cy.get('.sticky-outer-wrapper').should('have.class', 'active');
-    });
-    cy.screenshot(`${ssPrefix}subnav-switcher-is-sticky-on-scroll-down`);
-
-    // Not sticky
-    cy.waitUntil(() => {
-      // do
-      // Scroll page to top
-      cy.scrollTo(0, 0);
-      // wait until
-      return cy.get('.sticky-outer-wrapper').should('not.have.class', 'active');
-    });
-    cy.screenshot(`${ssPrefix}subnav-switcher-is-not-sticky-on-scroll-top`);
-  });
-
-});

+ 0 - 71
apps/app/test/cypress/e2e/22-sharelink/22-sharelink--access-to-sharelink.cy.ts

@@ -1,71 +0,0 @@
-context('Access to sharelink by guest', () => {
-  const ssPrefix = 'access-to-sharelink-by-guest-';
-
-  let createdSharelinkId: string;
-
-  it('Prepare sharelink', () => {
-    // login
-    cy.fixture("user-admin.json").then(user => {
-      cy.login(user.username, user.password);
-    });
-
-    cy.visit('/Sandbox/Bootstrap5');
-
-    // open dropdown
-    cy.waitUntil(() => {
-      // do
-      cy.getByTestid('grw-contextual-sub-nav').within(() => {
-        cy.getByTestid('open-page-item-control-btn', { timeout: 14000 }).find('button').click({force: true});
-      });
-      // wait until
-      return cy.getByTestid('page-item-control-menu').then($elem => $elem.is(':visible'))
-    });
-
-    // open modal
-    cy.get('.dropdown-menu.show').should('be.visible').within(() => {
-      cy.getByTestid('open-page-accessories-modal-btn-with-share-link-management-data-tab').click({force: true});
-    });
-    cy.waitUntilSpinnerDisappear();
-    cy.getByTestid('page-accessories-modal').should('be.visible');
-    cy.getByTestid('share-link-management').should('be.visible');
-
-    // create share link
-    cy.getByTestid('share-link-management').within(() => {
-      // open form
-      cy.waitUntil(() => {
-        // do
-        cy.getByTestid('btn-sharelink-toggleform').click();
-        // wait until
-        return cy.getByTestid('btn-sharelink-issue').then($elem => $elem.is(':visible'))
-      });
-
-      cy.getByTestid('btn-sharelink-issue').should('be.visible').click();
-
-      cy.get('tbody')
-        .find('tr').first() // the first row
-        .find('td').first() // the first column
-        .find('span').first().then((elem) => {
-
-        // store id
-        createdSharelinkId = elem.text();
-        // overwrite the label
-        elem.html('63d100000000000000000000');
-      });
-    });
-
-    cy.getByTestid('page-accessories-modal').within(() => { cy.screenshot(`${ssPrefix}-sharelink-created`) });
-  });
-
-  it('The sharelink page is successfully loaded', () => {
-    cy.visit(`/share/${createdSharelinkId}`);
-
-    cy.getByTestid('grw-contextual-sub-nav').should('be.visible');
-    cy.get('.wiki').should('be.visible');
-
-    cy.waitUntilSkeletonDisappear();
-    cy.screenshot(`${ssPrefix}-access-to-sharelink`);
-  });
-
-});
-
-

+ 6 - 0
packages/pluginkit/CHANGELOG.md

@@ -1,5 +1,11 @@
 # @growi/pluginkit
 
+## 1.0.1
+
+### Patch Changes
+
+- [#8898](https://github.com/weseek/growi/pull/8898) [`7a50227`](https://github.com/weseek/growi/commit/7a502271b35bae4b419e54a08b2b00c7b140db46) Thanks [@yuki-takei](https://github.com/yuki-takei)! - Update tsconfig.json module setting
+
 ## 1.0.0
 
 ### Major Changes

+ 1 - 1
packages/pluginkit/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/pluginkit",
-  "version": "1.0.0",
+  "version": "1.0.1",
   "description": "Plugin kit for GROWI",
   "license": "MIT",
   "main": "dist/index.cjs",

+ 35 - 5
yarn.lock

@@ -2136,7 +2136,7 @@
     react-dom "^18.2.0"
 
 "@growi/pluginkit@link:packages/pluginkit":
-  version "1.0.0"
+  version "1.0.1"
   dependencies:
     "@growi/core" "^1.0.0"
     extensible-custom-error "^0.0.7"
@@ -17263,7 +17263,7 @@ string-template@>=1.0.0:
   resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
   integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=
 
-"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -17281,6 +17281,15 @@ string-width@=4.2.2:
     is-fullwidth-code-point "^3.0.0"
     strip-ansi "^6.0.0"
 
+"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+  dependencies:
+    emoji-regex "^8.0.0"
+    is-fullwidth-code-point "^3.0.0"
+    strip-ansi "^6.0.1"
+
 string-width@^5.0.1, string-width@^5.1.2:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
@@ -17364,7 +17373,7 @@ stringify-entities@^4.0.0:
     character-entities-html4 "^2.0.0"
     character-entities-legacy "^3.0.0"
 
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -17378,6 +17387,13 @@ strip-ansi@^3.0.0:
   dependencies:
     ansi-regex "^2.0.0"
 
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+  dependencies:
+    ansi-regex "^5.0.1"
+
 strip-ansi@^7.0.1, strip-ansi@^7.1.0:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -19209,7 +19225,7 @@ word-wrap@^1.2.3:
   resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
   integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
 
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -19227,6 +19243,15 @@ wrap-ansi@^6.2.0:
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
 
+wrap-ansi@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+  dependencies:
+    ansi-styles "^4.0.0"
+    string-width "^4.1.0"
+    strip-ansi "^6.0.0"
+
 wrap-ansi@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
@@ -19272,7 +19297,12 @@ ws@^7.3.1:
   resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
   integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
 
-ws@^8.3.0, ws@~8.11.0:
+ws@^8.17.1:
+  version "8.17.1"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b"
+  integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==
+
+ws@~8.11.0:
   version "8.11.0"
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
   integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==