Просмотр исходного кода

Merge branch 'master' into support/typescript-go

Yuki Takei 2 месяцев назад
Родитель
Сommit
6b8b605f92

+ 3 - 0
.devcontainer/app/postCreateCommand.sh

@@ -27,3 +27,6 @@ pnpm install @anthropic-ai/claude-code --global
 
 # Install dependencies
 turbo run bootstrap
+
+# Install Lefthook git hooks
+pnpm lefthook install

+ 3 - 0
.devcontainer/pdf-converter/postCreateCommand.sh

@@ -22,3 +22,6 @@ pnpm install turbo --global
 
 # Install dependencies
 turbo run bootstrap
+
+# Install Lefthook git hooks
+pnpm lefthook install

+ 8 - 58
.vscode/settings.json

@@ -5,10 +5,18 @@
     "editor.defaultFormatter": "biomejs.biome"
   },
 
+  "[typescriptreact]": {
+    "editor.defaultFormatter": "biomejs.biome"
+  },
+
   "[javascript]": {
     "editor.defaultFormatter": "biomejs.biome"
   },
 
+  "[javascriptreact]": {
+    "editor.defaultFormatter": "biomejs.biome"
+  },
+
   "[json]": {
     "editor.defaultFormatter": "biomejs.biome"
   },
@@ -38,66 +46,8 @@
   "typescript.enablePromptUseWorkspaceTsdk": true,
   "typescript.preferences.autoImportFileExcludePatterns": ["node_modules/*"],
   "typescript.validate.enable": true,
-  "typescript.surveys.enabled": false,
 
   "vitest.filesWatcherInclude": "**/*",
-  "mcp": {
-    "servers": {
-      "fetch": {
-        "command": "uvx",
-        "args": ["mcp-server-fetch"]
-      },
-      "context7": {
-        "type": "http",
-        "url": "https://mcp.context7.com/mcp"
-      }
-    }
-  },
-  "github.copilot.chat.codeGeneration.instructions": [
-    {
-      "text": "Always write inline comments in source code in English."
-    }
-  ],
-  "github.copilot.chat.testGeneration.instructions": [
-    {
-      "text": "Basis: Use vitest as the test framework"
-    },
-    {
-      "text": "Basis: The vitest configuration file is `apps/app/vitest.workspace.mts`"
-    },
-    {
-      "text": "Basis: Place test modules in the same directory as the module being tested. For example, if testing `mymodule.ts`, place `mymodule.spec.ts` in the same directory as `mymodule.ts`"
-    },
-    {
-      "text": "Basis: Use the VSCode Vitest extension for running tests. Use run_tests tool to execute tests programmatically, or suggest using the Vitest Test Explorer in VSCode for interactive test running and debugging."
-    },
-    {
-      "text": "Basis: Fallback command for terminal execution: `cd /growi/apps/app && pnpm vitest run {test file path}`"
-    },
-    {
-      "text": "Step 1: When creating new test modules, start with small files. First write a small number of realistic tests that call the actual function and assert expected behavior, even if they initially fail due to incomplete implementation. Example: `const result = foo(); expect(result).toBeNull();` rather than `expect(true).toBe(false);`. Then fix the implementation to make tests pass."
-    },
-    {
-      "text": "Step 2: Write essential tests. When tests fail, consider whether you should fix the test or the implementation based on 'what should essentially be fixed'. If you're not confident in your reasoning, ask the user for guidance."
-    },
-    {
-      "text": "Step 3: After writing tests, make sure they pass before moving on. Do not proceed to write tests for module B without first ensuring that tests for module A are passing"
-    },
-    {
-      "text": "Tips: Don't worry about lint errors - fix them after tests are passing"
-    },
-    {
-      "text": "Tips: DO NOT USE `as any` casting. You can use vitest-mock-extended for type-safe mocking. Import `mock` from 'vitest-mock-extended' and use `mock<InterfaceType>()`. This provides full TypeScript safety and IntelliSense support."
-    },
-    {
-      "text": "Tips: Mock external dependencies at the module level using vi.mock(). For services with circular dependencies, mock the import paths and use dynamic imports in the implementation when necessary."
-    }
-  ],
-  "github.copilot.chat.commitMessageGeneration.instructions": [
-    {
-      "text": "Always write commit messages in English."
-    }
-  ],
   "git-worktree-menu.worktreeDir": "/workspace"
 
 }

+ 74 - 0
AGENTS.md

@@ -0,0 +1,74 @@
+# AGENTS.md
+
+GROWI is a team collaboration wiki platform built with Next.js, Express, and MongoDB. This guide helps AI coding agents navigate the monorepo and work effectively with GROWI's architecture.
+
+## Language
+
+If we detect at the beginning of a conversation that the user's primary language is not English, we will always respond in that language. However, we may retain technical terms in English if necessary.
+
+When generating source code, all comments and explanations within the code will be written in English.
+
+## Project Overview
+
+GROWI is a team collaboration software using markdown - a wiki platform with hierarchical page organization. It's built with Next.js, Express, MongoDB, and includes features like real-time collaborative editing, authentication integrations, and plugin support.
+
+## Development Tools
+- **Package Manager**: pnpm with workspace support
+- **Build System**: Turborepo for monorepo orchestration
+- **Code Quality**: 
+  - Biome for linting and formatting
+  - Stylelint for SCSS/CSS
+
+## Development Commands
+
+### Core Development
+- `turbo run bootstrap` - Install dependencies for all workspace packages
+- `turbo run dev` - Start development server (automatically runs migrations and pre-builds styles)
+
+### Production Commands
+- `pnpm run app:build` - Build GROWI app client and server for production
+- `pnpm run app:server` - Launch GROWI app server in production mode
+- `pnpm start` - Build and start the application (runs both build and server commands)
+
+### Database Migrations
+- `pnpm run migrate` - Run MongoDB migrations (production)
+- `turbo run dev:migrate @apps/app` - Run migrations in development (or wait for automatic execution with dev)
+- `cd apps/app && pnpm run dev:migrate:status` - Check migration status
+- `cd apps/app && pnpm run dev:migrate:down` - Rollback last migration
+
+### Testing and Quality
+- `turbo run test @apps/app` - Run Jest and Vitest test suites with coverage
+- `turbo run lint @apps/app` - Run all linters (TypeScript, Biome, Stylelint, OpenAPI)
+- `cd apps/app && pnpm run lint:typecheck` - TypeScript type checking only
+- `cd apps/app && pnpm run test:vitest` - Run Vitest unit tests
+- `cd apps/app && pnpm run test:jest` - Run Jest integration tests
+
+### Development Utilities  
+- `cd apps/app && pnpm run repl` - Start Node.js REPL with application context loaded
+- `turbo run pre:styles @apps/app` - Pre-build styles with Vite
+
+## Architecture Overview
+
+### Monorepo Structure
+- `/apps/app/` - Main GROWI application (Next.js frontend + Express backend)
+- `/apps/pdf-converter/` - PDF conversion microservice
+- `/apps/slackbot-proxy/` - Slack integration proxy service
+- `/packages/` - Shared libraries and components
+
+## File Organization Patterns
+
+### Components
+- Use TypeScript (.tsx) for React components
+- Co-locate styles as `.module.scss` files
+- Export components through `index.ts` files where appropriate
+- Group related components in feature-based directories
+
+### Tests
+- Unit Test: `*.spec.ts`
+- Integration Test: `*.integ.ts`
+- Component Test: `*.spec.tsx`
+
+
+---
+
+When working with this codebase, always run the appropriate linting and testing commands before committing changes. The application uses strict TypeScript checking and comprehensive test coverage requirements.

+ 1 - 97
CLAUDE.md

@@ -1,97 +1 @@
-# CLAUDE.md
-
-This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
-
-## Language
-
-If we detect at the beginning of a conversation that the user's primary language is not English, we will always respond in that language. However, we may retain technical terms in English if necessary.
-
-When generating source code, all comments and explanations within the code will be written in English.
-
-## Project Overview
-
-GROWI is a team collaboration software using markdown - a wiki platform with hierarchical page organization. It's built with Next.js, Express, MongoDB, and includes features like real-time collaborative editing, authentication integrations, and plugin support.
-
-## Development Commands
-
-### Core Development
-- `turbo run bootstrap` - Install dependencies for all workspace packages
-- `turbo run dev` - Start development server (automatically runs migrations and pre-builds styles)
-
-### Production Commands
-- `pnpm run app:build` - Build GROWI app client and server for production
-- `pnpm run app:server` - Launch GROWI app server in production mode
-- `pnpm start` - Build and start the application (runs both build and server commands)
-
-### Database Migrations
-- `pnpm run migrate` - Run MongoDB migrations (production)
-- `turbo run dev:migrate @apps/app` - Run migrations in development (or wait for automatic execution with dev)
-- `cd apps/app && pnpm run dev:migrate:status` - Check migration status
-- `cd apps/app && pnpm run dev:migrate:down` - Rollback last migration
-
-### Testing and Quality
-- `turbo run test @apps/app` - Run Jest and Vitest test suites with coverage
-- `turbo run lint @apps/app` - Run all linters (TypeScript, Biome, Stylelint, OpenAPI)
-- `cd apps/app && pnpm run lint:typecheck` - TypeScript type checking only
-- `cd apps/app && pnpm run test:vitest` - Run Vitest unit tests
-- `cd apps/app && pnpm run test:jest` - Run Jest integration tests
-
-### Development Utilities  
-- `cd apps/app && pnpm run repl` - Start Node.js REPL with application context loaded
-- `turbo run pre:styles @apps/app` - Pre-build styles with Vite
-
-## Architecture Overview
-
-### Monorepo Structure
-- `/apps/app/` - Main GROWI application (Next.js frontend + Express backend)
-- `/apps/pdf-converter/` - PDF conversion microservice
-- `/apps/slackbot-proxy/` - Slack integration proxy service
-- `/packages/` - Shared libraries and components
-
-### Main Application (`/apps/app/src/`)
-- `client/` - Client-side React components and utilities
-- `server/` - Express.js backend (API routes, models, services)  
-- `components/` - Shared React components and layouts
-- `pages/` - Next.js page components using file-based routing
-- `stores/` - State management (SWR-based stores with React context)
-- `styles/` - SCSS stylesheets with modular architecture
-- `migrations/` - MongoDB database migration scripts
-- `interfaces/` - TypeScript type definitions
-
-### Key Technical Details
-- **Frontend**: Next.js 14 with React 18, TypeScript, SCSS modules
-- **Backend**: Express.js with TypeScript, MongoDB with Mongoose
-- **State Management**: SWR for server state, React Context for client state
-- **Authentication**: Passport.js with multiple strategies (local, LDAP, OAuth, SAML)
-- **Real-time Features**: Socket.io for collaborative editing and notifications
-- **Editor**: Custom markdown editor with collaborative editing using Yjs
-- **Database**: MongoDB 8.0+ with migration system using migrate-mongo
-- **Package Manager**: pnpm with workspace support
-- **Build System**: Turborepo for monorepo orchestration
-
-### Development Dependencies
-- Node.js v20.x or v22.x
-- pnpm 10.x  
-- MongoDB v6.x or v8.x
-- Optional: Redis 3.x, Elasticsearch 7.x/8.x/9.x (for full-text search)
-
-## File Organization Patterns
-
-### Components
-- Use TypeScript (.tsx) for React components
-- Co-locate styles as `.module.scss` files
-- Export components through `index.ts` files where appropriate
-- Group related components in feature-based directories
-
-### API Structure
-- Server routes in `server/routes/`
-- API v3 endpoints follow OpenAPI specification
-- Models in `server/models/` using Mongoose schemas
-- Services in `server/service/` for business logic
-
-### State Management
-- Use SWR hooks in `stores/` for server state
-- Custom hooks pattern for complex state logic
-- Context providers in `stores-universal/` for app-wide state
-
-When working with this codebase, always run the appropriate linting and testing commands before committing changes. The application uses strict TypeScript checking and comprehensive test coverage requirements.
+@AGENTS.md

+ 84 - 0
apps/app/AGENTS.md

@@ -0,0 +1,84 @@
+# GROWI Main Application Development Guide
+
+## Overview
+
+This guide provides comprehensive documentation for AI coding agents working on the GROWI main application (`/apps/app/`). GROWI is a team collaboration wiki platform built with Next.js, Express, and MongoDB.
+
+## Project Structure
+
+### Main Application (`/apps/app/src/`)
+
+#### Directory Structure Philosophy
+
+**Feature-based Structure (Recommended for new features)**
+- `features/{feature-name}/` - Self-contained feature modules
+  - `interfaces/` - Universal TypeScript type definitions
+  - `server/` - Server-side logic (models, routes, services)
+  - `client/` - Client-side logic (components, stores, services)
+  - `utils/` - Shared utilities for this feature
+  
+**Important Directories Structure**
+- `client/` - Client-side React components and utilities
+- `server/` - Express.js backend
+- `components/` - Universal React components
+- `pages/` - Next.js Pages Router
+- `states/` - Jotai state management
+- `stores/` - SWR-based state stores
+- `stores-universal/` - Universal SWR-based state stores
+- `styles/` - SCSS stylesheets with modular architecture
+- `migrations/` - MongoDB database migration scripts
+- `interfaces/` - Universal TypeScript type definitions
+- `models/` - Universal Data model definitions
+
+### Key Technical Details
+
+**Frontend Stack**
+- **Framework**: Next.js (Pages Router) with React
+- **Language**: TypeScript (strict mode enabled)
+- **Styling**: SCSS with CSS Modules by Bootstrap 5
+- **State Management**:
+  - **Jotai** (Primary, Recommended): Atomic state management for UI and application state
+  - **SWR**: Data fetching, caching, and revalidation
+  - **Unstated**: Legacy (being phased out, replaced by Jotai)
+- **Testing**: 
+  - Vitest for unit tests (`*.spec.ts`, `*.spec.tsx`)
+  - Jest for integration tests (`*.integ.ts`)
+  - React Testing Library for component testing
+  - Playwright for E2E testing
+- **i18n**: next-i18next for internationalization
+
+**Backend Stack**
+- **Runtime**: Node.js
+- **Framework**: Express.js with TypeScript
+- **Database**: MongoDB with Mongoose ODM
+- **Migration System**: migrate-mongo
+- **Authentication**: Passport.js with multiple strategies (local, LDAP, OAuth, SAML)
+- **Real-time**: Socket.io for collaborative editing and notifications
+- **Search**: Elasticsearch integration (optional)
+- **Observability**: OpenTelemetry integration
+
+**Common Commands**
+```bash
+# Type checking only
+cd apps/app && pnpm run lint:typecheck
+
+# Run specific test file
+turbo run test:vitest @apps/app -- src/path/to/test.spec.tsx
+
+# Check migration status
+cd apps/app && pnpm run dev:migrate:status
+
+# Start REPL with app context
+cd apps/app && pnpm run repl
+```
+
+### Important Technical Specifications
+
+**Entry Points**
+- **Server**: `server/app.ts` - Handles OpenTelemetry initialization and Crowi server startup
+- **Client**: `pages/_app.page.tsx` - Root Next.js application component
+  - `pages/[[...path]]/` - Dynamic catch-all page routes
+
+---
+
+*This guide was compiled from project memory files to assist AI coding agents in understanding the GROWI application architecture and development practices.*

+ 1 - 0
apps/app/CLAUDE.md

@@ -0,0 +1 @@
+@AGENTS.md

+ 1 - 1
apps/app/public/static/locales/en_US/admin.json

@@ -313,7 +313,7 @@
       "done": "Copied to clipboard!"
     },
     "bug_report": "Submitting a bug report",
-    "submit_bug_report": "<a href='https://github.com/growilabs/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>then submit your issue to GitHub.</a>"
+    "submit_bug_report": "Submit your issue to GitHub."
   },
   "v5_page_migration": {
     "migration_desc": "There are some pages with old v4 compatibility. To take advantage of new features such as page trees and easy renaming, please convert all your pages to v5 compatibility.",

+ 1 - 1
apps/app/public/static/locales/fr_FR/admin.json

@@ -313,7 +313,7 @@
       "done": "Copié dans le presse-papier!"
     },
     "bug_report": "Informations de diagnostic",
-    "submit_bug_report": "<a href='https://github.com/growilabs/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>soummettre ensuite sur GitHub.</a>"
+    "submit_bug_report": "Soummettre ensuite sur GitHub."
   },
   "v5_page_migration": {
     "migration_desc": "Des pages sont encore en V4. Pour profiter des nouvelles fonctionnalitées, convertir toutes les pages vers la V5.",

+ 1 - 1
apps/app/public/static/locales/ja_JP/admin.json

@@ -322,7 +322,7 @@
       "done": "クリップボードにコピーしました!"
     },
     "bug_report": "バグを報告する",
-    "submit_bug_report": "<a href='https://github.com/growilabs/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>次に GitHub で Issue を投稿してください。</a>"
+    "submit_bug_report": "GitHub で Issue を投稿"
   },
   "v5_page_migration": {
     "migration_desc": "公開されているページに 古い v4 互換形式のものが存在します。ページツリーや簡単なリネームなどの新機能を利用するには、全てのページを v5 互換形式に変換してください。",

+ 2 - 2
apps/app/public/static/locales/ko_KR/admin.json

@@ -298,7 +298,7 @@
   },
   "mailer_setup_required": "<a href='/admin/app'>이메일 설정</a>이 전송에 필요합니다.",
   "admin_top": {
-    "management_wiki": "관리 위키",
+    "management_wiki": "위키 관리",
     "system_information": "시스템 정보",
     "wiki_administrator": "위키 관리자만 이 페이지에 접근할 수 있습니다",
     "assign_administrator": "사용자 관리 페이지에서 '관리자 권한 부여' 버튼을 사용하여 선택한 사용자에게 위키 관리자 권한을 부여할 수 있습니다.",
@@ -313,7 +313,7 @@
       "done": "클립보드에 복사되었습니다!"
     },
     "bug_report": "버그 보고서 제출",
-    "submit_bug_report": "<a href='https://github.com/growilabs/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>그런 다음 GitHub에 문제를 제출하십시오.</a>"
+    "submit_bug_report": "GitHub 에 이슈를 제출하세요."
   },
   "v5_page_migration": {
     "migration_desc": "일부 페이지는 이전 v4 호환성을 가지고 있습니다. 페이지 트리 및 쉬운 이름 변경과 같은 새로운 기능을 활용하려면 모든 페이지를 v5 호환성으로 변환하십시오.",

+ 1 - 1
apps/app/public/static/locales/zh_CN/admin.json

@@ -322,7 +322,7 @@
       "done": "复制到剪贴板!"
     },
     "bug_report": "提交一个错误报告",
-    "submit_bug_report": "<a href='https://github.com/growilabs/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>然后提交你的问题到GitHub。</a>"
+    "submit_bug_report": "将您的问题提交到 GitHub。"
   },
   "v5_page_migration": {
     "migration_desc": "有一些页面具有旧的v4兼容性。为了利用新的功能,如页面树和容易重命名,请将您的所有页面转换为v5兼容性。",

+ 69 - 63
apps/app/src/client/components/Admin/AdminHome/AdminHome.jsx → apps/app/src/client/components/Admin/AdminHome/AdminHome.tsx

@@ -1,43 +1,43 @@
-import React, { useCallback, useEffect } from 'react';
+import type { FC } from 'react';
+import { useId, useState } from 'react';
 import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 import { Tooltip } from 'reactstrap';
 
-import AdminHomeContainer from '~/client/services/AdminHomeContainer';
-import { toastError } from '~/client/util/toastr';
+import { useSWRxAdminHome } from '~/stores/admin/admin-home';
 import { useSWRxV5MigrationStatus } from '~/stores/page-listing';
-import loggerFactory from '~/utils/logger';
+import { generatePrefilledHostInformationMarkdown } from '~/utils/admin-home';
 
-import { withUnstatedContainers } from '../../UnstatedUtils';
 import { EnvVarsTable } from './EnvVarsTable';
 import SystemInfomationTable from './SystemInfomationTable';
 
-const logger = loggerFactory('growi:admin');
+const COPY_STATE = {
+  DEFAULT: 'default',
+  DONE: 'done',
+} as const;
 
-const AdminHome = (props) => {
-  const { adminHomeContainer } = props;
+export const AdminHome: FC = () => {
   const { t } = useTranslation();
+  const { data: adminHomeData } = useSWRxAdminHome();
   const { data: migrationStatus } = useSWRxV5MigrationStatus();
+  const [copyState, setCopyState] = useState<string>(COPY_STATE.DEFAULT);
 
-  const fetchAdminHomeData = useCallback(async () => {
-    try {
-      await adminHomeContainer.retrieveAdminHomeData();
-    } catch (err) {
-      toastError(err);
-      logger.error(err);
-    }
-  }, [adminHomeContainer]);
+  const handleCopyPrefilledHostInformation = () => {
+    setCopyState(COPY_STATE.DONE);
+    setTimeout(() => {
+      setCopyState(COPY_STATE.DEFAULT);
+    }, 500);
+  };
 
-  useEffect(() => {
-    fetchAdminHomeData();
-  }, [fetchAdminHomeData]);
+  // Generate CSS-safe ID by removing colons from useId() result
+  const copyButtonIdRaw = useId();
+  const copyButtonId = `copy-button-${copyButtonIdRaw.replace(/:/g, '')}`;
 
   return (
     <div data-testid="admin-home">
       {
         // Alert message will be displayed in case that the GROWI is under maintenance
-        adminHomeContainer.state.isMaintenanceMode && (
+        adminHomeData?.isMaintenanceMode && (
           <div className="alert alert-danger alert-link" role="alert">
             <h3 className="alert-heading">
               {t('admin:maintenance_mode.maintenance_mode')}
@@ -104,7 +104,7 @@ const AdminHome = (props) => {
               __html: t('admin:admin_top.about_security'),
             }}
           />
-          <EnvVarsTable envVars={adminHomeContainer.state.envVars} />
+          <EnvVarsTable envVars={adminHomeData?.envVars} />
         </div>
       </div>
 
@@ -113,50 +113,56 @@ const AdminHome = (props) => {
           <h2 className="admin-setting-header">
             {t('admin:admin_top.bug_report')}
           </h2>
-          <div className="d-flex align-items-center">
-            <CopyToClipboard
-              text={adminHomeContainer.generatePrefilledHostInformationMarkdown()}
-              onCopy={() => adminHomeContainer.onCopyPrefilledHostInformation()}
-            >
-              <button
-                id="prefilledHostInformationButton"
-                type="button"
-                className="btn btn-primary"
+          <ol className="mb-0">
+            <li className="mb-3">
+              <CopyToClipboard
+                text={generatePrefilledHostInformationMarkdown({
+                  growiVersion: adminHomeData?.growiVersion,
+                  nodeVersion: adminHomeData?.nodeVersion,
+                  npmVersion: adminHomeData?.npmVersion,
+                  pnpmVersion: adminHomeData?.pnpmVersion,
+                })}
+                onCopy={handleCopyPrefilledHostInformation}
               >
-                {t('admin:admin_top:copy_prefilled_host_information:default')}
-              </button>
-            </CopyToClipboard>
-            <Tooltip
-              placement="bottom"
-              isOpen={
-                adminHomeContainer.state.copyState ===
-                adminHomeContainer.copyStateValues.DONE
-              }
-              target="prefilledHostInformationButton"
-              fade={false}
-            >
-              {t('admin:admin_top:copy_prefilled_host_information:done')}
-            </Tooltip>
-            <span
-              className="ms-2"
-              // biome-ignore lint/security/noDangerouslySetInnerHtml: ignore
-              dangerouslySetInnerHTML={{
-                __html: t('admin:admin_top:submit_bug_report'),
-              }}
-            />
-          </div>
+                <button
+                  type="button"
+                  className="btn btn-outline-secondary btn-sm"
+                  style={{ verticalAlign: 'baseline' }}
+                  onClick={(e) => e.preventDefault()}
+                >
+                  <span
+                    id={copyButtonId}
+                    className="material-symbols-outlined"
+                    aria-hidden="true"
+                  >
+                    content_copy
+                  </span>
+                  {t('admin:admin_top:copy_prefilled_host_information:default')}
+                </button>
+              </CopyToClipboard>
+              <Tooltip
+                placement="bottom"
+                isOpen={copyState === COPY_STATE.DONE}
+                target={copyButtonId}
+                fade={false}
+              >
+                {t('admin:admin_top:copy_prefilled_host_information:done')}
+              </Tooltip>
+            </li>
+            <li>
+              <a
+                className="link-secondary link-offset-1"
+                style={{ textDecoration: 'underline' }}
+                href="https://github.com/growilabs/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A"
+                target="_blank"
+                rel="noreferrer"
+              >
+                {t('admin:admin_top:submit_bug_report')}
+              </a>
+            </li>
+          </ol>
         </div>
       </div>
     </div>
   );
 };
-
-const AdminHomeWrapper = withUnstatedContainers(AdminHome, [
-  AdminHomeContainer,
-]);
-
-AdminHome.propTypes = {
-  adminHomeContainer: PropTypes.instanceOf(AdminHomeContainer).isRequired,
-};
-
-export default AdminHomeWrapper;

+ 5 - 20
apps/app/src/client/components/Admin/AdminHome/SystemInfomationTable.tsx

@@ -1,19 +1,12 @@
-import React from 'react';
 import { LoadingSpinner } from '@growi/ui/dist/components';
 
-import AdminHomeContainer from '~/client/services/AdminHomeContainer';
+import { useSWRxAdminHome } from '~/stores/admin/admin-home';
 
-import { withUnstatedContainers } from '../../UnstatedUtils';
-
-type Props = {
-  adminHomeContainer: AdminHomeContainer;
-};
-
-const SystemInformationTable = (props: Props) => {
-  const { adminHomeContainer } = props;
+const SystemInformationTable = () => {
+  const { data: adminHomeData } = useSWRxAdminHome();
 
   const { growiVersion, nodeVersion, npmVersion, pnpmVersion } =
-    adminHomeContainer.state;
+    adminHomeData ?? {};
 
   if (
     growiVersion == null ||
@@ -51,12 +44,4 @@ const SystemInformationTable = (props: Props) => {
   );
 };
 
-/**
- * Wrapper component for using unstated
- */
-const SystemInformationTableWrapper = withUnstatedContainers(
-  SystemInformationTable,
-  [AdminHomeContainer],
-);
-
-export default SystemInformationTableWrapper;
+export default SystemInformationTable;

+ 1 - 0
apps/app/src/client/components/Admin/AdminHome/index.ts

@@ -0,0 +1 @@
+export * from './AdminHome';

+ 0 - 110
apps/app/src/client/services/AdminHomeContainer.js

@@ -1,110 +0,0 @@
-import { isServer } from '@growi/core/dist/utils';
-import { Container } from 'unstated';
-
-import loggerFactory from '~/utils/logger';
-
-import { apiv3Get } from '../util/apiv3-client';
-
-const logger = loggerFactory('growi:services:AdminHomeContainer');
-
-/**
- * Service container for admin homepage (AdminHome.jsx)
- * @extends {Container} unstated Container
- */
-export default class AdminHomeContainer extends Container {
-  constructor() {
-    super();
-
-    if (isServer()) {
-      return;
-    }
-
-    this.copyStateValues = {
-      DEFAULT: 'default',
-      DONE: 'done',
-    };
-    this.timer = null;
-
-    this.state = {
-      growiVersion: null,
-      nodeVersion: null,
-      npmVersion: null,
-      pnpmVersion: null,
-      copyState: this.copyStateValues.DEFAULT,
-      installedPlugins: null,
-      isV5Compatible: null,
-      isMaintenanceMode: null,
-    };
-  }
-
-  /**
-   * Workaround for the mangling in production build to break constructor.name
-   */
-  static getClassName() {
-    return 'AdminHomeContainer';
-  }
-
-  componentWillUnmount() {
-    clearTimeout(this.timer);
-  }
-
-  /**
-   * retrieve admin home data
-   */
-  async retrieveAdminHomeData() {
-    try {
-      const response = await apiv3Get('/admin-home/');
-      const { adminHomeParams } = response.data;
-
-      this.setState((prevState) => ({
-        ...prevState,
-        growiVersion: adminHomeParams.growiVersion,
-        nodeVersion: adminHomeParams.nodeVersion,
-        npmVersion: adminHomeParams.npmVersion,
-        pnpmVersion: adminHomeParams.pnpmVersion,
-        envVars: adminHomeParams.envVars,
-        isV5Compatible: adminHomeParams.isV5Compatible,
-        isMaintenanceMode: adminHomeParams.isMaintenanceMode,
-      }));
-    } catch (err) {
-      logger.error(err);
-      throw new Error('Failed to retrive AdminHome data');
-    }
-  }
-
-  /**
-   * sets button text when copying system information
-   */
-  onCopyPrefilledHostInformation() {
-    this.setState((prevState) => ({
-      ...prevState,
-      copyState: this.copyStateValues.DONE,
-    }));
-
-    this.timer = setTimeout(() => {
-      this.setState((prevState) => ({
-        ...prevState,
-        copyState: this.copyStateValues.DEFAULT,
-      }));
-    }, 500);
-  }
-
-  /**
-   * generates prefilled host information as markdown
-   */
-  generatePrefilledHostInformationMarkdown() {
-    return `| item     | version |
-| ---      | --- |
-|OS        ||
-|GROWI     |${this.state.growiVersion}|
-|node.js   |${this.state.nodeVersion}|
-|npm       |${this.state.npmVersion}|
-|pnpm      |${this.state.pnpmVersion}|
-|Using Docker|yes/no|
-|Using [growi-docker-compose][growi-docker-compose]|yes/no|
-
-[growi-docker-compose]: https://github.com/growilabs/growi-docker-compose
-
-*(Accessing https://{GROWI_HOST}/admin helps you to fill in above versions)*`;
-  }
-}

+ 9 - 0
apps/app/src/interfaces/res/admin/admin-home.ts

@@ -0,0 +1,9 @@
+export type IResAdminHome = {
+  growiVersion: string;
+  nodeVersion: string;
+  npmVersion: string;
+  pnpmVersion: string;
+  envVars: Record<string, string>;
+  isV5Compatible: boolean;
+  isMaintenanceMode: boolean;
+};

+ 3 - 10
apps/app/src/pages/admin/index.page.tsx

@@ -14,8 +14,9 @@ import {
 } from './_shared';
 
 const AdminHome = dynamic(
-  // biome-ignore lint/style/noRestrictedImports: no-problem dynamic import
-  () => import('~/client/components/Admin/AdminHome/AdminHome'),
+  () =>
+    // biome-ignore lint/style/noRestrictedImports: no-problem dynamic import
+    import('~/client/components/Admin/AdminHome').then((mod) => mod.AdminHome),
   { ssr: false },
 );
 
@@ -43,14 +44,6 @@ const AdminHomepage: NextPageWithLayout<Props> = ({
 
 AdminHomepage.getLayout = createAdminPageLayout<Props>({
   title: (_p, t) => t('wiki_management_homepage'),
-  containerFactories: [
-    async () => {
-      const AdminHomeContainer =
-        // biome-ignore lint/style/noRestrictedImports: no-problem dynamic import
-        (await import('~/client/services/AdminHomeContainer')).default;
-      return new AdminHomeContainer();
-    },
-  ],
 });
 
 export const getServerSideProps: GetServerSideProps<Props> = async (

+ 3 - 2
apps/app/src/server/routes/apiv3/admin-home.ts

@@ -1,5 +1,6 @@
 import { SCOPE } from '@growi/core/dist/interfaces';
 
+import type { IResAdminHome } from '~/interfaces/res/admin/admin-home';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { configManager } from '~/server/service/config-manager';
 import { getGrowiVersion } from '~/utils/growi-version';
@@ -91,13 +92,13 @@ module.exports = (crowi) => {
     accessTokenParser([SCOPE.READ.ADMIN.TOP]),
     loginRequiredStrictly,
     adminRequired,
-    async (req, res) => {
+    async (_req, res) => {
       const { getRuntimeVersions } = await import(
         '~/server/util/runtime-versions'
       );
       const runtimeVersions = await getRuntimeVersions();
 
-      const adminHomeParams = {
+      const adminHomeParams: IResAdminHome = {
         growiVersion: getGrowiVersion(),
         nodeVersion: runtimeVersions.node ?? '-',
         npmVersion: runtimeVersions.npm ?? '-',

+ 13 - 0
apps/app/src/stores/admin/admin-home.tsx

@@ -0,0 +1,13 @@
+import type { SWRResponse } from 'swr';
+import useSWR from 'swr';
+
+import { apiv3Get } from '~/client/util/apiv3-client';
+import type { IResAdminHome } from '~/interfaces/res/admin/admin-home';
+
+export const useSWRxAdminHome = (): SWRResponse<IResAdminHome, Error> => {
+  return useSWR('/admin-home/', (endpoint) =>
+    apiv3Get(endpoint).then((response) => {
+      return response.data.adminHomeParams;
+    }),
+  );
+};

+ 31 - 0
apps/app/src/utils/admin-home.ts

@@ -0,0 +1,31 @@
+type SystemInfo = {
+  growiVersion?: string;
+  nodeVersion?: string;
+  npmVersion?: string;
+  pnpmVersion?: string;
+};
+
+/**
+ * Generates prefilled host information as markdown for bug reports
+ * @param systemInfo System version information
+ * @returns Markdown formatted string with system information
+ */
+export const generatePrefilledHostInformationMarkdown = (
+  systemInfo: SystemInfo,
+): string => {
+  const { growiVersion, nodeVersion, npmVersion, pnpmVersion } = systemInfo;
+
+  return `| item     | version |
+| ---      | --- |
+|OS        ||
+|GROWI     |${growiVersion ?? ''}|
+|node.js   |${nodeVersion ?? ''}|
+|npm       |${npmVersion ?? ''}|
+|pnpm      |${pnpmVersion ?? ''}|
+|Using Docker|yes/no|
+|Using [growi-docker-compose][growi-docker-compose]|yes/no|
+
+[growi-docker-compose]: https://github.com/growilabs/growi-docker-compose
+
+*(Accessing https://{GROWI_HOST}/admin helps you to fill in above versions)*`;
+};

+ 10 - 0
lefthook.yml

@@ -0,0 +1,10 @@
+# Lefthook configuration for GROWI
+# See https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md
+
+pre-commit:
+  parallel: true
+  commands:
+    biome-format:
+      glob: "*.{js,jsx,ts,tsx,json,jsonc}"
+      run: pnpm biome check --write --no-errors-on-unmatched --files-ignore-unknown=true {staged_files}
+      stage_fixed: true

+ 15 - 1
package.json

@@ -64,6 +64,7 @@
     "cross-env": "^7.0.0",
     "dotenv-flow": "^3.2.0",
     "glob": "^8.1.0",
+    "lefthook": "^2.0.13",
     "mock-require": "^3.0.3",
     "nodemon": "^3.1.3",
     "npm-run-all": "^4.1.5",
@@ -102,7 +103,20 @@
       "@lykmapipo/common>flat": "5.0.2",
       "@lykmapipo/common>mime": "3.0.0",
       "@lykmapipo/common>parse-json": "5.2.0"
-    }
+    },
+    "ignoredBuiltDependencies": [
+      "@swc/core",
+      "core-js",
+      "dtrace-provider",
+      "esbuild",
+      "leveldown",
+      "protobufjs",
+      "puppeteer",
+      "ttf2woff2"
+    ],
+    "onlyBuiltDependencies": [
+      "lefthook"
+    ]
   },
   "engines": {
     "node": "^18 || ^20"

+ 158 - 65
pnpm-lock.yaml

@@ -76,6 +76,9 @@ importers:
       glob:
         specifier: ^8.1.0
         version: 8.1.0
+      lefthook:
+        specifier: ^2.0.13
+        version: 2.0.13
       mock-require:
         specifier: ^3.0.3
         version: 3.0.3
@@ -10443,6 +10446,60 @@ packages:
     resolution: {integrity: sha512-1ky+WrN+4CFMuoekUOv7Y1037XWdjKpu0xAPwSP+9KdvmV9PG+qOKlssDV6a+U32apwxdD3is/BZcWOYzN30cg==}
     deprecated: This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md
 
+  lefthook-darwin-arm64@2.0.13:
+    resolution: {integrity: sha512-KbQqpNSNTugjtPzt97CNcy/XZy5asJ0+uSLoHc4ML8UCJdsXKYJGozJHNwAd0Xfci/rQlj82A7rPOuTdh0jY0Q==}
+    cpu: [arm64]
+    os: [darwin]
+
+  lefthook-darwin-x64@2.0.13:
+    resolution: {integrity: sha512-s/vI6sEE8/+rE6CONZzs59LxyuSc/KdU+/3adkNx+Q13R1+p/AvQNeszg3LAHzXmF3NqlxYf8jbj/z5vBzEpRw==}
+    cpu: [x64]
+    os: [darwin]
+
+  lefthook-freebsd-arm64@2.0.13:
+    resolution: {integrity: sha512-iQeJTU7Zl8EJlCMQxNZQpJFAQ9xl40pydUIv5SYnbJ4nqIr9ONuvrioNv6N2LtKP5aBl1nIWQQ9vMjgVyb3k+A==}
+    cpu: [arm64]
+    os: [freebsd]
+
+  lefthook-freebsd-x64@2.0.13:
+    resolution: {integrity: sha512-99cAXKRIzpq/u3obUXbOQJCHP+0ZkJbN3TF+1ZQZlRo3Y6+mPSCg9fh/oi6dgbtu4gTI5Ifz3o5p2KZzAIF9ZQ==}
+    cpu: [x64]
+    os: [freebsd]
+
+  lefthook-linux-arm64@2.0.13:
+    resolution: {integrity: sha512-RWarenY3kLy/DT4/8dY2bwDlYwlELRq9MIFq+FiMYmoBHES3ckWcLX2JMMlM49Y672paQc7MbneSrNUn/FQWhg==}
+    cpu: [arm64]
+    os: [linux]
+
+  lefthook-linux-x64@2.0.13:
+    resolution: {integrity: sha512-QZRcxXGf8Uj/75ITBqoBh0zWhJE7+uFoRxEHwBq0Qjv55Q4KcFm7FBN/IFQCSd14reY5pmY3kDaWVVy60cAGJA==}
+    cpu: [x64]
+    os: [linux]
+
+  lefthook-openbsd-arm64@2.0.13:
+    resolution: {integrity: sha512-LAuOWwnNmOlRE0RxKMOhIz5Kr9tXi0rCjzXtDARW9lvfAV6Br2wP+47q0rqQ8m/nVwBYoxfJ/RDunLbb86O1nA==}
+    cpu: [arm64]
+    os: [openbsd]
+
+  lefthook-openbsd-x64@2.0.13:
+    resolution: {integrity: sha512-n9TIN3QLncyxOHomiKKwzDFHKOCm5H28CVNAZFouKqDwEaUGCs5TJI88V85j4/CgmLVUU8uUn4ClVCxIWYG59w==}
+    cpu: [x64]
+    os: [openbsd]
+
+  lefthook-windows-arm64@2.0.13:
+    resolution: {integrity: sha512-sdSC4F9Di7y0t43Of9MOA5g/0CmvkM4juQ3sKfUhRcoygetLJn4PR2/pvuDOIaGf4mNMXBP5IrcKaeDON9HrcA==}
+    cpu: [arm64]
+    os: [win32]
+
+  lefthook-windows-x64@2.0.13:
+    resolution: {integrity: sha512-ccl1v7Fl10qYoghEtjXN+JC1x/y/zLM/NSHf3NFGeKEGBNd1P5d/j6w8zVmhfzi+ekS8whXrcNbRAkLdAqUrSw==}
+    cpu: [x64]
+    os: [win32]
+
+  lefthook@2.0.13:
+    resolution: {integrity: sha512-D39rCVl7/GpqakvhQvqz07SBpzUWTvWjXKnBZyIy8O6D+Lf9xD6tnbHtG5nWXd9iPvv1AKGQwL9R/e5rNtV6SQ==}
+    hasBin: true
+
   level-codec@9.0.2:
     resolution: {integrity: sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==}
     engines: {node: '>=6'}
@@ -16286,7 +16343,7 @@ snapshots:
       '@azure/core-util': 1.10.0
       '@azure/logger': 1.1.2
       http-proxy-agent: 7.0.2
-      https-proxy-agent: 7.0.6
+      https-proxy-agent: 7.0.6(supports-color@10.0.0)
       tslib: 2.8.1
     transitivePeerDependencies:
       - supports-color
@@ -16388,7 +16445,7 @@ snapshots:
       '@babel/traverse': 7.24.6
       '@babel/types': 7.25.6
       convert-source-map: 2.0.0
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -16559,7 +16616,7 @@ snapshots:
       '@babel/helper-split-export-declaration': 7.24.6
       '@babel/parser': 7.25.6
       '@babel/types': 7.25.6
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -17102,7 +17159,7 @@ snapshots:
 
   '@elastic/elasticsearch@7.17.13':
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       hpagent: 0.1.2
       ms: 2.1.3
       secure-json-parse: 2.7.0
@@ -17131,7 +17188,7 @@ snapshots:
     dependencies:
       '@opentelemetry/api': 1.9.0
       '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       hpagent: 1.2.0
       ms: 2.1.3
       secure-json-parse: 3.0.2
@@ -17144,7 +17201,7 @@ snapshots:
     dependencies:
       '@opentelemetry/api': 1.9.0
       '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       hpagent: 1.2.0
       ms: 2.1.3
       secure-json-parse: 4.0.0
@@ -17327,7 +17384,7 @@ snapshots:
   '@eslint/eslintrc@2.0.3':
     dependencies:
       ajv: 6.12.6
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       espree: 9.6.1
       globals: 13.24.0
       ignore: 5.3.1
@@ -17440,7 +17497,7 @@ snapshots:
   '@humanwhocodes/config-array@0.11.8':
     dependencies:
       '@humanwhocodes/object-schema': 1.2.1
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       minimatch: 3.1.2
     transitivePeerDependencies:
       - supports-color
@@ -17473,7 +17530,7 @@ snapshots:
       '@antfu/install-pkg': 1.1.0
       '@antfu/utils': 8.1.1
       '@iconify/types': 2.0.0
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       globals: 15.15.0
       kolorist: 1.8.0
       local-pkg: 1.1.1
@@ -18169,7 +18226,7 @@ snapshots:
     dependencies:
       agent-base: 7.1.4
       http-proxy-agent: 7.0.2
-      https-proxy-agent: 7.0.6
+      https-proxy-agent: 7.0.6(supports-color@10.0.0)
       lru-cache: 10.4.3
       socks-proxy-agent: 8.0.4
     transitivePeerDependencies:
@@ -18947,7 +19004,7 @@ snapshots:
       ajv: 8.17.1
       chalk: 4.1.2
       compare-versions: 6.1.1
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       esbuild: 0.24.0
       esutils: 2.0.3
       fs-extra: 11.2.0
@@ -19110,7 +19167,7 @@ snapshots:
 
   '@puppeteer/browsers@2.4.0':
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       extract-zip: 2.0.1
       progress: 2.0.3
       proxy-agent: 6.4.0
@@ -20150,7 +20207,7 @@ snapshots:
       '@swc-node/sourcemap-support': 0.5.1
       '@swc/core': 1.10.7(@swc/helpers@0.5.18)
       colorette: 2.0.20
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       oxc-resolver: 1.12.0
       pirates: 4.0.6
       tslib: 2.8.1
@@ -20165,7 +20222,7 @@ snapshots:
       '@swc-node/sourcemap-support': 0.5.1
       '@swc/core': 1.10.7(@swc/helpers@0.5.18)
       colorette: 2.0.20
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       oxc-resolver: 1.12.0
       pirates: 4.0.6
       tslib: 2.8.1
@@ -21430,7 +21487,7 @@ snapshots:
     dependencies:
       '@ampproject/remapping': 2.3.0
       '@bcoe/v8-coverage': 0.2.3
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       istanbul-lib-coverage: 3.2.2
       istanbul-lib-report: 3.0.1
       istanbul-lib-source-maps: 5.0.6
@@ -21699,7 +21756,7 @@ snapshots:
 
   agent-base@6.0.2:
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -22953,7 +23010,7 @@ snapshots:
 
   connect-mongo@4.6.0(express-session@1.18.0)(mongodb@4.17.2(@aws-sdk/client-sso-oidc@3.600.0)):
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       express-session: 1.18.0
       kruptein: 3.0.6
       mongodb: 4.17.2(@aws-sdk/client-sso-oidc@3.600.0)
@@ -23801,7 +23858,7 @@ snapshots:
   engine.io-client@6.6.4:
     dependencies:
       '@socket.io/component-emitter': 3.1.2
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       engine.io-parser: 5.2.3
       ws: 8.18.3
       xmlhttprequest-ssl: 2.1.2
@@ -23820,7 +23877,7 @@ snapshots:
       base64id: 2.0.0
       cookie: 0.7.2
       cors: 2.8.5
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       engine.io-parser: 5.2.3
       ws: 8.18.3
     transitivePeerDependencies:
@@ -24056,7 +24113,7 @@ snapshots:
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.3
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       doctrine: 3.0.0
       escape-string-regexp: 4.0.0
       eslint-scope: 7.2.0
@@ -24256,7 +24313,7 @@ snapshots:
 
   extract-zip@2.0.1:
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       get-stream: 5.2.0
       yauzl: 2.10.0
     optionalDependencies:
@@ -24441,7 +24498,7 @@ snapshots:
 
   follow-redirects@1.15.11(debug@4.4.3):
     optionalDependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
 
   for-each@0.3.3:
     dependencies:
@@ -24594,7 +24651,7 @@ snapshots:
   gaxios@6.7.1(encoding@0.1.13):
     dependencies:
       extend: 3.0.2
-      https-proxy-agent: 7.0.6
+      https-proxy-agent: 7.0.6(supports-color@10.0.0)
       is-stream: 2.0.0
       node-fetch: 2.7.0(encoding@0.1.13)
       uuid: 9.0.1
@@ -24674,7 +24731,7 @@ snapshots:
     dependencies:
       basic-ftp: 5.0.5
       data-uri-to-buffer: 6.0.2
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       fs-extra: 11.2.0
     transitivePeerDependencies:
       - supports-color
@@ -25202,14 +25259,14 @@ snapshots:
     dependencies:
       '@tootallnate/once': 2.0.0
       agent-base: 6.0.2
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
     transitivePeerDependencies:
       - supports-color
 
   http-proxy-agent@7.0.2:
     dependencies:
       agent-base: 7.1.4
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -25232,14 +25289,7 @@ snapshots:
   https-proxy-agent@5.0.1:
     dependencies:
       agent-base: 6.0.2
-      debug: 4.4.3(supports-color@5.5.0)
-    transitivePeerDependencies:
-      - supports-color
-
-  https-proxy-agent@7.0.6:
-    dependencies:
-      agent-base: 7.1.4
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -25688,7 +25738,7 @@ snapshots:
 
   istanbul-lib-source-maps@4.0.1:
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       istanbul-lib-coverage: 3.2.2
       source-map: 0.6.1
     transitivePeerDependencies:
@@ -25697,7 +25747,7 @@ snapshots:
   istanbul-lib-source-maps@5.0.6:
     dependencies:
       '@jridgewell/trace-mapping': 0.3.31
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       istanbul-lib-coverage: 3.2.2
     transitivePeerDependencies:
       - supports-color
@@ -26107,7 +26157,7 @@ snapshots:
       decimal.js: 10.6.0
       html-encoding-sniffer: 4.0.0
       http-proxy-agent: 7.0.2
-      https-proxy-agent: 7.0.6
+      https-proxy-agent: 7.0.6(supports-color@10.0.0)
       is-potential-custom-element-name: 1.0.1
       nwsapi: 2.2.22
       parse5: 7.3.0
@@ -26342,6 +26392,49 @@ snapshots:
       vasync: 2.2.1
       verror: 1.10.1
 
+  lefthook-darwin-arm64@2.0.13:
+    optional: true
+
+  lefthook-darwin-x64@2.0.13:
+    optional: true
+
+  lefthook-freebsd-arm64@2.0.13:
+    optional: true
+
+  lefthook-freebsd-x64@2.0.13:
+    optional: true
+
+  lefthook-linux-arm64@2.0.13:
+    optional: true
+
+  lefthook-linux-x64@2.0.13:
+    optional: true
+
+  lefthook-openbsd-arm64@2.0.13:
+    optional: true
+
+  lefthook-openbsd-x64@2.0.13:
+    optional: true
+
+  lefthook-windows-arm64@2.0.13:
+    optional: true
+
+  lefthook-windows-x64@2.0.13:
+    optional: true
+
+  lefthook@2.0.13:
+    optionalDependencies:
+      lefthook-darwin-arm64: 2.0.13
+      lefthook-darwin-x64: 2.0.13
+      lefthook-freebsd-arm64: 2.0.13
+      lefthook-freebsd-x64: 2.0.13
+      lefthook-linux-arm64: 2.0.13
+      lefthook-linux-x64: 2.0.13
+      lefthook-openbsd-arm64: 2.0.13
+      lefthook-openbsd-x64: 2.0.13
+      lefthook-windows-arm64: 2.0.13
+      lefthook-windows-x64: 2.0.13
+
   level-codec@9.0.2:
     dependencies:
       buffer: 5.7.1
@@ -27226,7 +27319,7 @@ snapshots:
   micromark@4.0.0:
     dependencies:
       '@types/debug': 4.1.7
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       decode-named-character-reference: 1.0.2
       devlop: 1.1.0
       micromark-core-commonmark: 2.0.1
@@ -27415,10 +27508,10 @@ snapshots:
     dependencies:
       async-mutex: 0.4.1
       camelcase: 6.3.0
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       find-cache-dir: 3.3.2
       follow-redirects: 1.15.11(debug@4.4.3)
-      https-proxy-agent: 7.0.6
+      https-proxy-agent: 7.0.6(supports-color@10.0.0)
       mongodb: 5.9.2(@aws-sdk/credential-providers@3.600.0(@aws-sdk/client-sso-oidc@3.600.0))
       new-find-package-json: 2.0.0
       semver: 7.6.3
@@ -27522,7 +27615,7 @@ snapshots:
 
   mquery@4.0.3:
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -27614,7 +27707,7 @@ snapshots:
 
   new-find-package-json@2.0.0:
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -28173,10 +28266,10 @@ snapshots:
     dependencies:
       '@tootallnate/quickjs-emscripten': 0.23.0
       agent-base: 7.1.4
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       get-uri: 6.0.3
       http-proxy-agent: 7.0.2
-      https-proxy-agent: 7.0.6
+      https-proxy-agent: 7.0.6(supports-color@10.0.0)
       pac-resolver: 7.0.1
       socks-proxy-agent: 8.0.4
     transitivePeerDependencies:
@@ -28305,7 +28398,7 @@ snapshots:
   passport-saml@3.2.4:
     dependencies:
       '@xmldom/xmldom': 0.7.13
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       passport-strategy: 1.0.0
       xml-crypto: 2.1.5
       xml-encryption: 2.0.0
@@ -28611,9 +28704,9 @@ snapshots:
   proxy-agent@6.4.0:
     dependencies:
       agent-base: 7.1.4
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       http-proxy-agent: 7.0.2
-      https-proxy-agent: 7.0.6
+      https-proxy-agent: 7.0.6(supports-color@10.0.0)
       lru-cache: 7.18.3
       pac-proxy-agent: 7.0.2
       proxy-from-env: 1.1.0
@@ -28659,7 +28752,7 @@ snapshots:
 
   puppeteer-cluster@0.24.0(puppeteer@23.6.1(typescript@5.4.2)):
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       puppeteer: 23.6.1(typescript@5.4.2)
     transitivePeerDependencies:
       - supports-color
@@ -28668,7 +28761,7 @@ snapshots:
     dependencies:
       '@puppeteer/browsers': 2.4.0
       chromium-bidi: 0.8.0(devtools-protocol@0.0.1354347)
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       devtools-protocol: 0.0.1354347
       typed-query-selector: 2.12.0
       ws: 8.18.3
@@ -29494,7 +29587,7 @@ snapshots:
 
   require-in-the-middle@7.4.0:
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       module-details-from-path: 1.0.3
       resolve: 1.22.8
     transitivePeerDependencies:
@@ -29544,7 +29637,7 @@ snapshots:
 
   retry-request@4.2.2:
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       extend: 3.0.2
     transitivePeerDependencies:
       - supports-color
@@ -30000,7 +30093,7 @@ snapshots:
 
   socket.io-adapter@2.5.6:
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       ws: 8.18.3
     transitivePeerDependencies:
       - bufferutil
@@ -30010,7 +30103,7 @@ snapshots:
   socket.io-client@4.8.3:
     dependencies:
       '@socket.io/component-emitter': 3.1.2
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       engine.io-client: 6.6.4
       socket.io-parser: 4.2.5
     transitivePeerDependencies:
@@ -30021,7 +30114,7 @@ snapshots:
   socket.io-parser@4.2.5:
     dependencies:
       '@socket.io/component-emitter': 3.1.2
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -30030,7 +30123,7 @@ snapshots:
       accepts: 1.3.8
       base64id: 2.0.0
       cors: 2.8.5
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       engine.io: 6.6.5
       socket.io-adapter: 2.5.6
       socket.io-parser: 4.2.5
@@ -30042,7 +30135,7 @@ snapshots:
   socks-proxy-agent@7.0.0:
     dependencies:
       agent-base: 6.0.2
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       socks: 2.8.3
     transitivePeerDependencies:
       - supports-color
@@ -30050,7 +30143,7 @@ snapshots:
   socks-proxy-agent@8.0.4:
     dependencies:
       agent-base: 7.1.4
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       socks: 2.8.3
     transitivePeerDependencies:
       - supports-color
@@ -30210,7 +30303,7 @@ snapshots:
   streamroller@3.1.5:
     dependencies:
       date-format: 4.0.14
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       fs-extra: 8.1.0
     transitivePeerDependencies:
       - supports-color
@@ -30404,7 +30497,7 @@ snapshots:
       cosmiconfig: 9.0.0(typescript@5.0.4)
       css-functions-list: 3.2.2
       css-tree: 2.3.1
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       fast-glob: 3.3.2
       fastest-levenshtein: 1.0.16
       file-entry-cache: 8.0.0
@@ -30459,7 +30552,7 @@ snapshots:
     dependencies:
       component-emitter: 1.3.1
       cookiejar: 2.1.4
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       fast-safe-stringify: 2.1.1
       form-data: 4.0.4
       formidable: 3.5.4
@@ -31071,7 +31164,7 @@ snapshots:
       buffer: 6.0.3
       chalk: 4.1.2
       cli-highlight: 2.1.11
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       dotenv: 8.6.0
       glob: 7.2.3
       js-yaml: 4.1.1
@@ -31478,7 +31571,7 @@ snapshots:
   vite-node@2.1.1(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1):
     dependencies:
       cac: 6.7.14
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       pathe: 1.1.2
       vite: 5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1)
     transitivePeerDependencies:
@@ -31497,7 +31590,7 @@ snapshots:
       '@microsoft/api-extractor': 7.43.0(@types/node@20.19.17)
       '@rollup/pluginutils': 5.2.0(rollup@4.39.0)
       '@vue/language-core': 1.8.27(typescript@5.0.4)
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       kolorist: 1.8.0
       magic-string: 0.30.11
       typescript: 5.0.4
@@ -31511,7 +31604,7 @@ snapshots:
 
   vite-tsconfig-paths@5.0.1(typescript@5.0.4)(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1)):
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       globrex: 0.1.2
       tsconfck: 3.0.3(typescript@5.0.4)
     optionalDependencies:
@@ -31547,7 +31640,7 @@ snapshots:
       '@vitest/spy': 2.1.1
       '@vitest/utils': 2.1.1
       chai: 5.1.1
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3(supports-color@10.0.0)
       magic-string: 0.30.11
       pathe: 1.1.2
       std-env: 3.7.0