From b0710722abe9b759318f5fbf5c3b67cf26c83192 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 17:25:35 +0330 Subject: [PATCH 01/81] chore: enforce formatting in CI --- .gitattributes | 11 +++++++++++ .github/workflows/ci.yml | 2 ++ 2 files changed, 13 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..530bbde --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto + +*.css text eol=lf +*.html text eol=lf +*.js text eol=lf +*.json text eol=lf +*.md text eol=lf +*.ts text eol=lf +*.tsx text eol=lf +*.yml text eol=lf +*.yaml text eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33e291a..45ed554 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,8 @@ jobs: - run: npm run lint + - run: npm run format:check + - run: npm run test:coverage - run: npm run knip From bf8f54154669ea44586dfbbcef88c0d9ae8adb25 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 17:28:15 +0330 Subject: [PATCH 02/81] chore: declare Node.js version --- .nvmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 From df9381a9d98e3e454899d7a8fceca9ef3ddb8425 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 17:29:11 +0330 Subject: [PATCH 03/81] docs: add repository contribution policies --- .github/CODEOWNERS | 7 +++++ CONTRIBUTING.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++ SECURITY.md | 36 ++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..cd1e4e2 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# Default reviewer for all repository changes. +* @sepaseh + +# Require explicit review for automation and dependency policy changes. +/.github/ @sepaseh +/package.json @sepaseh +/package-lock.json @sepaseh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..df92d37 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,64 @@ +# Contributing to Kernel + +Thank you for helping improve Kernel. Keep changes focused, tested, and easy to +review. + +## Development setup + +Kernel uses Node.js 22. If you use `nvm`, select the repository version before +installing dependencies: + +```bash +nvm use +npm ci +cp .env.example .env.local +npm run dev +``` + +On Windows, copy `.env.example` to `.env.local` using your preferred file +manager or shell. + +## Working on a change + +1. Create a branch from the latest `main`. +2. Keep each pull request limited to one coherent change. +3. Add or update tests for behavior changes. +4. Do not commit secrets, local environment files, generated reports, or build + output. +5. Update documentation when behavior, configuration, or public APIs change. + +Use clear commit messages that describe the intent of the change. The existing +history follows a Conventional Commits-style format such as `feat:`, `fix:`, +`test:`, `docs:`, and `chore:`. + +## Required checks + +Run the same core checks used by continuous integration before opening a pull +request: + +```bash +npm run typecheck +npm run lint +npm run format:check +npm run test:coverage +npm run knip +npm run build +npm run test:e2e +``` + +Use `npm run format` and `npm run lint:fix` to apply safe automatic fixes. + +## Pull requests + +A pull request should: + +- Explain the problem and the chosen solution. +- Link related issues when applicable. +- Describe testing performed and any known limitations. +- Include screenshots or recordings for visible interface changes. +- Pass all required checks. +- Avoid unrelated formatting or refactoring. + +At least one code-owner review is expected before merging. Security +vulnerabilities must follow [SECURITY.md](SECURITY.md) instead of being +disclosed in a public issue. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..7cf5f27 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,36 @@ +# Security Policy + +## Supported versions + +Kernel is currently maintained on the `main` branch. Security fixes are applied +to the latest code; older commits and forks are not separately supported. + +## Reporting a vulnerability + +Do not disclose suspected vulnerabilities in a public issue, discussion, pull +request, or commit. + +Use the repository's +[private vulnerability reporting](https://github.com/sepaseh/kernel/security/advisories/new) +page to provide: + +- A description of the vulnerability and its potential impact. +- Reproduction steps or a minimal proof of concept. +- Affected routes, components, versions, or commits. +- Any suggested mitigation, if known. + +If private vulnerability reporting is unavailable, contact the repository +owner privately through their GitHub profile and request a secure communication +channel. Do not include sensitive vulnerability details in the initial public +contact. + +Reports will be acknowledged as soon as practical. The maintainer will validate +the issue, determine severity and scope, coordinate a fix, and disclose it after +users have had a reasonable opportunity to update. Please avoid public +disclosure until that process is complete. + +## Security expectations + +Contributors must not commit credentials, tokens, private keys, production +data, or populated environment files. Use `.env.example` for documented +configuration and rotate any secret that may have been exposed. From 48cf1479b64e7d2b28a5a402064da40b86cc7c58 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 17:32:45 +0330 Subject: [PATCH 04/81] refactor: centralize API error messages --- src/api/instance.ts | 26 ++++++++++++++++---------- src/api/user.test.ts | 27 ++++++++++++++++++++++++++- src/forms/Role.tsx | 4 ++-- src/forms/User.tsx | 4 ++-- src/forms/UserPassword.tsx | 4 ++-- src/forms/UserRole.tsx | 4 ++-- src/forms/UserWorkspace.tsx | 4 ++-- src/locales/en.ts | 2 ++ src/locales/fa.ts | 2 ++ src/pages/Account.tsx | 4 ++-- src/pages/ForgotPass.tsx | 7 +++---- src/pages/Login.tsx | 4 ++-- src/pages/Register.tsx | 7 +++---- src/pages/Roles.tsx | 13 +++++-------- src/pages/Users.tsx | 19 +++++++------------ src/utils/error.test.ts | 19 +++++++++++++++++++ src/utils/error.ts | 4 ++++ src/utils/index.ts | 1 + 18 files changed, 102 insertions(+), 53 deletions(-) create mode 100644 src/utils/error.test.ts create mode 100644 src/utils/error.ts diff --git a/src/api/instance.ts b/src/api/instance.ts index 08419d4..ad2dd73 100644 --- a/src/api/instance.ts +++ b/src/api/instance.ts @@ -1,6 +1,7 @@ import axios, { AxiosRequestConfig, InternalAxiosRequestConfig } from "axios"; import { apiUrl } from "@/config"; +import { i18nInstance } from "@/i18n"; import { AccessTokenProps } from "@/types"; import { toCamelCase, toSnakeCase } from "@/utils"; @@ -73,7 +74,7 @@ api.interceptors.response.use( (response) => response, async (error) => { if ( - axios.isAxiosError<{ errors: Record; message: string }>( + axios.isAxiosError<{ cause: Record; message: string }>( error, ) ) { @@ -101,21 +102,26 @@ api.interceptors.response.use( } if (error.response) { - return Promise.reject( - new Error(error.response.data.message, { - cause: error.response.data.errors, - }), - ); + const { data } = error.response; + + if ( + typeof data !== "object" || + data === null || + typeof data.message !== "string" || + !data.message + ) { + return Promise.reject(new Error(i18nInstance.t("unexpectedError"))); + } + + return Promise.reject(new Error(data.message, { cause: data.cause })); } if (error.request) { - return Promise.reject( - new Error("Network error - please check your connection"), - ); + return Promise.reject(new Error(i18nInstance.t("networkError"))); } } - return Promise.reject(error); + return Promise.reject(new Error(i18nInstance.t("unexpectedError"))); }, ); diff --git a/src/api/user.test.ts b/src/api/user.test.ts index 3fd8a13..7b96bfd 100644 --- a/src/api/user.test.ts +++ b/src/api/user.test.ts @@ -1,10 +1,15 @@ import { http, HttpResponse } from "msw"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; +import { i18nInstance } from "@/i18n"; import { server } from "@/test/mocks/server"; import { fetchUsers } from "./user"; +vi.mock("@/i18n", () => ({ + i18nInstance: { t: (key: string) => key }, +})); + describe("users API", () => { it("serializes filters and converts the response to camel case", async () => { server.use( @@ -50,4 +55,24 @@ describe("users API", () => { total: 1, }); }); + + it("localizes network errors", async () => { + server.use(http.get("http://localhost/users", () => HttpResponse.error())); + + await expect(fetchUsers({})).rejects.toThrow( + i18nInstance.t("networkError"), + ); + }); + + it("localizes unexpected API responses", async () => { + server.use( + http.get("http://localhost/users", () => + HttpResponse.json(null, { status: 500 }), + ), + ); + + await expect(fetchUsers({})).rejects.toThrow( + i18nInstance.t("unexpectedError"), + ); + }); }); diff --git a/src/forms/Role.tsx b/src/forms/Role.tsx index e4fe2e5..58d1686 100644 --- a/src/forms/Role.tsx +++ b/src/forms/Role.tsx @@ -18,6 +18,7 @@ import { createRole, updateRole } from "@/api"; import { modalKeys } from "@/config"; import { useAntd, useGoBack } from "@/hooks"; import { PermissionGroupProps, RoleMutationParams, RoleProps } from "@/types"; +import { getErrorMessage } from "@/utils"; export const RoleForm: FC<{ data?: RoleProps; @@ -46,8 +47,7 @@ export const RoleForm: FC<{ goBack(); onFinish(); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } finally { setSubmitting(false); } diff --git a/src/forms/User.tsx b/src/forms/User.tsx index 58d5c79..a0a9348 100644 --- a/src/forms/User.tsx +++ b/src/forms/User.tsx @@ -9,6 +9,7 @@ import { DigitsInput } from "@/components/DigitsInput"; import { modalKeys } from "@/config"; import { useAntd, useGoBack } from "@/hooks"; import { CreateUserParams, UpdateUserParams, UserProps } from "@/types"; +import { getErrorMessage } from "@/utils"; type UserFormParams = CreateUserParams & { confirmPassword: string; @@ -51,8 +52,7 @@ export const UserForm: FC<{ data?: UserProps; onFinish: () => void }> = ({ goBack(); onFinish(); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } finally { setSubmitting(false); } diff --git a/src/forms/UserPassword.tsx b/src/forms/UserPassword.tsx index 98c68a6..6e6e2d9 100644 --- a/src/forms/UserPassword.tsx +++ b/src/forms/UserPassword.tsx @@ -8,6 +8,7 @@ import { updateUserPassword } from "@/api"; import { modalKeys } from "@/config"; import { useAntd, useGoBack } from "@/hooks"; import { UserPasswordParams, UserProps } from "@/types"; +import { getErrorMessage } from "@/utils"; type UserPasswordFormParams = UserPasswordParams & { confirmPassword: string; @@ -33,8 +34,7 @@ export const UserPasswordForm: FC<{ data?: UserProps }> = ({ data }) => { messageAPI.success(t("passwordUpdated")); goBack(); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } finally { setSubmitting(false); } diff --git a/src/forms/UserRole.tsx b/src/forms/UserRole.tsx index 646643f..2c69111 100644 --- a/src/forms/UserRole.tsx +++ b/src/forms/UserRole.tsx @@ -8,6 +8,7 @@ import { updateUserRoles } from "@/api"; import { modalKeys } from "@/config"; import { useAntd, useGoBack } from "@/hooks"; import { UserOptionProps, UserProps, UserRoleParams } from "@/types"; +import { getErrorMessage } from "@/utils"; type UserFormRoleProps = { data?: UserProps; @@ -40,8 +41,7 @@ export const UserFormRole: FC = ({ goBack(); onFinish(); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } finally { setSubmitting(false); } diff --git a/src/forms/UserWorkspace.tsx b/src/forms/UserWorkspace.tsx index 313c404..fa4d05d 100644 --- a/src/forms/UserWorkspace.tsx +++ b/src/forms/UserWorkspace.tsx @@ -8,6 +8,7 @@ import { updateUserWorkspaces } from "@/api"; import { modalKeys } from "@/config"; import { useAntd, useGoBack } from "@/hooks"; import { UserOptionProps, UserProps, UserWorkspaceParams } from "@/types"; +import { getErrorMessage } from "@/utils"; type UserWorkspaceFormProps = { data?: UserProps; @@ -40,8 +41,7 @@ export const UserWorkspaceForm: FC = ({ goBack(); onFinish(); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } finally { setSubmitting(false); } diff --git a/src/locales/en.ts b/src/locales/en.ts index bbe7bdc..be59445 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -30,6 +30,7 @@ export const en = { logout: "Logout", mobile: "Mobile", name: "Name", + networkError: "Network error. Please check your connection.", newPass: "New password", no: "No", notFoundDescription: "Sorry, the page you visited does not exist.", @@ -60,6 +61,7 @@ export const en = { "Are you sure you want to change system administrator access?", systemAdminUpdated: "System administrator access updated successfully.", update: "Update", + unexpectedError: "An unexpected error occurred.", userCreated: "User created successfully.", userDeleted: "User deleted successfully.", userUpdated: "User updated successfully.", diff --git a/src/locales/fa.ts b/src/locales/fa.ts index 6817102..2654782 100644 --- a/src/locales/fa.ts +++ b/src/locales/fa.ts @@ -40,6 +40,7 @@ export const fa = { login: "ورود به سیستم", logout: "خروج", name: "نام", + networkError: "خطای شبکه. لطفاً اتصال اینترنت خود را بررسی کنید.", newPass: "رمز عبور جدید", no: "خیر", passsMismatch: "تکرار رمز عبور با رمز عبور جدید مطابقت ندارد.", @@ -54,6 +55,7 @@ export const fa = { statusConfirm: "آیا از تغییر وضعیت مطمئن هستید؟", submit: "ثبت", update: "ویرایش", + unexpectedError: "خطای غیرمنتظره‌ای رخ داد.", username: "نام کاربری", users: "کاربران", yes: "بله", diff --git a/src/pages/Account.tsx b/src/pages/Account.tsx index 2c56948..87c4fcf 100644 --- a/src/pages/Account.tsx +++ b/src/pages/Account.tsx @@ -28,6 +28,7 @@ import { UpdateUsernameParams, VerifyEmailParams, } from "@/types"; +import { getErrorMessage } from "@/utils"; type PasswordFormParams = ChangePasswordParams & { confirmPassword: string; @@ -63,8 +64,7 @@ export const AccountPage = () => { if (!user) return null; const showError = (error: unknown) => { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); }; const handleProfileSubmit: FormProps["onFinish"] = diff --git a/src/pages/ForgotPass.tsx b/src/pages/ForgotPass.tsx index 29aa5be..5758ff4 100644 --- a/src/pages/ForgotPass.tsx +++ b/src/pages/ForgotPass.tsx @@ -9,6 +9,7 @@ import { Icon } from "@/components/Icon"; import { routeTree } from "@/config"; import { useAntd } from "@/hooks"; import { ForgotPasswordParams } from "@/types"; +import { getErrorMessage } from "@/utils"; type ForgotPasswordFormParams = ForgotPasswordParams & { confirmPassword: string; @@ -50,8 +51,7 @@ export const ForgotPassPage = () => { setRemainingSeconds(response.remainingSeconds); messageAPI.success(t("otpSent")); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } finally { setRequestingOtp(false); } @@ -72,8 +72,7 @@ export const ForgotPassPage = () => { messageAPI.success(t("passwordReset")); navigate(routeTree.auth.path, { replace: true }); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } finally { setSubmitting(false); } diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index b60b1c5..e8a8195 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -8,6 +8,7 @@ import { Icon } from "@/components/Icon"; import { routeTree } from "@/config"; import { useAntd } from "@/hooks"; import { LoginParams } from "@/types"; +import { getErrorMessage } from "@/utils"; export const LoginPage = () => { const { t } = useTranslation(); @@ -26,8 +27,7 @@ export const LoginPage = () => { navigate(routeTree.root.path, { replace: true }); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } finally { setSubmitting(false); } diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index 48c04d6..c953bb1 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -9,6 +9,7 @@ import { Icon } from "@/components/Icon"; import { routeTree } from "@/config"; import { useAntd } from "@/hooks"; import { RegisterParams } from "@/types"; +import { getErrorMessage } from "@/utils"; type RegisterFormParams = RegisterParams & { confirmPassword: string; @@ -47,8 +48,7 @@ export const RegisterPage = () => { setRemainingSeconds(response.remainingSeconds); messageAPI.success(t("otpSent")); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } finally { setRequestingOtp(false); } @@ -70,8 +70,7 @@ export const RegisterPage = () => { navigate(routeTree.root.path, { replace: true }); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } finally { setSubmitting(false); } diff --git a/src/pages/Roles.tsx b/src/pages/Roles.tsx index af85320..22d2e76 100644 --- a/src/pages/Roles.tsx +++ b/src/pages/Roles.tsx @@ -19,6 +19,7 @@ import { modalKeys } from "@/config"; import { RoleForm } from "@/forms/Role"; import { useActionPermissions, useAntd } from "@/hooks"; import { PermissionGroupProps, RoleProps } from "@/types"; +import { getErrorMessage } from "@/utils"; export const RolesPage = () => { const { t } = useTranslation(); @@ -38,8 +39,7 @@ export const RolesPage = () => { setLoading(true); setData(await fetchRoles()); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } finally { setLoading(false); } @@ -51,8 +51,7 @@ export const RolesPage = () => { setSelectedData(await fetchRole(id)); navigate({ hash: modalKeys.update, pathname, search }, { state: true }); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } finally { setLoading(false); } @@ -69,8 +68,7 @@ export const RolesPage = () => { messageAPI.success(t("roleDeleted")); await fetchData(); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } }, title: t("deleteRoleConfirm"), @@ -137,8 +135,7 @@ export const RolesPage = () => { try { setPermissions(await fetchPermissions()); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } })(); }, [canCreateRoles, canUpdateRoles, messageAPI]); diff --git a/src/pages/Users.tsx b/src/pages/Users.tsx index 01a8498..fba2c7e 100644 --- a/src/pages/Users.tsx +++ b/src/pages/Users.tsx @@ -47,6 +47,7 @@ import { UserProps, UserSummaryProps, } from "@/types"; +import { getErrorMessage } from "@/utils"; export const UsersPage = () => { const { t } = useTranslation(); @@ -86,8 +87,7 @@ export const UsersPage = () => { setData(response.items); setTotal(response.total); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } finally { setLoading(false); } @@ -100,8 +100,7 @@ export const UsersPage = () => { setSelectedData(details); navigate({ hash, pathname, search }, { state: true }); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } finally { setLoading(false); } @@ -119,8 +118,7 @@ export const UsersPage = () => { messageAPI.success(t("statusUpdated")); await fetchData(); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } }, title: t("statusConfirm"), @@ -139,8 +137,7 @@ export const UsersPage = () => { messageAPI.success(t("systemAdminUpdated")); await fetchData(); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } }, title: t("systemAdminConfirm"), @@ -158,8 +155,7 @@ export const UsersPage = () => { messageAPI.success(t("userDeleted")); await fetchData(); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } }, title: t("deleteUserConfirm"), @@ -324,8 +320,7 @@ export const UsersPage = () => { setRoles(roleOptions); setWorkspaces(workspaceOptions); } catch (error) { - if (error instanceof Error) messageAPI.error(error.message); - else console.error(error); + messageAPI.error(getErrorMessage(error)); } })(); }, [canUpdateUsers, messageAPI]); diff --git a/src/utils/error.test.ts b/src/utils/error.test.ts new file mode 100644 index 0000000..be4d09e --- /dev/null +++ b/src/utils/error.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it, vi } from "vitest"; + +import { getErrorMessage } from "./error"; + +vi.mock("@/i18n", () => ({ + i18nInstance: { t: (key: string) => key }, +})); + +describe("getErrorMessage", () => { + it("returns the message from Error instances", () => { + expect(getErrorMessage(new Error("Request failed"))).toBe("Request failed"); + }); + + it("returns the localized fallback for other thrown values", () => { + expect(getErrorMessage({ message: "unsafe value" })).toBe( + "unexpectedError", + ); + }); +}); diff --git a/src/utils/error.ts b/src/utils/error.ts new file mode 100644 index 0000000..8b39a83 --- /dev/null +++ b/src/utils/error.ts @@ -0,0 +1,4 @@ +import { i18nInstance } from "@/i18n"; + +export const getErrorMessage = (error: unknown): string => + error instanceof Error ? error.message : i18nInstance.t("unexpectedError"); diff --git a/src/utils/index.ts b/src/utils/index.ts index 6aa37dc..72bc338 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1 +1,2 @@ +export * from "./error"; export * from "./transform"; From 4e719968cc44da297c6adc4d5d73b9d2036f03cf Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 18:48:56 +0330 Subject: [PATCH 05/81] build: validate production environment --- .github/workflows/ci.yml | 6 ++++++ docs/deployment.md | 7 ++++++- docs/development.md | 6 +++++- package.json | 2 +- scripts/validate-production-env.mjs | 28 ++++++++++++++++++++++++++++ 5 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 scripts/validate-production-env.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45ed554..80c1a96 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,9 @@ jobs: build: needs: check runs-on: ubuntu-latest + env: + VITE_API_BASE_URL: https://api.example.com + VITE_APP_BASE_URL: / steps: - uses: actions/checkout@v4 @@ -67,6 +70,9 @@ jobs: e2e: needs: check runs-on: ubuntu-latest + env: + VITE_API_BASE_URL: https://api.example.com + VITE_APP_BASE_URL: / steps: - uses: actions/checkout@v4 diff --git a/docs/deployment.md b/docs/deployment.md index 17158e7..640cc5f 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -20,7 +20,8 @@ npm run preview ## Environment Variables -Vite reads environment variables at build time. Make sure production values are present before building the image or static bundle. +Vite reads environment variables at build time. Production builds fail when +either required value is missing or invalid. Required values usually include: @@ -35,6 +36,10 @@ VITE_APP_BASE_URL=/ VITE_APP_BASE_URL=/app/ ``` +`VITE_API_BASE_URL` must be an absolute HTTPS URL. `VITE_APP_BASE_URL` must +start and end with `/`; use `/` when the application is served at the domain +root. + ## Docker Build the image: diff --git a/docs/development.md b/docs/development.md index 08591a6..f1168a8 100644 --- a/docs/development.md +++ b/docs/development.md @@ -4,7 +4,7 @@ This project is a Vite React application written in TypeScript. ## Prerequisites -- Node.js compatible with the versions required by the dependencies in `package-lock.json` +- Node.js 22 - npm - Access to the backend API @@ -57,6 +57,10 @@ Build the app: npm run build ``` +Production builds require `VITE_API_BASE_URL` and `VITE_APP_BASE_URL` from +`.env.local` or the build environment. See [Deployment](deployment.md) for +their validation rules. + Check unused files, exports, and dependencies: ```bash diff --git a/package.json b/package.json index 272e478..97b3726 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite --host", - "build": "tsc -b && vite build", + "build": "node scripts/validate-production-env.mjs && tsc -b && vite build", "format": "prettier . --write", "format:check": "prettier . --check", "lint": "eslint . --ext .ts,.tsx", diff --git a/scripts/validate-production-env.mjs b/scripts/validate-production-env.mjs new file mode 100644 index 0000000..743eafd --- /dev/null +++ b/scripts/validate-production-env.mjs @@ -0,0 +1,28 @@ +import process from "node:process"; +import { URL } from "node:url"; + +import { loadEnv } from "vite"; + +const env = loadEnv("production", process.cwd(), ""); +const apiUrl = env.VITE_API_BASE_URL; +const appBaseUrl = env.VITE_APP_BASE_URL; + +if (!apiUrl) { + throw new Error("VITE_API_BASE_URL is required for production builds"); +} + +let parsedApiUrl; + +try { + parsedApiUrl = new URL(apiUrl); +} catch { + throw new Error("VITE_API_BASE_URL must be a valid absolute URL"); +} + +if (parsedApiUrl.protocol !== "https:") { + throw new Error("VITE_API_BASE_URL must use HTTPS in production"); +} + +if (!appBaseUrl || !appBaseUrl.startsWith("/") || !appBaseUrl.endsWith("/")) { + throw new Error("VITE_APP_BASE_URL must start and end with / in production"); +} From 779452cf8cc61092da80e59f76bb9055bedc3b34 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 18:52:30 +0330 Subject: [PATCH 06/81] chore: enforce dependency security audit --- .github/workflows/ci.yml | 4 +- .nvmrc | 2 +- CONTRIBUTING.md | 5 ++- README.md | 1 + docs/development.md | 2 +- package-lock.json | 71 +++++++++++++++--------------------- package.json | 6 ++- src/Routes.tsx | 7 +--- src/forms/Role.tsx | 2 +- src/forms/User.tsx | 2 +- src/forms/UserPassword.tsx | 2 +- src/forms/UserRole.tsx | 2 +- src/forms/UserWorkspace.tsx | 2 +- src/hooks/useFilterParams.ts | 2 +- src/hooks/useGoBack.ts | 7 +--- src/layouts/Auth.tsx | 2 +- src/layouts/Default.tsx | 2 +- src/pages/ForgotPass.tsx | 2 +- src/pages/Login.tsx | 2 +- src/pages/Register.tsx | 2 +- src/pages/Roles.tsx | 2 +- src/pages/Users.tsx | 2 +- 22 files changed, 59 insertions(+), 72 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80c1a96..c6775d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ permissions: contents: read env: - NODE_VERSION: 22 + NODE_VERSION: 22.22.0 jobs: check: @@ -33,6 +33,8 @@ jobs: - run: npm ci + - run: npm run audit + - run: npm run typecheck - run: npm run lint diff --git a/.nvmrc b/.nvmrc index 2bd5a0a..85e5027 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22 +22.22.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index df92d37..a9346fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,8 +5,8 @@ review. ## Development setup -Kernel uses Node.js 22. If you use `nvm`, select the repository version before -installing dependencies: +Kernel uses Node.js 22.22.0 or newer. If you use `nvm`, select the repository +version before installing dependencies: ```bash nvm use @@ -38,6 +38,7 @@ request: ```bash npm run typecheck +npm run audit npm run lint npm run format:check npm run test:coverage diff --git a/README.md b/README.md index bbb8938..60aa9ef 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ npm run build # typecheck and build for production npm run preview # preview production build npm run lint # run ESLint npm run lint:fix # run ESLint with auto-fix +npm run audit # check dependencies for high-severity vulnerabilities npm run typecheck # typecheck only npm run knip # detect unused files, exports, and dependencies ``` diff --git a/docs/development.md b/docs/development.md index f1168a8..0572a3b 100644 --- a/docs/development.md +++ b/docs/development.md @@ -4,7 +4,7 @@ This project is a Vite React application written in TypeScript. ## Prerequisites -- Node.js 22 +- Node.js 22.22.0 or newer - npm - Access to the backend API diff --git a/package-lock.json b/package-lock.json index dd42f7e..4cbbb1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "react": "^19.2.7", "react-dom": "^19.2.7", "react-i18next": "^17.0.8", - "react-router-dom": "^7.18.0" + "react-router": "^8.3.0" }, "devDependencies": { "@eslint/js": "^10.0.1", @@ -48,6 +48,9 @@ "typescript-eslint": "^8.62.0", "vite": "^8.1.0", "vitest": "^4.1.10" + }, + "engines": { + "node": ">=22.22.0" } }, "node_modules/@adobe/css-tools": { @@ -3743,16 +3746,16 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/browserslist": { @@ -3925,6 +3928,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -3934,6 +3938,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/cookie-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-3.1.1.tgz", + "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==", + "license": "MIT" + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -5774,9 +5784,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "dev": true, "funding": [ { @@ -6108,9 +6118,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", "dev": true, "funding": [ { @@ -6128,7 +6138,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -6258,20 +6268,19 @@ "license": "MIT" }, "node_modules/react-router": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.0.tgz", - "integrity": "sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-8.3.0.tgz", + "integrity": "sha512-qyPMvW83jGIct3yiieisxdk9M745anqhpIMKN5m1t6yBMfgVPpt77aHOqs5fUlEJRMCGffg9BaQLH9oPVOL7xQ==", "license": "MIT", "dependencies": { - "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0" + "cookie-es": "^3.1.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.22.0" }, "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" + "react": ">=19.2.7", + "react-dom": ">=19.2.7" }, "peerDependenciesMeta": { "react-dom": { @@ -6279,22 +6288,6 @@ } } }, - "node_modules/react-router-dom": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.0.tgz", - "integrity": "sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA==", - "license": "MIT", - "dependencies": { - "react-router": "7.18.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - } - }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -6448,12 +6441,6 @@ "semver": "bin/semver.js" } }, - "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", - "license": "MIT" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index 97b3726..32e0369 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,11 @@ "private": true, "version": "0.0.0", "type": "module", + "engines": { + "node": ">=22.22.0" + }, "scripts": { + "audit": "npm audit --audit-level=high", "dev": "vite --host", "build": "node scripts/validate-production-env.mjs && tsc -b && vite build", "format": "prettier . --write", @@ -31,7 +35,7 @@ "react": "^19.2.7", "react-dom": "^19.2.7", "react-i18next": "^17.0.8", - "react-router-dom": "^7.18.0" + "react-router": "^8.3.0" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/src/Routes.tsx b/src/Routes.tsx index 0f6b648..30ac136 100644 --- a/src/Routes.tsx +++ b/src/Routes.tsx @@ -1,9 +1,6 @@ import { FC, ReactNode, useEffect } from "react"; -import { - createBrowserRouter, - Navigate, - RouterProvider, -} from "react-router-dom"; +import { createBrowserRouter, Navigate } from "react-router"; +import { RouterProvider } from "react-router/dom"; import { baseUrl, RouteKey, routeTree } from "@/config"; import { useAllowedRoutes, useCore } from "@/hooks"; diff --git a/src/forms/Role.tsx b/src/forms/Role.tsx index 58d1686..e8d77d0 100644 --- a/src/forms/Role.tsx +++ b/src/forms/Role.tsx @@ -12,7 +12,7 @@ import { } from "antd"; import { FC, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useLocation } from "react-router-dom"; +import { useLocation } from "react-router"; import { createRole, updateRole } from "@/api"; import { modalKeys } from "@/config"; diff --git a/src/forms/User.tsx b/src/forms/User.tsx index a0a9348..d52970f 100644 --- a/src/forms/User.tsx +++ b/src/forms/User.tsx @@ -2,7 +2,7 @@ import { Button, Drawer, Form, FormProps, Input, Space } from "antd"; import { FC, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useLocation } from "react-router-dom"; +import { useLocation } from "react-router"; import { createUser, updateUser } from "@/api"; import { DigitsInput } from "@/components/DigitsInput"; diff --git a/src/forms/UserPassword.tsx b/src/forms/UserPassword.tsx index 6e6e2d9..0c49c02 100644 --- a/src/forms/UserPassword.tsx +++ b/src/forms/UserPassword.tsx @@ -2,7 +2,7 @@ import { Button, Drawer, Form, FormProps, Input, Space } from "antd"; import { FC, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useLocation } from "react-router-dom"; +import { useLocation } from "react-router"; import { updateUserPassword } from "@/api"; import { modalKeys } from "@/config"; diff --git a/src/forms/UserRole.tsx b/src/forms/UserRole.tsx index 2c69111..110e1cf 100644 --- a/src/forms/UserRole.tsx +++ b/src/forms/UserRole.tsx @@ -2,7 +2,7 @@ import { Button, Drawer, Form, FormProps, Select, Space } from "antd"; import { FC, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useLocation } from "react-router-dom"; +import { useLocation } from "react-router"; import { updateUserRoles } from "@/api"; import { modalKeys } from "@/config"; diff --git a/src/forms/UserWorkspace.tsx b/src/forms/UserWorkspace.tsx index fa4d05d..cbac86b 100644 --- a/src/forms/UserWorkspace.tsx +++ b/src/forms/UserWorkspace.tsx @@ -2,7 +2,7 @@ import { Button, Drawer, Form, FormProps, Select, Space } from "antd"; import { FC, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useLocation } from "react-router-dom"; +import { useLocation } from "react-router"; import { updateUserWorkspaces } from "@/api"; import { modalKeys } from "@/config"; diff --git a/src/hooks/useFilterParams.ts b/src/hooks/useFilterParams.ts index 493b3c4..56bf2d1 100644 --- a/src/hooks/useFilterParams.ts +++ b/src/hooks/useFilterParams.ts @@ -1,5 +1,5 @@ import { useCallback, useMemo } from "react"; -import { useSearchParams } from "react-router-dom"; +import { useSearchParams } from "react-router"; export const useFilterParams = >() => { const [searchParams, setSearchParams] = useSearchParams(); diff --git a/src/hooks/useGoBack.ts b/src/hooks/useGoBack.ts index a43eb52..04eae3b 100644 --- a/src/hooks/useGoBack.ts +++ b/src/hooks/useGoBack.ts @@ -1,10 +1,5 @@ import { useCallback } from "react"; -import { - NavigateOptions, - To, - useLocation, - useNavigate, -} from "react-router-dom"; +import { NavigateOptions, To, useLocation, useNavigate } from "react-router"; export const useGoBack = (): ((to?: To, options?: NavigateOptions) => void) => { const { pathname, state } = useLocation(); diff --git a/src/layouts/Auth.tsx b/src/layouts/Auth.tsx index e80941b..ca13a2f 100644 --- a/src/layouts/Auth.tsx +++ b/src/layouts/Auth.tsx @@ -1,7 +1,7 @@ import { Flex } from "antd"; import { useAntdToken } from "antd-style"; import { useEffect } from "react"; -import { Outlet } from "react-router-dom"; +import { Outlet } from "react-router"; import { clearAccessToken } from "@/api/token"; import { useCore } from "@/hooks"; diff --git a/src/layouts/Default.tsx b/src/layouts/Default.tsx index d055b41..8509cd6 100644 --- a/src/layouts/Default.tsx +++ b/src/layouts/Default.tsx @@ -13,7 +13,7 @@ import { import { useAntdToken } from "antd-style"; import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Link, Outlet, useNavigate } from "react-router-dom"; +import { Link, Outlet, useNavigate } from "react-router"; import { getAccount, logout, setUnauthorizedHandler } from "@/api"; import { clearAccessToken } from "@/api/token"; diff --git a/src/pages/ForgotPass.tsx b/src/pages/ForgotPass.tsx index 5758ff4..c078f90 100644 --- a/src/pages/ForgotPass.tsx +++ b/src/pages/ForgotPass.tsx @@ -1,7 +1,7 @@ import { Button, Flex, Form, FormProps, Input, Typography } from "antd"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Link, useNavigate } from "react-router-dom"; +import { Link, useNavigate } from "react-router"; import { forgotPassword, requestOtp } from "@/api"; import { DigitsInput } from "@/components/DigitsInput"; diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index e8a8195..71f3e78 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,7 +1,7 @@ import { Button, Flex, Form, FormProps, Input, Typography } from "antd"; import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { Link, useNavigate } from "react-router-dom"; +import { Link, useNavigate } from "react-router"; import { login } from "@/api"; import { Icon } from "@/components/Icon"; diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index c953bb1..7349392 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -1,7 +1,7 @@ import { Button, Flex, Form, FormProps, Input, Typography } from "antd"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Link, useNavigate } from "react-router-dom"; +import { Link, useNavigate } from "react-router"; import { register, requestOtp } from "@/api"; import { DigitsInput } from "@/components/DigitsInput"; diff --git a/src/pages/Roles.tsx b/src/pages/Roles.tsx index 22d2e76..f064f9c 100644 --- a/src/pages/Roles.tsx +++ b/src/pages/Roles.tsx @@ -11,7 +11,7 @@ import { import { useAntdToken } from "antd-style"; import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useLocation, useNavigate } from "react-router-dom"; +import { useLocation, useNavigate } from "react-router"; import { deleteRole, fetchPermissions, fetchRole, fetchRoles } from "@/api"; import { Icon } from "@/components/Icon"; diff --git a/src/pages/Users.tsx b/src/pages/Users.tsx index fba2c7e..e3122d3 100644 --- a/src/pages/Users.tsx +++ b/src/pages/Users.tsx @@ -17,7 +17,7 @@ import { useAntdToken } from "antd-style"; import { debounce } from "lodash"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useLocation, useNavigate } from "react-router-dom"; +import { useLocation, useNavigate } from "react-router"; import { deleteUser, From 9036ee0899bc262bd904d634b8ba05593747e275 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 19:30:11 +0330 Subject: [PATCH 07/81] chore: update dependencies and Node runtime --- .github/workflows/ci.yml | 2 +- .nvmrc | 2 +- CONTRIBUTING.md | 4 +- docs/development.md | 2 +- package-lock.json | 1185 +++++++++++++++++++------------------- package.json | 34 +- 6 files changed, 629 insertions(+), 600 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6775d0..cda8199 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ permissions: contents: read env: - NODE_VERSION: 22.22.0 + NODE_VERSION: 24.18.0 jobs: check: diff --git a/.nvmrc b/.nvmrc index 85e5027..ca5c350 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.22.0 +24.18.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a9346fc..676dd55 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,8 +5,8 @@ review. ## Development setup -Kernel uses Node.js 22.22.0 or newer. If you use `nvm`, select the repository -version before installing dependencies: +Kernel uses Node.js 24.15.0 or newer in the Node.js 24 LTS line. If you use +`nvm`, select the repository version before installing dependencies: ```bash nvm use diff --git a/docs/development.md b/docs/development.md index 0572a3b..1729e60 100644 --- a/docs/development.md +++ b/docs/development.md @@ -4,7 +4,7 @@ This project is a Vite React application written in TypeScript. ## Prerequisites -- Node.js 22.22.0 or newer +- Node.js 24.15.0 or newer in the Node.js 24 LTS line - npm - Access to the backend API diff --git a/package-lock.json b/package-lock.json index 4cbbb1c..333218f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,16 +8,16 @@ "name": "kernel", "version": "0.0.0", "dependencies": { - "antd": "^6.5.0", + "antd": "^6.5.2", "antd-style": "^4.1.0", - "axios": "^1.18.1", + "axios": "^1.19.0", "dayjs": "^1.11.21", - "i18next": "^26.3.3", + "i18next": "^26.3.6", "jalaliday": "^3.1.1", "lodash": "^4.18.1", - "react": "^19.2.7", - "react-dom": "^19.2.7", - "react-i18next": "^17.0.8", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "react-i18next": "^17.0.11", "react-router": "^8.3.0" }, "devDependencies": { @@ -28,29 +28,29 @@ "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", "@types/lodash": "^4.17.24", - "@types/node": "^26.0.1", + "@types/node": "^24.13.3", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", - "@typescript-eslint/types": "^8.62.0", + "@typescript-eslint/types": "^8.65.0", "@vitejs/plugin-basic-ssl": "^2.3.0", - "@vitejs/plugin-react": "^6.0.3", + "@vitejs/plugin-react": "^6.0.5", "@vitest/coverage-v8": "^4.1.10", - "eslint": "^10.6.0", + "eslint": "^10.8.0", "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.3", - "eslint-plugin-simple-import-sort": "^13.0.0", - "globals": "^17.7.0", - "jsdom": "^29.1.1", - "knip": "^6.22.0", + "eslint-plugin-simple-import-sort": "^14.0.0", + "globals": "^17.8.0", + "jsdom": "^30.0.1", + "knip": "^6.29.0", "msw": "^2.15.0", "prettier": "^3.9.6", "typescript": "~6.0.3", - "typescript-eslint": "^8.62.0", - "vite": "^8.1.0", + "typescript-eslint": "^8.65.0", + "vite": "^8.2.0", "vitest": "^4.1.10" }, "engines": { - "node": ">=22.22.0" + "node": ">=24.15.0 <25 || >=26.0.0" } }, "node_modules/@adobe/css-tools": { @@ -154,56 +154,58 @@ } }, "node_modules/@asamuzakjp/css-color": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", - "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.5.tgz", + "integrity": "sha512-mbhpPMmnw/kwW19aRNmSUl1QzLbdGo1SCuE49BT98MNwqF6zaHb3o2owssFc/PEO/4t2UjqtCNwocuDtJornzA==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/generational-cache": "^1.0.1", - "@csstools/css-calc": "^3.2.0", - "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-calc": "^3.2.1", + "@csstools/css-color-parser": "^4.1.9", "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.5.2" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", - "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-8.3.0.tgz", + "integrity": "sha512-UJLfKXBhrc8i1vH2eJXuYQMwlsLKWFw3O+CPqXSuVEiikeAim3UgrfWX0k4tA/X8cRFM8iZ7OaqBokFGbYusdg==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/generational-cache": "^1.0.1", - "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.2.1", - "is-potential-custom-element-name": "^1.0.1" + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^22.13.0 || >=24.0.0" } }, - "node_modules/@asamuzakjp/generational-cache": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", - "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "20 || >=22" } }, - "node_modules/@asamuzakjp/nwsapi": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", - "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -607,9 +609,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", "dev": true, "license": "MIT", "optional": true, @@ -619,9 +621,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", "dev": true, "license": "MIT", "optional": true, @@ -861,9 +863,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", - "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1174,22 +1176,25 @@ "license": "MIT" }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.1.tgz", + "integrity": "sha512-KjZdi8Q1wh89gsVmghvbrMgWl6ZWmRmHV6wjB7/g4Zf0dyO+hH3neZUtuDNPO00qq5YE5RITVWvrIZKRaAmzGQ==", "dev": true, "license": "MIT", "optional": true, "dependencies": { "@tybys/wasm-util": "^0.10.3" }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" } }, "node_modules/@open-draft/deferred-promise": { @@ -1218,9 +1223,9 @@ "license": "MIT" }, "node_modules/@oxc-parser/binding-android-arm-eabi": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.137.0.tgz", - "integrity": "sha512-KDs+0VPdEmasOkpuJHW9V5WCF+cvYdMQv2Jd+aJXt+cxIx12NToRQRbXaRwUEDsZw+/jMk81Ve8ZFbjUkJTOwA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.140.0.tgz", + "integrity": "sha512-ZfjDZ422mo7eo3b3VltqNsV9kmv1qt/sPEAMSl64iOSwhVfd0eIZ9LB79Mbs1xYXJnk7WSROwzBCKDIiVxPTvQ==", "cpu": [ "arm" ], @@ -1235,9 +1240,9 @@ } }, "node_modules/@oxc-parser/binding-android-arm64": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.137.0.tgz", - "integrity": "sha512-WhALNzfy3x/RfC6bsqX+csavuUY0yHHE7XfgPE5M542uhoBZUUoGTPG+nkMbGoG4+gcfss5s7urMyn5QBHu0sw==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.140.0.tgz", + "integrity": "sha512-Ia8jSvikUX6Sf+Ht+KOCUF/k1HpR0VlmqIYymubmWDebOEGtsyliHDR6JxsZ4IX3/c/GbrB1uh09aVGQv/LQmQ==", "cpu": [ "arm64" ], @@ -1252,9 +1257,9 @@ } }, "node_modules/@oxc-parser/binding-darwin-arm64": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.137.0.tgz", - "integrity": "sha512-bFPr5hgmNMOMoyPTGtdsK4Ug21RovIPojRMgDDhSp1LtCnc/DkLwGONKjgRjszg677RlGnkYSviQ8hHaUPOVYA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.140.0.tgz", + "integrity": "sha512-G6VK0nK61pH0d0mBjUqSZbVxGqqO5uzeginLDQj+gOO6ObfJjXRwgkD/ol0w1INcnFeAb6YGGO7qc3ueGHaycQ==", "cpu": [ "arm64" ], @@ -1269,9 +1274,9 @@ } }, "node_modules/@oxc-parser/binding-darwin-x64": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.137.0.tgz", - "integrity": "sha512-CL5dMm1asqXIDZHg14FLxj3Mc36w8PI7xCWh1uA4is6z8g2XrIILoTcQYOxDbwzuk34RDPX5IAGUxZr6LA9KAg==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.140.0.tgz", + "integrity": "sha512-HazBOuZzd2pO1C2uMmp8Gv7mhzMHqKSKDS1OZfcLEvpIcgA+48J92HEtNanVHDIzRD9PRPCV6aS6fkZIWOVl8Q==", "cpu": [ "x64" ], @@ -1286,9 +1291,9 @@ } }, "node_modules/@oxc-parser/binding-freebsd-x64": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.137.0.tgz", - "integrity": "sha512-79h8rYGnSlKPGWo7mHr2ixO6ea7aW8B0CT965SZ8SLbNnCOH5aOYBTeVXUY6eMvEaiLyWr8Skuiugr5pDYgLGw==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.140.0.tgz", + "integrity": "sha512-9hSUU+HmTUyOe4JzMHxNGgLWNY7rrO+6ShicZwImNJacEAACDMIkuEQQkvXSL+WJN50jaNtLYJv8s4OcBdpyUQ==", "cpu": [ "x64" ], @@ -1303,9 +1308,9 @@ } }, "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.137.0.tgz", - "integrity": "sha512-ASgmlSimhGyr0lksgVIo6hibz1obnDq4qJbiMX/AzltfgPnanRrzG1Q+23g8ljOHOjv6dsznkUuCYL3gg0sY1Q==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.140.0.tgz", + "integrity": "sha512-RAEuQsYtS0KcDFqN0ABTjyyNlokS91JeuDuoW9tEbG0JTbRNXnpQUdbYc/16JoA6Z/2ALbNrE3KmxtqDiuIjCQ==", "cpu": [ "arm" ], @@ -1320,9 +1325,9 @@ } }, "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.137.0.tgz", - "integrity": "sha512-AU2J9aa22Sx32wRGnDjybOU9TQXXQUud5sdUi+ZB0XxwM8aToWLweV+yA0wlQm0yIUVqljquqoHCYEq9II8gJQ==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.140.0.tgz", + "integrity": "sha512-c4CkHvPvqfojouredJ0w3e6+jiBq0SbFyhH61kr/zPb/7XsaYTNKQ54vmlSsopfdQbNDX40ZeK9Abs2Qet6wcw==", "cpu": [ "arm" ], @@ -1337,9 +1342,9 @@ } }, "node_modules/@oxc-parser/binding-linux-arm64-gnu": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.137.0.tgz", - "integrity": "sha512-GdEtiG89yMr7XkUGxifgodXEEm2f+xW2f9CpDjlgAnBOwhTmrpQMvhOGobLVKUyzf/qHBXW16smk5zbF3nZU6w==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.140.0.tgz", + "integrity": "sha512-yrjmLj8ixPB25yqvPGr28meGjb+keed7m1GqqY/0uqkhZIoT4t9zmfwUgFEtC33C7dtE+UQ7TU0IaVxf97SWJg==", "cpu": [ "arm64" ], @@ -1354,9 +1359,9 @@ } }, "node_modules/@oxc-parser/binding-linux-arm64-musl": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.137.0.tgz", - "integrity": "sha512-EGJ+Bs8iXx8KBH8DQ5BLoEm5lnHaYjlh4/8j8vFhrr/6z4tqONy5BZDzLpKmmNWlN6Hlc5r8YOuBVHqZ9vRFEQ==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.140.0.tgz", + "integrity": "sha512-ggGMQTN8Agwxp2WiLMpdY671dt0qTDJWiWlJeig3HnUwTnerRl0J2JdGVghWBeDcss2D9S2V2Js6dZHEiVabVA==", "cpu": [ "arm64" ], @@ -1371,9 +1376,9 @@ } }, "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.137.0.tgz", - "integrity": "sha512-vzFUQENy/fnbSe5DZWovq6tIBc1uhuMztanSW6rz1e9WdQE4gHwYuD7ZII6JnrJifd1R3RSoqiZbgRFlVL2tYQ==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.140.0.tgz", + "integrity": "sha512-IgTs8xYAFgAUGNmR65tIqjlJ8vKgrfXzC515e9goSdfMyKQV4aJpd2pUUudU4u51G64H0/DSEJEXKOraxm9ZCA==", "cpu": [ "ppc64" ], @@ -1388,9 +1393,9 @@ } }, "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.137.0.tgz", - "integrity": "sha512-SfVI14HBQs9gtLcUD5hTt5hsNbdrqSUNg9S8muN+LhVQ5nf1WwH3hAoK6B9NKgdYgWAQSXFXGiiBedQ4r/BKuw==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.140.0.tgz", + "integrity": "sha512-A1x+PMWZmSGaFVOx2YeNTFau8uD+QO14/vLP4GrcuvUPs3+nBkUOjy9Lus86ftHsDojjYMbvBelmKc3F7Rv08g==", "cpu": [ "riscv64" ], @@ -1405,9 +1410,9 @@ } }, "node_modules/@oxc-parser/binding-linux-riscv64-musl": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.137.0.tgz", - "integrity": "sha512-e7Ppy4FCIFNQxT/ikSeIWFoQ0l+N9vgtRBtLcyZXeolTzApyVoPqEXsYPrcdM/9i0Bwk8knvYd37vaEMxHyi6g==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.140.0.tgz", + "integrity": "sha512-zBqpfRo2myWPrPo5xUjeZqlnPXPXsX8BcWtWff66/eGRQdbPjhzPgXa/F+AtxT2afUViPxbuDlwscMKzQ5tg+g==", "cpu": [ "riscv64" ], @@ -1422,9 +1427,9 @@ } }, "node_modules/@oxc-parser/binding-linux-s390x-gnu": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.137.0.tgz", - "integrity": "sha512-Bho5qFwdhqsIFR7gipYEUlqvi3SRrY8sugxXig380MIaakBB1PyU9+7dBiBVScfImTNWhijUxdBwqrprGdq5WA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.140.0.tgz", + "integrity": "sha512-2M1DPm/8w9I//YzFlFC9qXw+r2tJFh5CYwRlYTq2vUJQS7qoQftEDeCZ8EnN7KHtvSiXvYj8mZI5pR7DpXmcEw==", "cpu": [ "s390x" ], @@ -1439,9 +1444,9 @@ } }, "node_modules/@oxc-parser/binding-linux-x64-gnu": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.137.0.tgz", - "integrity": "sha512-36mGWtg7PyFzjJwGDkH6/F4o2nIDEoKXLPr/X/lwqklkomQwJJt1I5GJVmGhovUEmgPK5WAeAZMqlFCehwiy9Q==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.140.0.tgz", + "integrity": "sha512-8aRDbZ/U/jO8N7go1MO72jtbpb4uswV8d7vOkMvt/BPgZiyEYvl1VIWK4ESxZZhnJ4tqwVldgX7dNiP/eB1Jdg==", "cpu": [ "x64" ], @@ -1456,9 +1461,9 @@ } }, "node_modules/@oxc-parser/binding-linux-x64-musl": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.137.0.tgz", - "integrity": "sha512-/Jqx6+N7A44n2BdvUr7pXhVr2vFjs6WGH3unZRczwrfiH0H1zY0QwKQMG/dtRiTlKGDKGukznPT8lx84/oEsZg==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.140.0.tgz", + "integrity": "sha512-xRqpeI8U2sQQS1W5BMWRyMTxtagkuLG2dEWruet5lFsWHTvBth11/TpSaJatHdqVVwHN0q3uuoS9zRsGinq8hg==", "cpu": [ "x64" ], @@ -1473,9 +1478,9 @@ } }, "node_modules/@oxc-parser/binding-openharmony-arm64": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.137.0.tgz", - "integrity": "sha512-9Uj0qHNNl+OgT1UTGwF7ixIXU6T1u2SbMidmgPy/h1h/fl2gRS6YpAxxY1gwHofcWjoTwkoMFd8xs5Vuj6GOFA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.140.0.tgz", + "integrity": "sha512-GbGRe26MqAKciFRvXeHNQJ6VAHYs9R4miP89sEAncysM3n+f4lnyLWgsa9kklJNpfnxdq2yRoNYHFqwBckVimw==", "cpu": [ "arm64" ], @@ -1490,9 +1495,9 @@ } }, "node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.137.0.tgz", - "integrity": "sha512-gW2vfkytNGgMVADiuzdvOfw0mWG9za20F/1fCJsif5aBMAvWJTSbpIXbIe0XkOe0VENk+PadpQ7cZgUy2sUJcA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.140.0.tgz", + "integrity": "sha512-vFiC1hqys+hkX1GnQkIoiTQJNiUm43Z0lO35ETKXTw0YtpW7+cN58YRRXFAQQ+TgpkIi3lrhcxdlnqz+Oi3ptQ==", "cpu": [ "wasm32" ], @@ -1500,18 +1505,18 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.5" + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-win32-arm64-msvc": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.137.0.tgz", - "integrity": "sha512-x+pFANF0yL5uK/6T7lu6SlR5qid6sp//eZXKLq5iNsIE+EQg6EaS8/wsW7E91nXXjpnPhSoMOHXShSVhGRdn8w==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.140.0.tgz", + "integrity": "sha512-fGSQldwEYKhM+H8uLt76Op8hh5+FYaR6lvvQ1Txw3Mhn86DyQXLcI0fi1EkFlTK7F+46OCk/j0AJMzZQm6g5Xg==", "cpu": [ "arm64" ], @@ -1526,9 +1531,9 @@ } }, "node_modules/@oxc-parser/binding-win32-ia32-msvc": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.137.0.tgz", - "integrity": "sha512-sQUqym80PFi6McRsIqfJrSu2JrSClEZIXXD+/FjAFoULEKzOPsldIdFBG96xdX8aVMzCNQ9792FPx3MfkEIrFA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.140.0.tgz", + "integrity": "sha512-sDS2Bai+g3ZWYwfZqmosiSuFDBcVnZ3Ta6pszzsiJoLMqsJEWKcxXXbGa7b7yXr++W2lQNPb3ZRJ8czseqL7RA==", "cpu": [ "ia32" ], @@ -1543,9 +1548,9 @@ } }, "node_modules/@oxc-parser/binding-win32-x64-msvc": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.137.0.tgz", - "integrity": "sha512-2AsevxlvNN4WKxpEn3RtqD5zbqMaXF+T7JXblsP4gVuY+vC9dXS4ED/PwfRCliFqoeisYS3Iro4DHzxr0TEvVA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.140.0.tgz", + "integrity": "sha512-kHbE1zWyb5OQgJA6/5P4WjiuB01sYdQwtZnSSyE58FQEXDAMnyeeq4vj7KgN75i5SlBzOs8A5MrtlD3gOlDKqQ==", "cpu": [ "x64" ], @@ -1560,9 +1565,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", - "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.140.0.tgz", + "integrity": "sha512-h5LUOzGArYemnW1NMz/DuuQhBi96J6JL2Bk8zE4kvqxB5Sg3jxmCiH4uyOWHDkiKSt5vWlG4FIwCR/DbstcNRQ==", "dev": true, "license": "MIT", "funding": { @@ -1570,9 +1575,9 @@ } }, "node_modules/@oxc-resolver/binding-android-arm-eabi": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.21.3.tgz", - "integrity": "sha512-eNU11A2WNizh04v3uyaJCootrHIaS0B9aHYXvAvVnPNk4xYSjMUjHnhQ6dewPN2MRYDskV85d1N0Aw0WNWhcyg==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.24.2.tgz", + "integrity": "sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==", "cpu": [ "arm" ], @@ -1584,9 +1589,9 @@ ] }, "node_modules/@oxc-resolver/binding-android-arm64": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.21.3.tgz", - "integrity": "sha512-8Q+ZjTLvn2dIcWsrmhdrEihm7q+ag/k+mkry7Z+t0QbbHaVxXQfvH9AewyVMh/WrpEKhQ3DDgx9fYbqeCpeOEw==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.24.2.tgz", + "integrity": "sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg==", "cpu": [ "arm64" ], @@ -1598,9 +1603,9 @@ ] }, "node_modules/@oxc-resolver/binding-darwin-arm64": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.21.3.tgz", - "integrity": "sha512-wkh0qKZGHXVUDxFw3oA1TXnU2BDYY/r775oJflGeIr8uDPPoN2pk8gijQIzYRT6hoql/lg3+Tx/SaTn9e2/aGg==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.24.2.tgz", + "integrity": "sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w==", "cpu": [ "arm64" ], @@ -1612,9 +1617,9 @@ ] }, "node_modules/@oxc-resolver/binding-darwin-x64": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.21.3.tgz", - "integrity": "sha512-HbNc23FAQYbuyDV2vBWMez4u4mrsm5RAkniGZAWqr6lYZ3N4beeqIb776jzwRl8qL2zRhHVXpUj97X0QgogVzg==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.24.2.tgz", + "integrity": "sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q==", "cpu": [ "x64" ], @@ -1626,9 +1631,9 @@ ] }, "node_modules/@oxc-resolver/binding-freebsd-x64": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.21.3.tgz", - "integrity": "sha512-K6xNsTUPEUdfrn0+kbMq5nOUB5w1C5pavPQngt4TM2FpN91lP0PBe2srSpamb4d69O7h86oAi/qWX/kZNRSjkw==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.24.2.tgz", + "integrity": "sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw==", "cpu": [ "x64" ], @@ -1640,9 +1645,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.21.3.tgz", - "integrity": "sha512-VcFmOpcpWX1zoEy8M58tR2M9YxM+Z9RuQhqAx5q0CTmrruaP7Gveejg75hzd/5sg5nk9G3aLALEa3hE2FsmmTQ==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.24.2.tgz", + "integrity": "sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg==", "cpu": [ "arm" ], @@ -1654,9 +1659,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.21.3.tgz", - "integrity": "sha512-quVoxFLBy43hWaQbbDtQNRwAX5vX76mv7n64icAtQcJ3eNgVeblqmkupF/hAneNthdqSlnd1sTjb3aQSaDPaCQ==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.24.2.tgz", + "integrity": "sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA==", "cpu": [ "arm" ], @@ -1668,9 +1673,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.21.3.tgz", - "integrity": "sha512-X0AqNZgcD07Q4V3RDK18/vYOj/HQT/FnmEFGYS2jTWqY7JO13ryE3TEs3eAIgUJhBnNkpEaiXqz3VK8M7qQhWQ==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.24.2.tgz", + "integrity": "sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA==", "cpu": [ "arm64" ], @@ -1682,9 +1687,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm64-musl": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.21.3.tgz", - "integrity": "sha512-YkaQnaKYdbuaXvRt5Qd0GpbihzVnyfR6z1SpYfIUC6RTu4NF7lDKPjVkYb+jRI2gedVO2rVpN35Y6akG6ud4Lw==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.24.2.tgz", + "integrity": "sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw==", "cpu": [ "arm64" ], @@ -1696,9 +1701,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.21.3.tgz", - "integrity": "sha512-gB9HwhrPiFqUzDeEq+y/CgAijz1YdI6BnXz5GaH2Pa9cWdutchlkGFAiAuGb/PjVQpiK6NFKzFuztxrweoit7A==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.24.2.tgz", + "integrity": "sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA==", "cpu": [ "ppc64" ], @@ -1710,9 +1715,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.21.3.tgz", - "integrity": "sha512-zjDWBlYk8QGv0H8dsPUWqkfjYIIjG2TvspGkzXL0eImbgxtZorA/klKeHyolevoT3Kvbi+1iMr9Lhrh7jf54Og==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.24.2.tgz", + "integrity": "sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw==", "cpu": [ "riscv64" ], @@ -1724,9 +1729,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.21.3.tgz", - "integrity": "sha512-4UfsQvacV388y1zpXL7C1x1FNYaV52JtuNRiuzrfQA2z1z6ElVrsidkGsrvQ5EgeSq1Pj7kaKqrgGkvFuxJ/tw==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.24.2.tgz", + "integrity": "sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w==", "cpu": [ "riscv64" ], @@ -1738,9 +1743,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.21.3.tgz", - "integrity": "sha512-b5uH+HKH0MP5mNBYaK75SKsJbw52URqrx2LavYdq6wb0l3ExAG5niYRP9DWUNHdKilpaBVM2bXk9HNWrH3ew7Q==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.24.2.tgz", + "integrity": "sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ==", "cpu": [ "s390x" ], @@ -1752,9 +1757,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-x64-gnu": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.21.3.tgz", - "integrity": "sha512-PjYlmilBpNRh2ntXNYAK3Am5w/nPfEpnU/96iNx7CI8EzAn12J4JRiec63wHJTH31nLoCNxBg/829pN+3CfG3Q==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.24.2.tgz", + "integrity": "sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg==", "cpu": [ "x64" ], @@ -1766,9 +1771,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-x64-musl": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.21.3.tgz", - "integrity": "sha512-QTBAb7JuHlZ7JUEyM8UiQi2f7m/L4swBhP2TNpYIDc9Wp/wRw1G/8sl6i13aIzQAXH7LKIm294LeOHd0lQR8zA==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.24.2.tgz", + "integrity": "sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw==", "cpu": [ "x64" ], @@ -1780,9 +1785,9 @@ ] }, "node_modules/@oxc-resolver/binding-openharmony-arm64": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.21.3.tgz", - "integrity": "sha512-4j1DFwjwv36ec9kds0jU/ucQ5Ha4ERO/H95BxR5JFf0kqUUAJ1kwII7XhTc1vZrkdJkvLGC9Q2MbpObpum8RBg==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.24.2.tgz", + "integrity": "sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg==", "cpu": [ "arm64" ], @@ -1794,9 +1799,9 @@ ] }, "node_modules/@oxc-resolver/binding-wasm32-wasi": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.21.3.tgz", - "integrity": "sha512-i8oluoel5kru/j1WNrjmQSiA3GQ7wvIYVR1IwIoZtKogAhya2iub+ZKIeSIkcJOrnzQ18Tzl/F+kL3fYOxZLvA==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.24.2.tgz", + "integrity": "sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q==", "cpu": [ "wasm32" ], @@ -1804,41 +1809,18 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.11.0", - "@emnapi/runtime": "1.11.0", - "@napi-rs/wasm-runtime": "^1.1.5" + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz", - "integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz", - "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.21.3.tgz", - "integrity": "sha512-M/8dw8dD6aOs+NlPJax401CZB9I7Aut84isQLgALGGwke4Afvw+/7yYhZb94yXf6t2sPLhQLmSmtSV+2FhsOWg==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.24.2.tgz", + "integrity": "sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg==", "cpu": [ "arm64" ], @@ -1850,9 +1832,9 @@ ] }, "node_modules/@oxc-resolver/binding-win32-x64-msvc": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.21.3.tgz", - "integrity": "sha512-H7BCt/VnS9hnmMp42eGhZ99izSCRvlnWwy/N71K1/J8QoExwY4262Z8QiEkMDtduRJrztayDxETTckmUuAVL9Q==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.24.2.tgz", + "integrity": "sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw==", "cpu": [ "x64" ], @@ -1998,13 +1980,13 @@ } }, "node_modules/@rc-component/dropdown": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.2.tgz", - "integrity": "sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.3.tgz", + "integrity": "sha512-YTST/N6kpqpDz3IMuM/PSSZnrDpSOA6dgHv12gPA90ZTSLv2CoqkZ0+9NtwTY6BeO7dstPblSic2QJg7dSFy/g==", "license": "MIT", "dependencies": { "@rc-component/trigger": "^3.0.0", - "@rc-component/util": "^1.2.1", + "@rc-component/util": "^1.11.1", "clsx": "^2.1.1" }, "peerDependencies": { @@ -2400,9 +2382,9 @@ } }, "node_modules/@rc-component/table": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.10.2.tgz", - "integrity": "sha512-b3PjqB9Gp25p5t/zq+9QrbXbodkptT8/zvLmwgd2FNPUUtaYyDnQqfxeD5a7ao8E8lpinLHsi2u2vdfPhyNvAw==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.10.4.tgz", + "integrity": "sha512-HwoTnrwc29zeoXkXGhWqzJh8FIibGUxi1jM4LtoSzmR9d5Vv5osUQpZxnXKBP8iOCvyD6BQzZm1nXJRcnrxpAg==", "license": "MIT", "dependencies": { "@rc-component/context": "^2.0.1", @@ -2510,15 +2492,15 @@ } }, "node_modules/@rc-component/trigger": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.9.1.tgz", - "integrity": "sha512-LNsYvz60mrLJ/kRvKcHE7boUvcQfVMCfRqZ71x3Fo9AOiZ1KKIEqkzMA8DNvz2V3Bcvir/vwQNn7JF1NPODQ7Q==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.10.1.tgz", + "integrity": "sha512-mXlDN0IXdtV8Yqqm8195ECCyrbmfvvfKvwVvSlH0+qvKD6BUF8gRhEjSy0FOcD1+CcDRHgTiX99LoxfQrmh3Cw==", "license": "MIT", "dependencies": { - "@rc-component/motion": "^1.1.4", - "@rc-component/portal": "^2.2.0", - "@rc-component/resize-observer": "^1.1.1", - "@rc-component/util": "^1.2.1", + "@rc-component/motion": "^1.3.3", + "@rc-component/portal": "^2.2.1", + "@rc-component/resize-observer": "^1.1.2", + "@rc-component/util": "^1.11.1", "clsx": "^2.1.1" }, "engines": { @@ -2544,13 +2526,13 @@ } }, "node_modules/@rc-component/util": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.11.1.tgz", - "integrity": "sha512-awVlI3ub2vqfqkYxOBc/uQ0efm3jw0wcrhtO/YWLyZfxiKXczKwNbVuhlnyxytDt7H9pbbVQiqr+O6MLATtRYg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.12.0.tgz", + "integrity": "sha512-AEjPL8JVdohIITaiXokyjL9WQ6tKWWjAYK9QU16tGNE9JaQABBQy+hA4H2Lup5MgXy9yY3iLrbZJheuU13hTdQ==", "license": "MIT", "dependencies": { "is-mobile": "^5.0.0", - "react-is": "^18.2.0" + "react-is": "^19.2.7" }, "peerDependencies": { "react": ">=18.0.0", @@ -2577,9 +2559,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", - "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.1.tgz", + "integrity": "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==", "cpu": [ "arm64" ], @@ -2594,9 +2576,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", - "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.1.tgz", + "integrity": "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==", "cpu": [ "arm64" ], @@ -2611,9 +2593,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", - "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.1.tgz", + "integrity": "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==", "cpu": [ "x64" ], @@ -2628,9 +2610,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", - "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.1.tgz", + "integrity": "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==", "cpu": [ "x64" ], @@ -2645,9 +2627,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", - "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.1.tgz", + "integrity": "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==", "cpu": [ "arm" ], @@ -2662,9 +2644,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", - "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.1.tgz", + "integrity": "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==", "cpu": [ "arm64" ], @@ -2679,9 +2661,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", - "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.1.tgz", + "integrity": "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==", "cpu": [ "arm64" ], @@ -2696,9 +2678,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", - "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.1.tgz", + "integrity": "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==", "cpu": [ "ppc64" ], @@ -2713,9 +2695,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", - "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.1.tgz", + "integrity": "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==", "cpu": [ "s390x" ], @@ -2730,9 +2712,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", - "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.1.tgz", + "integrity": "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==", "cpu": [ "x64" ], @@ -2747,9 +2729,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", - "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.1.tgz", + "integrity": "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==", "cpu": [ "x64" ], @@ -2764,9 +2746,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", - "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.1.tgz", + "integrity": "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==", "cpu": [ "arm64" ], @@ -2781,28 +2763,59 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", - "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", - "cpu": [ - "wasm32" - ], + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.1.tgz", + "integrity": "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" + "@emnapi/core": "2.0.0-alpha.3", + "@emnapi/runtime": "2.0.0-alpha.3", + "@napi-rs/wasm-runtime": "^1.2.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz", + "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "2.0.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz", + "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz", + "integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", - "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.1.tgz", + "integrity": "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==", "cpu": [ "arm64" ], @@ -2817,9 +2830,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", - "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.1.tgz", + "integrity": "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==", "cpu": [ "x64" ], @@ -3004,13 +3017,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz", - "integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==", + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~8.3.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/parse-json": { @@ -3057,17 +3070,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.0.tgz", - "integrity": "sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.62.0", - "@typescript-eslint/type-utils": "8.62.0", - "@typescript-eslint/utils": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -3080,15 +3093,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.62.0", + "@typescript-eslint/parser": "^8.65.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { @@ -3096,16 +3109,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.0.tgz", - "integrity": "sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3" }, "engines": { @@ -3121,14 +3134,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.0.tgz", - "integrity": "sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.62.0", - "@typescript-eslint/types": "^8.62.0", + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", "debug": "^4.4.3" }, "engines": { @@ -3143,14 +3156,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.0.tgz", - "integrity": "sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0" + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3161,9 +3174,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz", - "integrity": "sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", "dev": true, "license": "MIT", "engines": { @@ -3178,15 +3191,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.0.tgz", - "integrity": "sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0", - "@typescript-eslint/utils": "8.62.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -3203,9 +3216,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.0.tgz", - "integrity": "sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", "dev": true, "license": "MIT", "engines": { @@ -3217,16 +3230,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.0.tgz", - "integrity": "sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.62.0", - "@typescript-eslint/tsconfig-utils": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -3258,16 +3271,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.0.tgz", - "integrity": "sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0" + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3282,13 +3295,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.0.tgz", - "integrity": "sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -3313,9 +3326,9 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", - "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", "dev": true, "license": "MIT", "dependencies": { @@ -3558,16 +3571,16 @@ } }, "node_modules/antd": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/antd/-/antd-6.5.0.tgz", - "integrity": "sha512-9zbVc9UukfGuqCvIAov01nlpDQWfARNmZQyt21ZhqLX7ilXmi4cdkp12xA48WEmXRXwZvno8A03qQuGE9JG8fg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/antd/-/antd-6.5.2.tgz", + "integrity": "sha512-ntYx0lr4Jq192QnBkDWkDqEeoberXZ34vSE9SgiP/0J6DY8O0pzR3bVZLBsdpCSguVkwjtEAP+QNeMN7LNAvgw==", "license": "MIT", "dependencies": { "@ant-design/colors": "^8.0.1", "@ant-design/cssinjs": "^2.1.2", "@ant-design/cssinjs-utils": "^2.1.2", "@ant-design/fast-color": "^3.0.1", - "@ant-design/icons": "^6.3.1", + "@ant-design/icons": "^6.3.2", "@ant-design/react-slick": "~2.0.0", "@babel/runtime": "^7.29.2", "@rc-component/cascader": "~1.17.0", @@ -3576,7 +3589,7 @@ "@rc-component/color-picker": "~3.1.1", "@rc-component/dialog": "~1.10.0", "@rc-component/drawer": "~1.4.2", - "@rc-component/dropdown": "~1.0.2", + "@rc-component/dropdown": "~1.0.3", "@rc-component/form": "~1.8.5", "@rc-component/image": "~1.9.0", "@rc-component/input": "~1.3.1", @@ -3597,15 +3610,15 @@ "@rc-component/slider": "~1.1.1", "@rc-component/steps": "~1.2.2", "@rc-component/switch": "~1.0.3", - "@rc-component/table": "~1.10.2", + "@rc-component/table": "~1.10.4", "@rc-component/tabs": "~1.11.0", "@rc-component/tooltip": "~1.4.0", "@rc-component/tour": "~2.4.0", "@rc-component/tree": "~1.3.2", "@rc-component/tree-select": "~1.11.0", - "@rc-component/trigger": "^3.9.1", + "@rc-component/trigger": "^3.10.1", "@rc-component/upload": "~1.1.1", - "@rc-component/util": "^1.11.1", + "@rc-component/util": "^1.12.0", "clsx": "^2.1.1", "dayjs": "^1.11.11", "scroll-into-view-if-needed": "^3.1.0", @@ -3686,13 +3699,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", - "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", + "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } @@ -4223,9 +4236,9 @@ } }, "node_modules/eslint": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz", - "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", + "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", "dev": true, "license": "MIT", "workspaces": [ @@ -4235,7 +4248,7 @@ "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.6.0", + "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", @@ -4259,7 +4272,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", + "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -4312,9 +4325,9 @@ } }, "node_modules/eslint-plugin-simple-import-sort": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-13.0.0.tgz", - "integrity": "sha512-McAc+/Nlvcg4byY/CABGH8kqnefWBj8s3JA2okEtz8ixbECQgU46p0HkTUKa4YS7wvgGceimlc34p1nXqbWqtA==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-14.0.0.tgz", + "integrity": "sha512-NUJO0+XFCkk+o5EsAJruTgnfMEpeWrPWeJS15UVF60GgXmqz1BJ9/3hzlvG7lkL8Bubzos5cCLptThbFfPnSMQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -4623,9 +4636,9 @@ } }, "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4730,9 +4743,9 @@ } }, "node_modules/globals": { - "version": "17.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", - "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", + "version": "17.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.8.0.tgz", + "integrity": "sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==", "dev": true, "license": "MIT", "engines": { @@ -4884,12 +4897,12 @@ "license": "MIT" }, "node_modules/html-parse-stringify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", - "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-4.0.1.tgz", + "integrity": "sha512-0zHsZJrK7S3K2aucXWL6ycoYJ/iNtIcFHC/nYQgFklPtrv5LpJctIiSCroWZWeuoXvuyFdzp6KzjJQ+OT5MfFw==", "license": "MIT", - "dependencies": { - "void-elements": "3.1.0" + "funding": { + "url": "https://locize.com" } }, "node_modules/https-proxy-agent": { @@ -4906,9 +4919,9 @@ } }, "node_modules/i18next": { - "version": "26.3.3", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.3.tgz", - "integrity": "sha512-aYVegyBdXSO93CMMihvr47jI7GHSOcIahMpJX+qzUXDzW4xDJf2uenIA+45vDU+YhiVdcfsql70AC9RVdMNrHg==", + "version": "26.3.6", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.6.tgz", + "integrity": "sha512-Bu5Z2nAXgfVyM8xvW3jk9EKRIuX37PudsrBViThNFx7CR7aaYTpP01cxNB/E4c4UUzTDiAZRstEhsRfPOL/8xA==", "funding": [ { "type": "individual", @@ -4925,7 +4938,7 @@ ], "license": "MIT", "peerDependencies": { - "typescript": "^5 || ^6" + "typescript": "^5 || ^6 || ^7" }, "peerDependenciesMeta": { "typescript": { @@ -5125,39 +5138,39 @@ "license": "MIT" }, "node_modules/jsdom": { - "version": "29.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", - "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.0.1.tgz", + "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^5.1.11", - "@asamuzakjp/dom-selector": "^7.1.1", + "@asamuzakjp/css-color": "^6.0.5", + "@asamuzakjp/dom-selector": "^8.3.0", "@bramus/specificity": "^2.4.2", - "@csstools/css-syntax-patches-for-csstree": "^1.1.3", - "@exodus/bytes": "^1.15.0", + "@csstools/css-syntax-patches-for-csstree": "^1.1.7", + "@exodus/bytes": "^1.15.1", "css-tree": "^3.2.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.3.5", + "lru-cache": "^11.5.2", "parse5": "^8.0.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.1", - "undici": "^7.25.0", + "tough-cookie": "^6.0.2", + "undici": "^8.9.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.1", + "whatwg-url": "^17.1.0", "xml-name-validator": "^5.0.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" }, "peerDependencies": { - "canvas": "^3.0.0" + "canvas": "^3.2.3" }, "peerDependenciesMeta": { "canvas": { @@ -5175,6 +5188,21 @@ "node": "20 || >=22" } }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.0.tgz", + "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.15.1", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^22.14.0 || >=24.0.0" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -5247,9 +5275,9 @@ } }, "node_modules/knip": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/knip/-/knip-6.22.0.tgz", - "integrity": "sha512-HTqtalzPNERFiyTj/3pERGss8oBzgxL9ZWiEQ29N9/+cTMZ1guIIEtcczrYpaoE76//XSHO11uuc66O9feY8wA==", + "version": "6.29.0", + "resolved": "https://registry.npmjs.org/knip/-/knip-6.29.0.tgz", + "integrity": "sha512-A3kXqSBky1tWBAqiU9srdtu0Larhzkuyor0aD/gg+ToiqyBncCCs2Q60sLsxmcKhV0OsKss9LV0hMPpwLv711Q==", "dev": true, "funding": [ { @@ -5267,15 +5295,15 @@ "formatly": "^0.3.0", "get-tsconfig": "4.14.0", "jiti": "^2.7.0", - "oxc-parser": "^0.137.0", - "oxc-resolver": "11.21.3", - "picomatch": "^4.0.4", - "smol-toml": "^1.6.1", + "oxc-parser": "^0.140.0", + "oxc-resolver": "11.24.2", + "picomatch": "^4.0.5", + "smol-toml": "^1.7.0", "strip-json-comments": "5.0.3", "tinyglobby": "^0.2.17", - "unbash": "^4.0.1", + "unbash": "^4.0.3", "yaml": "^2.9.0", - "zod": "^4.1.11" + "zod": "^4.4.3" }, "bin": { "knip": "bin/knip.js", @@ -5300,9 +5328,9 @@ } }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -5316,23 +5344,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -5351,9 +5379,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -5372,9 +5400,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -5393,9 +5421,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -5414,9 +5442,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -5435,9 +5463,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], @@ -5456,9 +5484,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], @@ -5477,9 +5505,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], @@ -5498,9 +5526,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], @@ -5519,9 +5547,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -5540,9 +5568,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -5859,13 +5887,13 @@ "license": "MIT" }, "node_modules/oxc-parser": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.137.0.tgz", - "integrity": "sha512-yFImD+WLElJpLKy8llG1qe4DCmMsL18peRp8XP1JKfig/gISbJkglnpDtX2aTmAn10kZF7164HbN2H8QPsXxGg==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.140.0.tgz", + "integrity": "sha512-h6QFWd6lBMfjESqgQ27GjzrSDb0qbznp7VDQqp2zvgsrWut4vcchyMIzOVXvGQ2GMZgKw9RWrFNWv9WqGL0p7Q==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "^0.137.0" + "@oxc-project/types": "^0.140.0" }, "engines": { "node": "^20.19.0 || >=22.12.0" @@ -5874,57 +5902,57 @@ "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxc-parser/binding-android-arm-eabi": "0.137.0", - "@oxc-parser/binding-android-arm64": "0.137.0", - "@oxc-parser/binding-darwin-arm64": "0.137.0", - "@oxc-parser/binding-darwin-x64": "0.137.0", - "@oxc-parser/binding-freebsd-x64": "0.137.0", - "@oxc-parser/binding-linux-arm-gnueabihf": "0.137.0", - "@oxc-parser/binding-linux-arm-musleabihf": "0.137.0", - "@oxc-parser/binding-linux-arm64-gnu": "0.137.0", - "@oxc-parser/binding-linux-arm64-musl": "0.137.0", - "@oxc-parser/binding-linux-ppc64-gnu": "0.137.0", - "@oxc-parser/binding-linux-riscv64-gnu": "0.137.0", - "@oxc-parser/binding-linux-riscv64-musl": "0.137.0", - "@oxc-parser/binding-linux-s390x-gnu": "0.137.0", - "@oxc-parser/binding-linux-x64-gnu": "0.137.0", - "@oxc-parser/binding-linux-x64-musl": "0.137.0", - "@oxc-parser/binding-openharmony-arm64": "0.137.0", - "@oxc-parser/binding-wasm32-wasi": "0.137.0", - "@oxc-parser/binding-win32-arm64-msvc": "0.137.0", - "@oxc-parser/binding-win32-ia32-msvc": "0.137.0", - "@oxc-parser/binding-win32-x64-msvc": "0.137.0" + "@oxc-parser/binding-android-arm-eabi": "0.140.0", + "@oxc-parser/binding-android-arm64": "0.140.0", + "@oxc-parser/binding-darwin-arm64": "0.140.0", + "@oxc-parser/binding-darwin-x64": "0.140.0", + "@oxc-parser/binding-freebsd-x64": "0.140.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.140.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.140.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.140.0", + "@oxc-parser/binding-linux-arm64-musl": "0.140.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.140.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.140.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.140.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.140.0", + "@oxc-parser/binding-linux-x64-gnu": "0.140.0", + "@oxc-parser/binding-linux-x64-musl": "0.140.0", + "@oxc-parser/binding-openharmony-arm64": "0.140.0", + "@oxc-parser/binding-wasm32-wasi": "0.140.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.140.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.140.0", + "@oxc-parser/binding-win32-x64-msvc": "0.140.0" } }, "node_modules/oxc-resolver": { - "version": "11.21.3", - "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.21.3.tgz", - "integrity": "sha512-2Mx3fKQz7+xgrBONjsxOgCGtMHOn38/HxMzW1I5efwXB5a4lRN0Vp40gYUJFBWJslcrvwoofTrqoTnLbwTd3pA==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.24.2.tgz", + "integrity": "sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxc-resolver/binding-android-arm-eabi": "11.21.3", - "@oxc-resolver/binding-android-arm64": "11.21.3", - "@oxc-resolver/binding-darwin-arm64": "11.21.3", - "@oxc-resolver/binding-darwin-x64": "11.21.3", - "@oxc-resolver/binding-freebsd-x64": "11.21.3", - "@oxc-resolver/binding-linux-arm-gnueabihf": "11.21.3", - "@oxc-resolver/binding-linux-arm-musleabihf": "11.21.3", - "@oxc-resolver/binding-linux-arm64-gnu": "11.21.3", - "@oxc-resolver/binding-linux-arm64-musl": "11.21.3", - "@oxc-resolver/binding-linux-ppc64-gnu": "11.21.3", - "@oxc-resolver/binding-linux-riscv64-gnu": "11.21.3", - "@oxc-resolver/binding-linux-riscv64-musl": "11.21.3", - "@oxc-resolver/binding-linux-s390x-gnu": "11.21.3", - "@oxc-resolver/binding-linux-x64-gnu": "11.21.3", - "@oxc-resolver/binding-linux-x64-musl": "11.21.3", - "@oxc-resolver/binding-openharmony-arm64": "11.21.3", - "@oxc-resolver/binding-wasm32-wasi": "11.21.3", - "@oxc-resolver/binding-win32-arm64-msvc": "11.21.3", - "@oxc-resolver/binding-win32-x64-msvc": "11.21.3" + "@oxc-resolver/binding-android-arm-eabi": "11.24.2", + "@oxc-resolver/binding-android-arm64": "11.24.2", + "@oxc-resolver/binding-darwin-arm64": "11.24.2", + "@oxc-resolver/binding-darwin-x64": "11.24.2", + "@oxc-resolver/binding-freebsd-x64": "11.24.2", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.24.2", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.24.2", + "@oxc-resolver/binding-linux-arm64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-arm64-musl": "11.24.2", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-riscv64-musl": "11.24.2", + "@oxc-resolver/binding-linux-s390x-gnu": "11.24.2", + "@oxc-resolver/binding-linux-x64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-x64-musl": "11.24.2", + "@oxc-resolver/binding-openharmony-arm64": "11.24.2", + "@oxc-resolver/binding-wasm32-wasi": "11.24.2", + "@oxc-resolver/binding-win32-arm64-msvc": "11.24.2", + "@oxc-resolver/binding-win32-x64-msvc": "11.24.2" } }, "node_modules/p-limit": { @@ -6058,9 +6086,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -6102,21 +6130,6 @@ "node": ">=20" } }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/postcss": { "version": "8.5.25", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", @@ -6214,40 +6227,40 @@ } }, "node_modules/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", - "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", - "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.7" + "react": "^19.2.8" } }, "node_modules/react-i18next": { - "version": "17.0.8", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.8.tgz", - "integrity": "sha512-0ooKbGLU8JXhe1zwpQUWIeXSgLPOfwJmgheWRIUpcoA0CpyabpGhayjdG+/eA5esC1AQ8h2jWpXjJfzQzeDOCw==", + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.11.tgz", + "integrity": "sha512-cDtkXgxjuFTWUH6V+aQn1Ve5vDiUztCNPWW5GtSHDccsgRXO1nE6QFWCEmc1KAutrb3OUv87wFShJL5RhUwPXg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.2", - "html-parse-stringify": "^3.0.1", + "html-parse-stringify": "^4.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 26.2.0", "react": ">= 16.8.0", - "typescript": "^5 || ^6" + "typescript": "^5 || ^6 || ^7" }, "peerDependenciesMeta": { "react-dom": { @@ -6262,9 +6275,9 @@ } }, "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", "license": "MIT" }, "node_modules/react-router": { @@ -6370,13 +6383,13 @@ "license": "MIT" }, "node_modules/rolldown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", - "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.1.tgz", + "integrity": "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.137.0", + "@oxc-project/types": "=0.142.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -6386,21 +6399,31 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.3", - "@rolldown/binding-darwin-arm64": "1.1.3", - "@rolldown/binding-darwin-x64": "1.1.3", - "@rolldown/binding-freebsd-x64": "1.1.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", - "@rolldown/binding-linux-arm64-gnu": "1.1.3", - "@rolldown/binding-linux-arm64-musl": "1.1.3", - "@rolldown/binding-linux-ppc64-gnu": "1.1.3", - "@rolldown/binding-linux-s390x-gnu": "1.1.3", - "@rolldown/binding-linux-x64-gnu": "1.1.3", - "@rolldown/binding-linux-x64-musl": "1.1.3", - "@rolldown/binding-openharmony-arm64": "1.1.3", - "@rolldown/binding-wasm32-wasi": "1.1.3", - "@rolldown/binding-win32-arm64-msvc": "1.1.3", - "@rolldown/binding-win32-x64-msvc": "1.1.3" + "@rolldown/binding-android-arm64": "1.2.1", + "@rolldown/binding-darwin-arm64": "1.2.1", + "@rolldown/binding-darwin-x64": "1.2.1", + "@rolldown/binding-freebsd-x64": "1.2.1", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.1", + "@rolldown/binding-linux-arm64-gnu": "1.2.1", + "@rolldown/binding-linux-arm64-musl": "1.2.1", + "@rolldown/binding-linux-ppc64-gnu": "1.2.1", + "@rolldown/binding-linux-s390x-gnu": "1.2.1", + "@rolldown/binding-linux-x64-gnu": "1.2.1", + "@rolldown/binding-linux-x64-musl": "1.2.1", + "@rolldown/binding-openharmony-arm64": "1.2.1", + "@rolldown/binding-wasm32-wasi": "1.2.1", + "@rolldown/binding-win32-arm64-msvc": "1.2.1", + "@rolldown/binding-win32-x64-msvc": "1.2.1" + } + }, + "node_modules/rolldown/node_modules/@oxc-project/types": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", + "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, "node_modules/saxes": { @@ -6822,16 +6845,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.0.tgz", - "integrity": "sha512-8QxXi+ZACKX0kaqO4gY8kn0RSD9gFfaHDWwjqtEN48aWCBkX4MJaufWN+c3BzlrXLOxfywDL8CaoqUwcRq4j4Q==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.62.0", - "@typescript-eslint/parser": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0", - "@typescript-eslint/utils": "8.62.0" + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6846,9 +6869,9 @@ } }, "node_modules/unbash": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/unbash/-/unbash-4.0.1.tgz", - "integrity": "sha512-1ajSo3813sDoVIHx4inJdUS4l5L2ic5cFiddemPiyjb/PZEoBAhFwHtbaEdRDFxbAKy7FCG7s5ww3/uCFawuIA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unbash/-/unbash-4.0.4.tgz", + "integrity": "sha512-60m9IVGbavD6jholbxt0jVBXZkEB/HsMZq7Tyaghseve2/Sf0zQRAIfWsD34sde+DKP2tBxJS2wP88ZM0D1FhA==", "dev": true, "license": "ISC", "engines": { @@ -6856,19 +6879,19 @@ } }, "node_modules/undici": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", - "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz", + "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==", "dev": true, "license": "MIT", "engines": { - "node": ">=20.18.1" + "node": ">=22.19.0" } }, "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, @@ -6942,16 +6965,16 @@ } }, "node_modules/vite": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz", - "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz", + "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "~1.1.2", + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.23", + "rolldown": "~1.2.0", "tinyglobby": "^0.2.17" }, "bin": { @@ -6968,7 +6991,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.3.0", + "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -7019,6 +7042,21 @@ } } }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/vitest": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", @@ -7109,15 +7147,6 @@ } } }, - "node_modules/void-elements": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", - "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index 32e0369..d9ce31b 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "engines": { - "node": ">=22.22.0" + "node": ">=24.15.0 <25 || >=26.0.0" }, "scripts": { "audit": "npm audit --audit-level=high", @@ -25,16 +25,16 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "antd": "^6.5.0", + "antd": "^6.5.2", "antd-style": "^4.1.0", - "axios": "^1.18.1", + "axios": "^1.19.0", "dayjs": "^1.11.21", - "i18next": "^26.3.3", + "i18next": "^26.3.6", "jalaliday": "^3.1.1", "lodash": "^4.18.1", - "react": "^19.2.7", - "react-dom": "^19.2.7", - "react-i18next": "^17.0.8", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "react-i18next": "^17.0.11", "react-router": "^8.3.0" }, "devDependencies": { @@ -45,25 +45,25 @@ "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", "@types/lodash": "^4.17.24", - "@types/node": "^26.0.1", + "@types/node": "^24.13.3", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", - "@typescript-eslint/types": "^8.62.0", + "@typescript-eslint/types": "^8.65.0", "@vitejs/plugin-basic-ssl": "^2.3.0", - "@vitejs/plugin-react": "^6.0.3", + "@vitejs/plugin-react": "^6.0.5", "@vitest/coverage-v8": "^4.1.10", - "eslint": "^10.6.0", + "eslint": "^10.8.0", "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.3", - "eslint-plugin-simple-import-sort": "^13.0.0", - "globals": "^17.7.0", - "jsdom": "^29.1.1", - "knip": "^6.22.0", + "eslint-plugin-simple-import-sort": "^14.0.0", + "globals": "^17.8.0", + "jsdom": "^30.0.1", + "knip": "^6.29.0", "msw": "^2.15.0", "prettier": "^3.9.6", "typescript": "~6.0.3", - "typescript-eslint": "^8.62.0", - "vite": "^8.1.0", + "typescript-eslint": "^8.65.0", + "vite": "^8.2.0", "vitest": "^4.1.10" } } From 65e44867f9fb20e4d6a42cb12a03679848d51c4c Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 19:31:48 +0330 Subject: [PATCH 08/81] chore: configure automated dependency updates --- .github/dependabot.yml | 48 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..66eecc1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,48 @@ +version: 2 + +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + day: monday + time: "09:00" + timezone: Asia/Tehran + open-pull-requests-limit: 10 + reviewers: + - sepaseh + commit-message: + prefix: chore + include: scope + groups: + production-minor-and-patch: + dependency-type: production + update-types: + - minor + - patch + development-minor-and-patch: + dependency-type: development + update-types: + - minor + - patch + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + time: "09:00" + timezone: Asia/Tehran + open-pull-requests-limit: 5 + reviewers: + - sepaseh + commit-message: + prefix: chore + include: scope + groups: + actions-minor-and-patch: + patterns: + - "*" + update-types: + - minor + - patch From 976f01b82d343cc133dd484dde27d8f966737a99 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 19:33:53 +0330 Subject: [PATCH 09/81] ci: pin GitHub Actions to immutable revisions --- .github/workflows/ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cda8199..01cb083 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,9 +24,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ env.NODE_VERSION }} cache: npm @@ -53,9 +53,9 @@ jobs: VITE_APP_BASE_URL: / steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ env.NODE_VERSION }} cache: npm @@ -64,7 +64,7 @@ jobs: - run: npm run build - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: dist-${{ github.sha }} path: dist @@ -77,9 +77,9 @@ jobs: VITE_APP_BASE_URL: / steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ env.NODE_VERSION }} cache: npm @@ -91,7 +91,7 @@ jobs: - run: npm run test:e2e - if: ${{ !cancelled() }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: playwright-report-${{ github.sha }} path: | From 70d7e226d443fd410d47af3baf41ad5b0d77ff13 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 19:38:49 +0330 Subject: [PATCH 10/81] ci: add CodeQL security scanning --- .github/workflows/codeql.yml | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..c002bf6 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,37 @@ +name: CodeQL + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + - cron: "30 3 * * 1" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +permissions: + contents: read + +jobs: + analyze: + name: Analyze JavaScript and TypeScript + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - uses: github/codeql-action/init@f52b05f4acaaa234e44466e66d29050e135ea9ef # v4.36.0 + with: + languages: javascript-typescript + queries: security-extended + + - uses: github/codeql-action/analyze@f52b05f4acaaa234e44466e66d29050e135ea9ef # v4.36.0 From 73e3249c63240194ad5d33c1b78bb5b2406ad029 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 19:43:44 +0330 Subject: [PATCH 11/81] ci: integrate SonarQube analysis --- .github/workflows/ci.yml | 20 ++++++++++++++++++++ docs/testing.md | 8 ++++++++ sonar-project.properties | 7 +++++++ 3 files changed, 35 insertions(+) create mode 100644 sonar-project.properties diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01cb083..5491b17 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,26 @@ jobs: - run: npm run test:coverage + - name: Check SonarQube configuration + id: sonarqube + env: + SONAR_PROJECT_KEY: ${{ vars.SONAR_PROJECT_KEY }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + if [[ -n "$SONAR_PROJECT_KEY" && -n "$SONAR_TOKEN" ]]; then + echo "enabled=true" >> "$GITHUB_OUTPUT" + fi + + - name: SonarQube scan + if: ${{ steps.sonarqube.outputs.enabled == 'true' }} + uses: SonarSource/sonarqube-scan-action@7006c4492b2e0ee0f816d36501671557c97f5995 # v8.1.0 + env: + SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_PROJECT_KEY: ${{ vars.SONAR_PROJECT_KEY }} + with: + args: -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} + - run: npm run knip build: diff --git a/docs/testing.md b/docs/testing.md index e97e0d1..508793d 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -91,3 +91,11 @@ a change pass. Coverage is a guardrail, not a quality score. Critical authentication, token refresh, authorization, and account-management branches should receive direct behavioral tests even when the global threshold is already satisfied. + +## SonarQube + +CI sends the existing LCOV report to SonarQube when the repository has a +`SONAR_TOKEN` secret and `SONAR_PROJECT_KEY` variable. Set `SONAR_HOST_URL` as a +repository variable when using SonarQube Server; SonarQube Cloud uses its +default service URL. The scan waits for the configured quality gate, so a +failed gate fails CI. diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..53b6bd9 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,7 @@ +sonar.sources=src +sonar.tests=src,e2e +sonar.test.inclusions=src/**/*.test.ts,src/**/*.test.tsx,e2e/**/*.spec.ts +sonar.exclusions=src/**/*.d.ts,src/**/*.test.ts,src/**/*.test.tsx,src/test/** +sonar.coverage.exclusions=src/**/*.d.ts,src/**/*.test.ts,src/**/*.test.tsx,src/**/index.ts,src/main.tsx,src/test/**,src/types/** +sonar.javascript.lcov.reportPaths=coverage/lcov.info +sonar.qualitygate.wait=true From 48854d15fc94e48c4516cd3a1e96af3e3598d840 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 19:49:34 +0330 Subject: [PATCH 12/81] test: cover API authentication lifecycle --- src/api/instance.test.ts | 116 +++++++++++++++++++++++++++++++++++++++ vitest.config.ts | 8 +-- 2 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 src/api/instance.test.ts diff --git a/src/api/instance.test.ts b/src/api/instance.test.ts new file mode 100644 index 0000000..0819561 --- /dev/null +++ b/src/api/instance.test.ts @@ -0,0 +1,116 @@ +import { delay, http, HttpResponse } from "msw"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { server } from "@/test/mocks/server"; + +import { apiClient, setUnauthorizedHandler } from "./instance"; +import { clearAccessToken, getAccessToken, setAccessToken } from "./token"; + +afterEach(() => { + clearAccessToken(); + setUnauthorizedHandler(null); +}); + +describe("API client authentication", () => { + it("adds the access token to protected requests", async () => { + setAccessToken("access-token"); + server.use( + http.get("http://localhost/protected", ({ request }) => { + expect(request.headers.get("authorization")).toBe( + "Bearer access-token", + ); + + return HttpResponse.json({ user_id: "user-1" }); + }), + ); + + await expect( + apiClient.get<{ userId: string }>("/protected"), + ).resolves.toEqual({ userId: "user-1" }); + }); + + it("refreshes an expired token and retries the request once", async () => { + let protectedRequestCount = 0; + let refreshRequestCount = 0; + + setAccessToken("expired-token"); + server.use( + http.get("http://localhost/protected", ({ request }) => { + protectedRequestCount += 1; + + if (request.headers.get("authorization") === "Bearer fresh-token") { + return HttpResponse.json({ status: "ready" }); + } + + return HttpResponse.json({ message: "Unauthorized" }, { status: 401 }); + }), + http.post("http://localhost/auth/refresh-token", () => { + refreshRequestCount += 1; + return HttpResponse.json({ access_token: "fresh-token" }); + }), + ); + + await expect( + apiClient.get<{ status: string }>("/protected"), + ).resolves.toEqual({ status: "ready" }); + expect(getAccessToken()).toBe("fresh-token"); + expect(protectedRequestCount).toBe(2); + expect(refreshRequestCount).toBe(1); + }); + + it("shares one token refresh across concurrent unauthorized requests", async () => { + let refreshRequestCount = 0; + + setAccessToken("expired-token"); + server.use( + http.get("http://localhost/protected/:id", ({ request, params }) => { + if (request.headers.get("authorization") === "Bearer fresh-token") { + return HttpResponse.json({ id: params.id }); + } + + return HttpResponse.json({ message: "Unauthorized" }, { status: 401 }); + }), + http.post("http://localhost/auth/refresh-token", async () => { + refreshRequestCount += 1; + await delay(10); + return HttpResponse.json({ access_token: "fresh-token" }); + }), + ); + + await expect( + Promise.all([ + apiClient.get<{ id: string }>("/protected/one"), + apiClient.get<{ id: string }>("/protected/two"), + ]), + ).resolves.toEqual([{ id: "one" }, { id: "two" }]); + expect(refreshRequestCount).toBe(1); + }); + + it("clears authentication once when a shared token refresh fails", async () => { + const onUnauthorized = vi.fn(); + + setAccessToken("expired-token"); + setUnauthorizedHandler(onUnauthorized); + server.use( + http.get("http://localhost/protected/:id", () => + HttpResponse.json({ message: "Unauthorized" }, { status: 401 }), + ), + http.post("http://localhost/auth/refresh-token", async () => { + await delay(10); + return HttpResponse.json( + { message: "Refresh rejected" }, + { status: 401 }, + ); + }), + ); + + const results = await Promise.allSettled([ + apiClient.get("/protected/one"), + apiClient.get("/protected/two"), + ]); + + expect(results.every(({ status }) => status === "rejected")).toBe(true); + expect(getAccessToken()).toBeNull(); + expect(onUnauthorized).toHaveBeenCalledOnce(); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts index a9a48b8..45e0f09 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -22,10 +22,10 @@ export default defineConfig({ provider: "v8", reporter: ["text", "json-summary", "html", "lcov"], thresholds: { - branches: 5, - functions: 5, - lines: 5, - statements: 5, + branches: 10, + functions: 10, + lines: 10, + statements: 10, }, }, environment: "jsdom", From 2a6412d404dd3573d356d088b577873acb20ce98 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 20:34:12 +0330 Subject: [PATCH 13/81] test: cover login journey end to end --- e2e/auth.spec.ts | 72 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/e2e/auth.spec.ts b/e2e/auth.spec.ts index 09665eb..39a5be9 100644 --- a/e2e/auth.spec.ts +++ b/e2e/auth.spec.ts @@ -1,12 +1,15 @@ import { expect, test } from "@playwright/test"; test.describe("public authentication", () => { - test("shows the login form and public account recovery links", async ({ - page, - }) => { + test.beforeEach(async ({ page }) => { await page.addInitScript(() => { localStorage.setItem("language", "en"); }); + }); + + test("shows the login form and public account recovery links", async ({ + page, + }) => { await page.goto("/auth"); await expect(page.getByRole("heading", { name: "Login" })).toBeVisible(); @@ -19,4 +22,67 @@ test.describe("public authentication", () => { page.getByRole("link", { name: "Forgot password?" }), ).toHaveAttribute("href", "/auth/forgot-password"); }); + + test("shows the API error when credentials are rejected", async ({ + page, + }) => { + await page.route("https://api.example.com/auth/login", async (route) => { + expect(route.request().postDataJSON()).toEqual({ + identifier: "ada", + password: "incorrect", + }); + + await route.fulfill({ + contentType: "application/json", + json: { message: "Invalid credentials" }, + status: 401, + }); + }); + await page.goto("/auth"); + + await page.getByLabel("Username, email, or mobile").fill("ada"); + await page.getByLabel("Password").fill("incorrect"); + await page.getByRole("button", { name: "Enter" }).click(); + + await expect(page.getByText("Invalid credentials")).toBeVisible(); + await expect(page).toHaveURL(/\/auth$/); + }); + + test("logs in and loads the authenticated account", async ({ page }) => { + await page.route("https://api.example.com/auth/login", async (route) => { + await route.fulfill({ + contentType: "application/json", + json: { access_token: "access-token" }, + }); + }); + await page.route("https://api.example.com/account/me", async (route) => { + expect(route.request().headers().authorization).toBe( + "Bearer access-token", + ); + + await route.fulfill({ + contentType: "application/json", + json: { + email: "ada@example.com", + first_name: "Ada", + id: "user-1", + is_system_admin: true, + last_name: "Lovelace", + mobile: "09120000000", + permissions: [], + personnel_code: "100", + status: "active", + username: "ada", + }, + }); + }); + await page.goto("/auth"); + + await page.getByLabel("Username, email, or mobile").fill("ada"); + await page.getByLabel("Password").fill("correct-password"); + await page.getByRole("button", { name: "Enter" }).click(); + + await expect(page).toHaveURL(/\/$/); + await expect(page.getByRole("link", { name: "kernel" })).toBeVisible(); + }); }); From 97bbe624d678ef8218e38ca5aef1e40c968b07f1 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 20:37:18 +0330 Subject: [PATCH 14/81] docs: define SonarQube quality gate --- docs/quality-gate.md | 34 ++++++++++++++++++++++++++++++++++ docs/testing.md | 3 ++- sonar-project.properties | 1 + 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 docs/quality-gate.md diff --git a/docs/quality-gate.md b/docs/quality-gate.md new file mode 100644 index 0000000..3780603 --- /dev/null +++ b/docs/quality-gate.md @@ -0,0 +1,34 @@ +# SonarQube Quality Gate + +Kernel follows Clean as You Code. The SonarQube project must use a quality gate +that fails when new code violates any of these conditions: + +| New-code condition | Required value | +| -------------------------- | -------------- | +| Issues | `0` | +| Security Hotspots reviewed | `100%` | +| Coverage | `>= 80%` | +| Duplicated lines | `<= 3%` | + +These conditions apply to new code only. Existing debt should be improved +incrementally instead of weakening the gate for new changes. + +## SonarQube setup + +An administrator must: + +1. Create a custom quality gate based on `Sonar way`. +2. Require no issues on new code. +3. Keep Security Hotspots reviewed at 100%. +4. Keep new-code coverage at 80% or higher. +5. Keep duplicated lines on new code at 3% or lower. +6. Assign the gate to the Kernel project. +7. Define new code using the previous-version or reference-branch strategy. + +The repository disables SonarQube's small-change exception so coverage and +duplication conditions also apply when a change contains fewer than 20 lines. +CI waits for the quality-gate result and fails when the gate fails. + +The remote gate cannot be configured from this repository. It requires +SonarQube project-administrator access and the repository's `SONAR_TOKEN`, +`SONAR_PROJECT_KEY`, and, for SonarQube Server, `SONAR_HOST_URL` settings. diff --git a/docs/testing.md b/docs/testing.md index 508793d..78889dd 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -98,4 +98,5 @@ CI sends the existing LCOV report to SonarQube when the repository has a `SONAR_TOKEN` secret and `SONAR_PROJECT_KEY` variable. Set `SONAR_HOST_URL` as a repository variable when using SonarQube Server; SonarQube Cloud uses its default service URL. The scan waits for the configured quality gate, so a -failed gate fails CI. +failed gate fails CI. See [SonarQube Quality Gate](quality-gate.md) for the +required new-code conditions and project setup. diff --git a/sonar-project.properties b/sonar-project.properties index 53b6bd9..0a04dba 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -4,4 +4,5 @@ sonar.test.inclusions=src/**/*.test.ts,src/**/*.test.tsx,e2e/**/*.spec.ts sonar.exclusions=src/**/*.d.ts,src/**/*.test.ts,src/**/*.test.tsx,src/test/** sonar.coverage.exclusions=src/**/*.d.ts,src/**/*.test.ts,src/**/*.test.tsx,src/**/index.ts,src/main.tsx,src/test/**,src/types/** sonar.javascript.lcov.reportPaths=coverage/lcov.info +sonar.qualitygate.ignoreSmallChanges=false sonar.qualitygate.wait=true From 7c609e7f16b0dd447f0b0b577e9a9183a01d3a53 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 20:38:54 +0330 Subject: [PATCH 15/81] docs: define main branch protection --- docs/branch-protection.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 docs/branch-protection.md diff --git a/docs/branch-protection.md b/docs/branch-protection.md new file mode 100644 index 0000000..26f487e --- /dev/null +++ b/docs/branch-protection.md @@ -0,0 +1,26 @@ +# Main Branch Protection + +The `main` branch is protected on GitHub. Changes must be merged through a pull +request after the branch is current and these checks pass: + +| Required check | Coverage | +| ----------------------------------- | ------------------------------------------------------- | +| `check` | Audit, types, lint, format, tests, SonarQube, dead code | +| `build` | Validated production build | +| `e2e` | Critical browser journeys | +| `Analyze JavaScript and TypeScript` | CodeQL security analysis | + +Protection also: + +- Applies to repository administrators. +- Requires review conversations to be resolved. +- Prevents force-pushes. +- Prevents deletion of `main`. + +An approving review is not required while the repository has only one active +maintainer. Enable at least one approval and Code Owner review when another +maintainer can review pull requests without blocking all development. + +The SonarQube scan is part of the required `check` job. Configure the repository +settings described in [Testing](testing.md#sonarqube) before treating the +quality gate as active. From f0fd67ed0405f4f4b8aa89a5dcca98eb054bdf00 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 20:40:58 +0330 Subject: [PATCH 16/81] test token handling --- src/api/token.test.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/api/token.test.ts diff --git a/src/api/token.test.ts b/src/api/token.test.ts new file mode 100644 index 0000000..e0fd9a9 --- /dev/null +++ b/src/api/token.test.ts @@ -0,0 +1,27 @@ +import { beforeEach, describe, expect, it } from "vitest"; + +import { clearAccessToken, getAccessToken, setAccessToken } from "./token"; + +beforeEach(() => { + clearAccessToken(); +}); + +describe("access token", () => { + it("is absent before a token is set", () => { + expect(getAccessToken()).toBeNull(); + }); + + it("stores and retrieves a token", () => { + setAccessToken("access-token"); + + expect(getAccessToken()).toBe("access-token"); + }); + + it("clears a stored token", () => { + setAccessToken("access-token"); + + clearAccessToken(); + + expect(getAccessToken()).toBeNull(); + }); +}); From 17157111182ab93add193c67a6d367e6d1602201 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 20:43:21 +0330 Subject: [PATCH 17/81] test storage utilities --- .../hooks/useLocalStorageWatcher.test.ts | 50 +++++++++++++++++++ src/storage/preferences.test.ts | 31 ++++++++++++ src/storage/state.test.ts | 28 +++++++++++ 3 files changed, 109 insertions(+) create mode 100644 src/storage/hooks/useLocalStorageWatcher.test.ts create mode 100644 src/storage/preferences.test.ts create mode 100644 src/storage/state.test.ts diff --git a/src/storage/hooks/useLocalStorageWatcher.test.ts b/src/storage/hooks/useLocalStorageWatcher.test.ts new file mode 100644 index 0000000..f18c21a --- /dev/null +++ b/src/storage/hooks/useLocalStorageWatcher.test.ts @@ -0,0 +1,50 @@ +import { act, renderHook } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +import { storageKeys } from "@/config"; + +import { useLocalStorageWatcher } from "./useLocalStorageWatcher"; + +describe("useLocalStorageWatcher", () => { + it("calls the callback when the watched key changes", () => { + const callback = vi.fn(); + renderHook(() => useLocalStorageWatcher(storageKeys.language, callback)); + + act(() => { + window.dispatchEvent( + new StorageEvent("storage", { key: storageKeys.language }), + ); + }); + + expect(callback).toHaveBeenCalledOnce(); + }); + + it("ignores changes to other storage keys", () => { + const callback = vi.fn(); + renderHook(() => useLocalStorageWatcher(storageKeys.theme, callback)); + + act(() => { + window.dispatchEvent( + new StorageEvent("storage", { key: storageKeys.language }), + ); + }); + + expect(callback).not.toHaveBeenCalled(); + }); + + it("stops watching storage events after unmount", () => { + const callback = vi.fn(); + const { unmount } = renderHook(() => + useLocalStorageWatcher(storageKeys.theme, callback), + ); + + unmount(); + act(() => { + window.dispatchEvent( + new StorageEvent("storage", { key: storageKeys.theme }), + ); + }); + + expect(callback).not.toHaveBeenCalled(); + }); +}); diff --git a/src/storage/preferences.test.ts b/src/storage/preferences.test.ts new file mode 100644 index 0000000..ffa898e --- /dev/null +++ b/src/storage/preferences.test.ts @@ -0,0 +1,31 @@ +import { beforeEach, describe, expect, it } from "vitest"; + +import { defaultLanguage, defaultTheme, storageKeys } from "@/config"; + +import { getLanguage, setLanguage } from "./language"; +import { getTheme, setTheme } from "./theme"; + +beforeEach(() => { + localStorage.clear(); +}); + +describe("stored preferences", () => { + it("uses the default language and theme when values are missing", () => { + expect(getLanguage()).toBe(defaultLanguage); + expect(getTheme()).toBe(defaultTheme); + }); + + it("updates the language", () => { + setLanguage("en"); + + expect(getLanguage()).toBe("en"); + expect(localStorage.getItem(storageKeys.language)).toBe('"en"'); + }); + + it("updates the theme", () => { + setTheme("light"); + + expect(getTheme()).toBe("light"); + expect(localStorage.getItem(storageKeys.theme)).toBe('"light"'); + }); +}); diff --git a/src/storage/state.test.ts b/src/storage/state.test.ts new file mode 100644 index 0000000..12eda25 --- /dev/null +++ b/src/storage/state.test.ts @@ -0,0 +1,28 @@ +import { beforeEach, describe, expect, it } from "vitest"; + +import { getState, setState } from "./state"; + +beforeEach(() => { + localStorage.clear(); +}); + +describe("storage state", () => { + it("returns the initial value when the key is missing", () => { + expect(getState("missing", { enabled: true })).toEqual({ enabled: true }); + }); + + it("returns invalid JSON as its raw string value", () => { + localStorage.setItem("invalid", "not-json"); + + expect(getState("invalid", "fallback")).toBe("not-json"); + }); + + it("serializes and restores values", () => { + const value = { enabled: true, retries: 2 }; + + setState("settings", value); + + expect(localStorage.getItem("settings")).toBe(JSON.stringify(value)); + expect(getState("settings", {})).toEqual(value); + }); +}); From 138ce17a2a7b3f0c495cde4967e52d1791e1fb5f Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 20:47:16 +0330 Subject: [PATCH 18/81] test routing permissions --- src/Routes.test.tsx | 13 ++++ src/hooks/useActionPermissions.test.tsx | 83 ++++++++++++++++++++++ src/hooks/useAllowedRoutes.test.tsx | 92 +++++++++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 src/Routes.test.tsx create mode 100644 src/hooks/useActionPermissions.test.tsx create mode 100644 src/hooks/useAllowedRoutes.test.tsx diff --git a/src/Routes.test.tsx b/src/Routes.test.tsx new file mode 100644 index 0000000..9e4ef26 --- /dev/null +++ b/src/Routes.test.tsx @@ -0,0 +1,13 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +describe("application routes", () => { + it("renders the not-found page for an unknown route", async () => { + window.history.replaceState({}, "", "/unknown-route"); + const { Routes } = await import("./Routes"); + + render(); + + expect(screen.getByText("404")).toBeInTheDocument(); + }); +}); diff --git a/src/hooks/useActionPermissions.test.tsx b/src/hooks/useActionPermissions.test.tsx new file mode 100644 index 0000000..6560f87 --- /dev/null +++ b/src/hooks/useActionPermissions.test.tsx @@ -0,0 +1,83 @@ +import { renderHook } from "@testing-library/react"; +import { ReactNode } from "react"; +import { describe, expect, it } from "vitest"; + +import { CoreContext, CoreContextProps } from "@/contexts"; +import { AccountProps } from "@/types"; + +import { useActionPermissions } from "./useActionPermissions"; + +const createUser = (overrides: Partial = {}): AccountProps => ({ + email: null, + firstName: "Test", + id: "user-1", + isSystemAdmin: false, + lastName: "User", + mobile: "09120000000", + permissions: [], + personnelCode: null, + status: "active", + username: "test-user", + ...overrides, +}); + +const renderActionPermissions = (user?: AccountProps) => { + const value: CoreContextProps = { + currentRoute: "root", + language: "en", + setCurrentRoute: () => undefined, + setLanguage: () => undefined, + setTheme: () => undefined, + setUser: () => undefined, + theme: "light", + user, + }; + const wrapper = ({ children }: { children: ReactNode }) => ( + {children} + ); + + return renderHook(useActionPermissions, { wrapper }).result.current; +}; + +describe("useActionPermissions", () => { + it("denies every action for an unauthenticated user", () => { + expect(renderActionPermissions()).toEqual({ + canCreateRoles: false, + canCreateUsers: false, + canDeleteRoles: false, + canDeleteUsers: false, + canUpdateRoles: false, + canUpdateUsers: false, + }); + }); + + it("allows only explicitly granted actions", () => { + expect( + renderActionPermissions( + createUser({ + permissions: ["roles.create", "users.delete", "users.update"], + }), + ), + ).toEqual({ + canCreateRoles: true, + canCreateUsers: false, + canDeleteRoles: false, + canDeleteUsers: true, + canUpdateRoles: false, + canUpdateUsers: true, + }); + }); + + it("allows every action for a system administrator", () => { + expect( + renderActionPermissions(createUser({ isSystemAdmin: true })), + ).toEqual({ + canCreateRoles: true, + canCreateUsers: true, + canDeleteRoles: true, + canDeleteUsers: true, + canUpdateRoles: true, + canUpdateUsers: true, + }); + }); +}); diff --git a/src/hooks/useAllowedRoutes.test.tsx b/src/hooks/useAllowedRoutes.test.tsx new file mode 100644 index 0000000..8e91467 --- /dev/null +++ b/src/hooks/useAllowedRoutes.test.tsx @@ -0,0 +1,92 @@ +import { renderHook } from "@testing-library/react"; +import { ReactNode } from "react"; +import { describe, expect, it } from "vitest"; + +import { CoreContext, CoreContextProps } from "@/contexts"; +import { AccountProps } from "@/types"; + +import { useAllowedRoutes } from "./useAllowedRoutes"; + +const createUser = (overrides: Partial = {}): AccountProps => ({ + email: null, + firstName: "Test", + id: "user-1", + isSystemAdmin: false, + lastName: "User", + mobile: "09120000000", + permissions: [], + personnelCode: null, + status: "active", + username: "test-user", + ...overrides, +}); + +const renderAllowedRoutes = (user?: AccountProps) => { + const value: CoreContextProps = { + currentRoute: "root", + language: "en", + setCurrentRoute: () => undefined, + setLanguage: () => undefined, + setTheme: () => undefined, + setUser: () => undefined, + theme: "light", + user, + }; + const wrapper = ({ children }: { children: ReactNode }) => ( + {children} + ); + + return renderHook(useAllowedRoutes, { wrapper }).result.current; +}; + +describe("useAllowedRoutes", () => { + it("allows only public routes for an unauthenticated user", () => { + const routes = renderAllowedRoutes(); + + expect(routes).toEqual( + new Set(["auth", "forgotPassword", "notFound", "register"]), + ); + }); + + it("allows authenticated routes and routes with granted permissions", () => { + const routes = renderAllowedRoutes( + createUser({ permissions: ["users.read"] }), + ); + + expect(routes).toEqual( + new Set([ + "account", + "auth", + "forgotPassword", + "notFound", + "register", + "root", + "users", + ]), + ); + }); + + it("denies routes when the required permission is absent", () => { + const routes = renderAllowedRoutes(createUser()); + + expect(routes.has("users")).toBe(false); + expect(routes.has("roles")).toBe(false); + }); + + it("allows every route for a system administrator", () => { + const routes = renderAllowedRoutes(createUser({ isSystemAdmin: true })); + + expect(routes).toEqual( + new Set([ + "account", + "auth", + "forgotPassword", + "notFound", + "register", + "roles", + "root", + "users", + ]), + ); + }); +}); From d0ed5c7e233cc133fac469f1cbc988974113552c Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 20:54:46 +0330 Subject: [PATCH 19/81] test API error handling --- src/api/instance.test.ts | 81 ++++++++++++++++++++++++++++++++++++++++ src/api/instance.ts | 2 + 2 files changed, 83 insertions(+) diff --git a/src/api/instance.test.ts b/src/api/instance.test.ts index 0819561..328efe1 100644 --- a/src/api/instance.test.ts +++ b/src/api/instance.test.ts @@ -1,6 +1,8 @@ +import axios, { AxiosError } from "axios"; import { delay, http, HttpResponse } from "msw"; import { afterEach, describe, expect, it, vi } from "vitest"; +import { i18nInstance } from "@/i18n"; import { server } from "@/test/mocks/server"; import { apiClient, setUnauthorizedHandler } from "./instance"; @@ -114,3 +116,82 @@ describe("API client authentication", () => { expect(onUnauthorized).toHaveBeenCalledOnce(); }); }); + +describe("API client errors", () => { + it("transforms structured API errors and preserves their cause", async () => { + server.use( + http.get("http://localhost/invalid-request", () => + HttpResponse.json( + { + cause: { email: "Email is invalid" }, + message: "Validation failed", + }, + { status: 422 }, + ), + ), + ); + + await expect(apiClient.get("/invalid-request")).rejects.toMatchObject({ + cause: { email: "Email is invalid" }, + message: "Validation failed", + }); + }); + + it("uses the unexpected-error message for malformed error responses", async () => { + server.use( + http.get("http://localhost/malformed-error", () => + HttpResponse.json({ message: null }, { status: 500 }), + ), + ); + + await expect(apiClient.get("/malformed-error")).rejects.toThrow( + i18nInstance.t("unexpectedError"), + ); + }); + + it("preserves request cancellation", async () => { + const controller = new AbortController(); + + server.use( + http.get("http://localhost/cancelled", async () => { + await delay(100); + return HttpResponse.json({ status: "ready" }); + }), + ); + + const request = apiClient.get("/cancelled", { + signal: controller.signal, + }); + controller.abort(); + + await expect(request).rejects.toSatisfy((error: unknown) => + axios.isCancel(error), + ); + }); + + it("uses the network-error message when a request times out", async () => { + await expect( + apiClient.get("/slow", { + adapter: (config) => + Promise.reject( + new AxiosError( + "timeout exceeded", + AxiosError.ETIMEDOUT, + config, + {}, + ), + ), + }), + ).rejects.toThrow(i18nInstance.t("networkError")); + }); + + it("uses the network-error message for network failures", async () => { + server.use( + http.get("http://localhost/network-failure", () => HttpResponse.error()), + ); + + await expect(apiClient.get("/network-failure")).rejects.toThrow( + i18nInstance.t("networkError"), + ); + }); +}); diff --git a/src/api/instance.ts b/src/api/instance.ts index ad2dd73..699add3 100644 --- a/src/api/instance.ts +++ b/src/api/instance.ts @@ -73,6 +73,8 @@ api.interceptors.request.use( api.interceptors.response.use( (response) => response, async (error) => { + if (axios.isCancel(error)) return Promise.reject(error); + if ( axios.isAxiosError<{ cause: Record; message: string }>( error, From ab7df50afe1b3b7a4f362f5247d406fcb17e9899 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 20:57:52 +0330 Subject: [PATCH 20/81] test authentication refresh failures --- src/api/instance.test.ts | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/api/instance.test.ts b/src/api/instance.test.ts index 328efe1..d52493d 100644 --- a/src/api/instance.test.ts +++ b/src/api/instance.test.ts @@ -90,6 +90,7 @@ describe("API client authentication", () => { it("clears authentication once when a shared token refresh fails", async () => { const onUnauthorized = vi.fn(); + let refreshRequestCount = 0; setAccessToken("expired-token"); setUnauthorizedHandler(onUnauthorized); @@ -98,6 +99,7 @@ describe("API client authentication", () => { HttpResponse.json({ message: "Unauthorized" }, { status: 401 }), ), http.post("http://localhost/auth/refresh-token", async () => { + refreshRequestCount += 1; await delay(10); return HttpResponse.json( { message: "Refresh rejected" }, @@ -114,6 +116,60 @@ describe("API client authentication", () => { expect(results.every(({ status }) => status === "rejected")).toBe(true); expect(getAccessToken()).toBeNull(); expect(onUnauthorized).toHaveBeenCalledOnce(); + expect(refreshRequestCount).toBe(1); + }); + + it("clears authentication when a single token refresh fails", async () => { + const onUnauthorized = vi.fn(); + let protectedRequestCount = 0; + let refreshRequestCount = 0; + + setAccessToken("expired-token"); + setUnauthorizedHandler(onUnauthorized); + server.use( + http.get("http://localhost/protected", () => { + protectedRequestCount += 1; + return HttpResponse.json({ message: "Unauthorized" }, { status: 401 }); + }), + http.post("http://localhost/auth/refresh-token", () => { + refreshRequestCount += 1; + return HttpResponse.json( + { message: "Refresh rejected" }, + { status: 401 }, + ); + }), + ); + + await expect(apiClient.get("/protected")).rejects.toThrow("Unauthorized"); + expect(protectedRequestCount).toBe(1); + expect(refreshRequestCount).toBe(1); + expect(getAccessToken()).toBeNull(); + expect(onUnauthorized).toHaveBeenCalledOnce(); + }); + + it("does not start another refresh when the retried request is unauthorized", async () => { + const onUnauthorized = vi.fn(); + let protectedRequestCount = 0; + let refreshRequestCount = 0; + + setAccessToken("expired-token"); + setUnauthorizedHandler(onUnauthorized); + server.use( + http.get("http://localhost/protected", () => { + protectedRequestCount += 1; + return HttpResponse.json({ message: "Unauthorized" }, { status: 401 }); + }), + http.post("http://localhost/auth/refresh-token", () => { + refreshRequestCount += 1; + return HttpResponse.json({ access_token: "fresh-token" }); + }), + ); + + await expect(apiClient.get("/protected")).rejects.toThrow("Unauthorized"); + expect(protectedRequestCount).toBe(2); + expect(refreshRequestCount).toBe(1); + expect(getAccessToken()).toBeNull(); + expect(onUnauthorized).toHaveBeenCalledOnce(); }); }); From 0f949109b8204ff0917138c849351a67f7b2c847 Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 21:03:21 +0330 Subject: [PATCH 21/81] test priority forms and pages --- src/pages/pages.test.tsx | 252 +++++++++++++++++++++++++++++++++++++++ src/test/setup.ts | 24 ++++ 2 files changed, 276 insertions(+) create mode 100644 src/pages/pages.test.tsx diff --git a/src/pages/pages.test.tsx b/src/pages/pages.test.tsx new file mode 100644 index 0000000..7b9589b --- /dev/null +++ b/src/pages/pages.test.tsx @@ -0,0 +1,252 @@ +import { within } from "@testing-library/react"; +import { MemoryRouter, useLocation } from "react-router"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import * as api from "@/api"; +import { render, screen } from "@/test/render"; + +import { AccountPage } from "./Account"; +import { ForgotPassPage } from "./ForgotPass"; +import { LoginPage } from "./Login"; +import { RegisterPage } from "./Register"; +import { RolesPage } from "./Roles"; +import { UsersPage } from "./Users"; + +const mocks = vi.hoisted(() => { + const account = { + email: "ada@example.com", + firstName: "Ada", + id: "user-1", + isSystemAdmin: false, + lastName: "Lovelace", + mobile: "09120000000", + permissions: [], + personnelCode: "1001", + status: "active", + username: "ada", + }; + const messageError = vi.fn(); + const messageSuccess = vi.fn(); + const modalConfirm = vi.fn(); + const setFilters = vi.fn(); + const setUser = vi.fn(); + + return { + actionPermissions: { + canCreateRoles: false, + canCreateUsers: false, + canDeleteRoles: false, + canDeleteUsers: false, + canUpdateRoles: false, + canUpdateUsers: false, + }, + account, + antd: { + messageAPI: { + error: messageError, + success: messageSuccess, + }, + modalAPI: { confirm: modalConfirm }, + }, + core: { + setUser, + user: account, + }, + filterState: { + filters: {}, + setFilters, + }, + messageError, + messageSuccess, + modalConfirm, + setFilters, + setUser, + }; +}); + +vi.mock("@/api", () => ({ + changePassword: vi.fn(), + deleteRole: vi.fn(), + deleteUser: vi.fn(), + fetchPermissions: vi.fn(), + fetchRole: vi.fn(), + fetchRoles: vi.fn(), + fetchUser: vi.fn(), + fetchUserRoleOptions: vi.fn(), + fetchUsers: vi.fn(), + fetchUserWorkspaceOptions: vi.fn(), + forgotPassword: vi.fn(), + getAccount: vi.fn(), + login: vi.fn(), + register: vi.fn(), + requestEmailVerification: vi.fn(), + requestOtp: vi.fn(), + updateProfile: vi.fn(), + updateUsername: vi.fn(), + updateUserStatus: vi.fn(), + updateUserSystemAdmin: vi.fn(), + verifyEmail: vi.fn(), +})); + +vi.mock("@/forms/Role", () => ({ RoleForm: () => null })); +vi.mock("@/forms/User", () => ({ UserForm: () => null })); +vi.mock("@/forms/UserPassword", () => ({ UserPasswordForm: () => null })); +vi.mock("@/forms/UserRole", () => ({ UserFormRole: () => null })); +vi.mock("@/forms/UserWorkspace", () => ({ UserWorkspaceForm: () => null })); + +vi.mock("@/hooks", () => ({ + useActionPermissions: () => mocks.actionPermissions, + useAntd: () => mocks.antd, + useCore: () => mocks.core, + useFilterParams: () => mocks.filterState, +})); + +vi.mock("antd-style", () => ({ + useAntdToken: () => ({ + colorSuccess: "#00aa00", + marginMD: 16, + marginXS: 8, + paddingMD: 16, + paddingSM: 8, + screenMD: 768, + screenXL: 1200, + }), +})); + +vi.mock("react-i18next", async (importOriginal) => ({ + ...(await importOriginal()), + useTranslation: () => ({ t: (key: string) => key }), +})); + +const Location = () => { + const location = useLocation(); + return {location.pathname}; +}; + +const renderPage = (page: React.ReactNode) => + render( + + {page} + + , + ); + +beforeEach(() => { + vi.mocked(api.login).mockResolvedValue({ + accessToken: "access-token", + }); + vi.mocked(api.requestOtp).mockResolvedValue({ + expiresIn: 300, + remainingSeconds: 60, + }); + vi.mocked(api.updateProfile).mockResolvedValue(mocks.account); + vi.mocked(api.fetchRoles).mockResolvedValue([]); + vi.mocked(api.fetchUsers).mockResolvedValue({ items: [], total: 0 }); +}); + +describe("priority pages", () => { + it("submits login credentials and navigates home", async () => { + const { user } = renderPage(); + + await user.type(screen.getByLabelText("identifier"), "ada"); + await user.type(screen.getByLabelText("password"), "secret"); + await user.click(screen.getByRole("button", { name: "enter" })); + + expect(api.login).toHaveBeenCalledWith({ + identifier: "ada", + password: "secret", + }); + expect(screen.getByLabelText("location")).toHaveTextContent("/"); + }); + + it("requests a password-recovery OTP for the entered mobile", async () => { + const { user } = renderPage(); + + await user.type(screen.getByLabelText("mobile"), "09120000000"); + await user.click(screen.getByRole("button", { name: "requestOtp" })); + + expect(api.requestOtp).toHaveBeenCalledWith({ + mobile: "09120000000", + purpose: "forgot_password", + }); + expect(mocks.messageSuccess).toHaveBeenCalledWith("otpSent"); + }); + + it("requests a registration OTP for the entered mobile", async () => { + const { user } = renderPage(); + + await user.type(screen.getByLabelText("mobile"), "09120000000"); + await user.click(screen.getByRole("button", { name: "requestOtp" })); + + expect(api.requestOtp).toHaveBeenCalledWith({ + mobile: "09120000000", + purpose: "register", + }); + expect(mocks.messageSuccess).toHaveBeenCalledWith("otpSent"); + }); + + it("updates the account profile", async () => { + const updatedAccount = { ...mocks.account, firstName: "Augusta" }; + vi.mocked(api.updateProfile).mockResolvedValue(updatedAccount); + const { user } = renderPage(); + const firstName = screen.getByLabelText("firstName"); + const profileForm = firstName.closest("form"); + + expect(profileForm).not.toBeNull(); + await user.clear(firstName); + await user.type(firstName, "Augusta"); + await user.click( + within(profileForm!).getByRole("button", { name: "update" }), + ); + + expect(api.updateProfile).toHaveBeenCalledWith({ + firstName: "Augusta", + lastName: "Lovelace", + personnelCode: "1001", + }); + expect(mocks.setUser).toHaveBeenCalledWith(updatedAccount); + expect(mocks.messageSuccess).toHaveBeenCalledWith("profileUpdated"); + }); + + it("loads and displays roles", async () => { + vi.mocked(api.fetchRoles).mockResolvedValue([ + { + id: "role-1", + name: "Operators", + permissions: ["users.read", "users.update"], + }, + ]); + + renderPage(); + + expect(await screen.findByText("Operators")).toBeInTheDocument(); + expect(api.fetchRoles).toHaveBeenCalledOnce(); + }); + + it("loads and displays users", async () => { + vi.mocked(api.fetchUsers).mockResolvedValue({ + items: [ + { + email: "ada@example.com", + firstName: "Ada", + id: "user-1", + isSystemAdmin: false, + lastName: "Lovelace", + mobile: "09120000000", + personnelCode: "1001", + status: "active", + username: "ada", + }, + ], + total: 1, + }); + + renderPage(); + + expect(await screen.findByText("Ada Lovelace")).toBeInTheDocument(); + expect(api.fetchUsers).toHaveBeenCalledWith({ + offset: "0", + size: "12", + }); + }); +}); diff --git a/src/test/setup.ts b/src/test/setup.ts index a6779a2..b7287e9 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -5,6 +5,30 @@ import { afterAll, afterEach, beforeAll } from "vitest"; import { server } from "./mocks/server"; +class ResizeObserverMock implements ResizeObserver { + disconnect() {} + + observe() {} + + unobserve() {} +} + +Object.defineProperty(window, "matchMedia", { + value: (query: string): MediaQueryList => ({ + addEventListener: () => undefined, + addListener: () => undefined, + dispatchEvent: () => false, + matches: false, + media: query, + onchange: null, + removeEventListener: () => undefined, + removeListener: () => undefined, + }), + writable: true, +}); + +globalThis.ResizeObserver = ResizeObserverMock; + beforeAll(() => server.listen({ onUnhandledRequest: "error" })); afterEach(() => { From 67f533bbce6987b99b1f5b722d4f231e3f8973cc Mon Sep 17 00:00:00 2001 From: Sepaseh Date: Thu, 30 Jul 2026 21:07:08 +0330 Subject: [PATCH 22/81] test authentication journeys --- e2e/auth.spec.ts | 188 +++++++++++++++++++++++++++++++++++++--- src/layouts/Default.tsx | 6 +- 2 files changed, 180 insertions(+), 14 deletions(-) diff --git a/e2e/auth.spec.ts b/e2e/auth.spec.ts index 39a5be9..128fbda 100644 --- a/e2e/auth.spec.ts +++ b/e2e/auth.spec.ts @@ -1,4 +1,25 @@ -import { expect, test } from "@playwright/test"; +import { expect, Page, test } from "@playwright/test"; + +const accountResponse = { + email: "ada@example.com", + first_name: "Ada", + id: "user-1", + is_system_admin: true, + last_name: "Lovelace", + mobile: "09120000000", + permissions: [], + personnel_code: "100", + status: "active", + username: "ada", +}; + +const fillOtp = async (page: Page, otp: string) => { + for (const [index, digit] of [...otp].entries()) { + await page + .getByRole("textbox", { name: `OTP Input ${index + 1}` }) + .fill(digit); + } +}; test.describe("public authentication", () => { test.beforeEach(async ({ page }) => { @@ -62,18 +83,7 @@ test.describe("public authentication", () => { await route.fulfill({ contentType: "application/json", - json: { - email: "ada@example.com", - first_name: "Ada", - id: "user-1", - is_system_admin: true, - last_name: "Lovelace", - mobile: "09120000000", - permissions: [], - personnel_code: "100", - status: "active", - username: "ada", - }, + json: accountResponse, }); }); await page.goto("/auth"); @@ -85,4 +95,156 @@ test.describe("public authentication", () => { await expect(page).toHaveURL(/\/$/); await expect(page.getByRole("link", { name: "kernel" })).toBeVisible(); }); + + test("recovers a forgotten password and returns to login", async ({ + page, + }) => { + await page.route( + "https://api.example.com/auth/otp-request", + async (route) => { + expect(route.request().postDataJSON()).toEqual({ + mobile: "09120000000", + purpose: "forgot_password", + }); + await route.fulfill({ + contentType: "application/json", + json: { expires_in: 300, remaining_seconds: 60 }, + }); + }, + ); + await page.route( + "https://api.example.com/auth/forgot-password", + async (route) => { + expect(route.request().postDataJSON()).toEqual({ + mobile: "09120000000", + otp: "123456", + password: "new-password", + }); + await route.fulfill({ status: 204 }); + }, + ); + await page.goto("/auth/forgot-password"); + + await page.getByLabel("Mobile").fill("09120000000"); + await page.getByRole("button", { name: "Send code" }).click(); + await expect(page.getByText("Verification code sent.")).toBeVisible(); + await fillOtp(page, "123456"); + await page.getByLabel("New password").fill("new-password"); + await page.getByLabel("Confirm password").fill("new-password"); + await page.getByRole("button", { name: "Submit" }).click(); + + await expect(page).toHaveURL(/\/auth$/); + }); + + test("registers a new account and enters the application", async ({ + page, + }) => { + await page.route( + "https://api.example.com/auth/otp-request", + async (route) => { + expect(route.request().postDataJSON()).toEqual({ + mobile: "09120000000", + purpose: "register", + }); + await route.fulfill({ + contentType: "application/json", + json: { expires_in: 300, remaining_seconds: 60 }, + }); + }, + ); + await page.route("https://api.example.com/auth/register", async (route) => { + expect(route.request().postDataJSON()).toEqual({ + first_name: "Ada", + last_name: "Lovelace", + mobile: "09120000000", + otp: "123456", + password: "correct-password", + }); + await route.fulfill({ + contentType: "application/json", + json: { access_token: "registered-token" }, + }); + }); + await page.route("https://api.example.com/account/me", async (route) => { + expect(route.request().headers().authorization).toBe( + "Bearer registered-token", + ); + await route.fulfill({ + contentType: "application/json", + json: accountResponse, + }); + }); + await page.goto("/auth/register"); + + await page.getByLabel("First name").fill("Ada"); + await page.getByLabel("Last name").fill("Lovelace"); + await page.getByLabel("Mobile").fill("09120000000"); + await page.getByRole("button", { name: "Send code" }).click(); + await fillOtp(page, "123456"); + await page.getByLabel("Password", { exact: true }).fill("correct-password"); + await page.getByLabel("Confirm password").fill("correct-password"); + await page.getByRole("button", { name: "Register" }).click(); + + await expect(page).toHaveURL(/\/$/); + await expect(page.getByRole("link", { name: "kernel" })).toBeVisible(); + }); + + test("logs out and returns to login", async ({ page }) => { + await page.route("https://api.example.com/auth/login", async (route) => { + await route.fulfill({ + contentType: "application/json", + json: { access_token: "access-token" }, + }); + }); + await page.route("https://api.example.com/account/me", async (route) => { + await route.fulfill({ + contentType: "application/json", + json: accountResponse, + }); + }); + await page.route("https://api.example.com/auth/logout", async (route) => { + expect(route.request().headers().authorization).toBe( + "Bearer access-token", + ); + await route.fulfill({ status: 204 }); + }); + await page.goto("/auth"); + await page.getByLabel("Username, email, or mobile").fill("ada"); + await page.getByLabel("Password").fill("correct-password"); + await page.getByRole("button", { name: "Enter" }).click(); + await expect(page.getByRole("link", { name: "kernel" })).toBeVisible(); + + await page.getByRole("button", { name: "Account" }).click(); + await page.getByRole("menuitem", { name: "Logout" }).click(); + + await expect(page).toHaveURL(/\/auth$/); + await expect(page.getByRole("heading", { name: "Login" })).toBeVisible(); + }); + + test("redirects unauthenticated users away from protected routes", async ({ + page, + }) => { + await page.route("https://api.example.com/account/me", async (route) => { + await route.fulfill({ + contentType: "application/json", + json: { message: "Unauthorized" }, + status: 401, + }); + }); + await page.route( + "https://api.example.com/auth/refresh-token", + async (route) => { + await route.fulfill({ + contentType: "application/json", + json: { message: "Unauthorized" }, + status: 401, + }); + }, + ); + + await page.goto("/users"); + + await expect(page).toHaveURL(/\/auth$/); + await expect(page.getByRole("heading", { name: "Login" })).toBeVisible(); + }); }); diff --git a/src/layouts/Default.tsx b/src/layouts/Default.tsx index 8509cd6..e4c0cce 100644 --- a/src/layouts/Default.tsx +++ b/src/layouts/Default.tsx @@ -160,7 +160,11 @@ export const DefaultLayout = () => { )} > - } style={{ cursor: "pointer" }} /> + + + + } + mask={{ closable: false }} + onClose={onClose} + open={open} + styles={{ footer: { textAlign: "end" } }} + title={title} + > + {children} + + ); +}; diff --git a/src/components/form-drawer/index.ts b/src/components/form-drawer/index.ts new file mode 100644 index 0000000..61b16ca --- /dev/null +++ b/src/components/form-drawer/index.ts @@ -0,0 +1 @@ +export * from "./FormDrawer"; diff --git a/src/components/icon/Icon.tsx b/src/components/icon/Icon.tsx index 7f7034d..061e69a 100644 --- a/src/components/icon/Icon.tsx +++ b/src/components/icon/Icon.tsx @@ -15,9 +15,9 @@ const iconMap = { user: "👤", } as const; -export type IconName = keyof typeof iconMap; +type IconName = keyof typeof iconMap; -export type IconProps = { +type IconProps = { name: IconName; size?: number; }; diff --git a/src/components/icon/index.ts b/src/components/icon/index.ts index 27540cd..65d727b 100644 --- a/src/components/icon/index.ts +++ b/src/components/icon/index.ts @@ -1 +1 @@ -export { Icon, type IconName, type IconProps } from "./Icon"; +export { Icon } from "./Icon"; diff --git a/src/components/otp-input/OtpInput.tsx b/src/components/otp-input/OtpInput.tsx index 0fbf54d..0f01f18 100644 --- a/src/components/otp-input/OtpInput.tsx +++ b/src/components/otp-input/OtpInput.tsx @@ -3,7 +3,7 @@ import { ComponentProps, FC } from "react"; const Otp = Input.OTP; -export type OtpInputProps = ComponentProps; +type OtpInputProps = ComponentProps; export const OtpInput: FC = (props) => { return ; diff --git a/src/components/otp-input/index.ts b/src/components/otp-input/index.ts index 963a2a6..053decd 100644 --- a/src/components/otp-input/index.ts +++ b/src/components/otp-input/index.ts @@ -1 +1 @@ -export { OtpInput, type OtpInputProps } from "./OtpInput"; +export { OtpInput } from "./OtpInput"; diff --git a/src/components/password-fields/PasswordFields.tsx b/src/components/password-fields/PasswordFields.tsx new file mode 100644 index 0000000..eaaca82 --- /dev/null +++ b/src/components/password-fields/PasswordFields.tsx @@ -0,0 +1,51 @@ +import { Form, Input } from "antd"; +import { useTranslation } from "react-i18next"; + +type PasswordFieldsProps = { + passwordLabel?: "newPass" | "password"; + size?: "large"; +}; + +export const PasswordFields = ({ + passwordLabel = "password", + size, +}: PasswordFieldsProps) => { + const { t } = useTranslation(); + + return ( + <> + + + + ({ + validator(_, value) { + return !value || getFieldValue("password") === value + ? Promise.resolve() + : Promise.reject(new Error(t("passsMismatch"))); + }, + }), + ]} + > + + + + ); +}; diff --git a/src/components/password-fields/index.ts b/src/components/password-fields/index.ts new file mode 100644 index 0000000..4bf905b --- /dev/null +++ b/src/components/password-fields/index.ts @@ -0,0 +1 @@ +export * from "./PasswordFields"; diff --git a/src/forms/role/Role.tsx b/src/forms/role/Role.tsx index ddebbaa..10b3c67 100644 --- a/src/forms/role/Role.tsx +++ b/src/forms/role/Role.tsx @@ -1,25 +1,17 @@ /* eslint-disable react-hooks/set-state-in-effect */ -import { - Button, - Checkbox, - Divider, - Drawer, - Flex, - Form, - Input, - Space, -} from "antd"; +import { Checkbox, Divider, Flex, Form, Input } from "antd"; import { FC, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useLocation } from "react-router"; import { createRole, updateRole } from "@/api"; +import { FormDrawer } from "@/components/form-drawer"; import { modalKeys } from "@/config"; import { useAntd, useGoBack } from "@/hooks"; import { PermissionGroupProps, RoleMutationParams, RoleProps } from "@/types"; import { getErrorMessage } from "@/utils"; -export type RoleFormProps = { +type RoleFormProps = { data?: RoleProps; onFinish: () => void; options: { permissions: PermissionGroupProps[] }; @@ -80,29 +72,14 @@ export const RoleForm: FC = ({ }, [data, form, goBack, hash, open]); return ( - { if (isOpen) form.focusField("name"); }} - closeIcon={false} - footer={ - - - - - } - mask={{ closable: false }} - onClose={() => goBack()} + onClose={goBack} + onSubmit={() => form.submit()} open={open} - styles={{ footer: { textAlign: "end" } }} + submitting={submitting} title={t(isUpdate ? "update" : "create")} > @@ -140,6 +117,6 @@ export const RoleForm: FC = ({ - + ); }; diff --git a/src/forms/role/index.ts b/src/forms/role/index.ts index 9e1e429..0a9c9bc 100644 --- a/src/forms/role/index.ts +++ b/src/forms/role/index.ts @@ -1 +1 @@ -export { RoleForm, type RoleFormProps } from "./Role"; +export { RoleForm } from "./Role"; diff --git a/src/forms/user-password/UserPassword.tsx b/src/forms/user-password/UserPassword.tsx index a58a0b3..bcd5c7a 100644 --- a/src/forms/user-password/UserPassword.tsx +++ b/src/forms/user-password/UserPassword.tsx @@ -1,10 +1,12 @@ /* eslint-disable react-hooks/set-state-in-effect */ -import { Button, Drawer, Form, Input, Space } from "antd"; +import { Form } from "antd"; import { FC, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useLocation } from "react-router"; import { updateUserPassword } from "@/api"; +import { FormDrawer } from "@/components/form-drawer"; +import { PasswordFields } from "@/components/password-fields"; import { modalKeys } from "@/config"; import { useAntd, useGoBack } from "@/hooks"; import { UserPasswordParams, UserProps } from "@/types"; @@ -14,7 +16,7 @@ type UserPasswordFormParams = UserPasswordParams & { confirmPassword: string; }; -export type UserPasswordFormProps = { +type UserPasswordFormProps = { data?: UserProps; }; @@ -54,26 +56,11 @@ export const UserPasswordForm: FC = ({ data }) => { }, [data, form, goBack, hash, open]); return ( - - - - - } - mask={{ closable: false }} - onClose={() => goBack()} + form.submit()} open={open} - styles={{ footer: { textAlign: "end" } }} + submitting={submitting} title={t("changePassword")} > @@ -81,33 +68,8 @@ export const UserPasswordForm: FC = ({ data }) => { layout="vertical" onFinish={(values) => void handleSubmit(values)} > - - - - ({ - validator(_, value) { - if (!value || getFieldValue("password") === value) { - return Promise.resolve(); - } - - return Promise.reject(new Error(t("passsMismatch"))); - }, - }), - ]} - > - - + - + ); }; diff --git a/src/forms/user-password/index.ts b/src/forms/user-password/index.ts index e6d386c..8838a47 100644 --- a/src/forms/user-password/index.ts +++ b/src/forms/user-password/index.ts @@ -1 +1 @@ -export { UserPasswordForm, type UserPasswordFormProps } from "./UserPassword"; +export { UserPasswordForm } from "./UserPassword"; diff --git a/src/forms/user-role/UserRole.tsx b/src/forms/user-role/UserRole.tsx index 7a77ae7..e49fba6 100644 --- a/src/forms/user-role/UserRole.tsx +++ b/src/forms/user-role/UserRole.tsx @@ -1,16 +1,17 @@ /* eslint-disable react-hooks/set-state-in-effect */ -import { Button, Drawer, Form, Select, Space } from "antd"; +import { Form, Select } from "antd"; import { FC, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useLocation } from "react-router"; import { updateUserRoles } from "@/api"; +import { FormDrawer } from "@/components/form-drawer"; import { modalKeys } from "@/config"; import { useAntd, useGoBack } from "@/hooks"; import { UserOptionProps, UserProps, UserRoleParams } from "@/types"; import { getErrorMessage } from "@/utils"; -export type UserFormRoleProps = { +type UserFormRoleProps = { data?: UserProps; onFinish: () => void; options: { roles: UserOptionProps[] }; @@ -61,27 +62,12 @@ export const UserFormRole: FC = ({ }, [data, form, goBack, hash, open]); return ( - - - - - } - mask={{ closable: false }} - onClose={() => goBack()} + form.submit()} open={open} + submitting={submitting} title={t("roles")} - styles={{ footer: { textAlign: "end" } }} > form={form} @@ -98,6 +84,6 @@ export const UserFormRole: FC = ({ /> - + ); }; diff --git a/src/forms/user-role/index.ts b/src/forms/user-role/index.ts index 5fa787b..1ab45a7 100644 --- a/src/forms/user-role/index.ts +++ b/src/forms/user-role/index.ts @@ -1 +1 @@ -export { UserFormRole, type UserFormRoleProps } from "./UserRole"; +export { UserFormRole } from "./UserRole"; diff --git a/src/forms/user-workspace/UserWorkspace.tsx b/src/forms/user-workspace/UserWorkspace.tsx index e96667c..5da0a94 100644 --- a/src/forms/user-workspace/UserWorkspace.tsx +++ b/src/forms/user-workspace/UserWorkspace.tsx @@ -1,16 +1,17 @@ /* eslint-disable react-hooks/set-state-in-effect */ -import { Button, Drawer, Form, Select, Space } from "antd"; +import { Form, Select } from "antd"; import { FC, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useLocation } from "react-router"; import { updateUserWorkspaces } from "@/api"; +import { FormDrawer } from "@/components/form-drawer"; import { modalKeys } from "@/config"; import { useAntd, useGoBack } from "@/hooks"; import { UserOptionProps, UserProps, UserWorkspaceParams } from "@/types"; import { getErrorMessage } from "@/utils"; -export type UserWorkspaceFormProps = { +type UserWorkspaceFormProps = { data?: UserProps; onFinish: () => void; options: { workspaces: UserOptionProps[] }; @@ -61,27 +62,12 @@ export const UserWorkspaceForm: FC = ({ }, [data, form, goBack, hash, open]); return ( - - - - - } - mask={{ closable: false }} - onClose={() => goBack()} + form.submit()} open={open} + submitting={submitting} title={t("workspaces")} - styles={{ footer: { textAlign: "end" } }} > form={form} @@ -98,6 +84,6 @@ export const UserWorkspaceForm: FC = ({ /> - + ); }; diff --git a/src/forms/user-workspace/index.ts b/src/forms/user-workspace/index.ts index 38d8568..a7e40a9 100644 --- a/src/forms/user-workspace/index.ts +++ b/src/forms/user-workspace/index.ts @@ -1,4 +1 @@ -export { - UserWorkspaceForm, - type UserWorkspaceFormProps, -} from "./UserWorkspace"; +export { UserWorkspaceForm } from "./UserWorkspace"; diff --git a/src/forms/user/User.tsx b/src/forms/user/User.tsx index ce979cf..fa40265 100644 --- a/src/forms/user/User.tsx +++ b/src/forms/user/User.tsx @@ -1,11 +1,13 @@ /* eslint-disable react-hooks/set-state-in-effect */ -import { Button, Drawer, Form, Input, Space } from "antd"; +import { Form, Input } from "antd"; import { FC, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useLocation } from "react-router"; import { createUser, updateUser } from "@/api"; import { DigitsInput } from "@/components/digits-input"; +import { FormDrawer } from "@/components/form-drawer"; +import { PasswordFields } from "@/components/password-fields"; import { modalKeys } from "@/config"; import { useAntd, useGoBack } from "@/hooks"; import { CreateUserParams, UpdateUserParams, UserProps } from "@/types"; @@ -15,7 +17,7 @@ type UserFormParams = CreateUserParams & { confirmPassword: string; }; -export type UserFormProps = { +type UserFormProps = { data?: UserProps; onFinish: () => void; }; @@ -84,27 +86,12 @@ export const UserForm: FC = ({ data, onFinish }) => { }, [data, form, goBack, hash, open]); return ( - - - - - } - mask={{ closable: false }} - onClose={() => goBack()} + form.submit()} open={open} + submitting={submitting} title={t(isUpdate ? "update" : "create")} - styles={{ footer: { textAlign: "end" } }} > form={form} @@ -141,35 +128,10 @@ export const UserForm: FC = ({ data, onFinish }) => { {!isUpdate && ( <> - - - - ({ - validator(_, value) { - if (!value || getFieldValue("password") === value) { - return Promise.resolve(); - } - - return Promise.reject(new Error(t("passsMismatch"))); - }, - }), - ]} - > - - + )} - + ); }; diff --git a/src/forms/user/index.ts b/src/forms/user/index.ts index 376991c..9066b86 100644 --- a/src/forms/user/index.ts +++ b/src/forms/user/index.ts @@ -1 +1 @@ -export { UserForm, type UserFormProps } from "./User"; +export { UserForm } from "./User"; diff --git a/src/hooks/use-action-permissions/index.ts b/src/hooks/use-action-permissions/index.ts index 0e3a17e..4fe5e63 100644 --- a/src/hooks/use-action-permissions/index.ts +++ b/src/hooks/use-action-permissions/index.ts @@ -1,4 +1 @@ -export { - type ActionPermissions, - useActionPermissions, -} from "./useActionPermissions"; +export { useActionPermissions } from "./useActionPermissions"; diff --git a/src/hooks/use-action-permissions/useActionPermissions.ts b/src/hooks/use-action-permissions/useActionPermissions.ts index d28d1f8..1ad82bc 100644 --- a/src/hooks/use-action-permissions/useActionPermissions.ts +++ b/src/hooks/use-action-permissions/useActionPermissions.ts @@ -2,7 +2,7 @@ import { useMemo } from "react"; import { useCore } from "@/hooks/use-core"; -export type ActionPermissions = { +type ActionPermissions = { canCreateRoles: boolean; canCreateUsers: boolean; canDeleteRoles: boolean; diff --git a/src/pages/forgot-pass/ForgotPass.tsx b/src/pages/forgot-pass/ForgotPass.tsx index dfe57a0..0005f62 100644 --- a/src/pages/forgot-pass/ForgotPass.tsx +++ b/src/pages/forgot-pass/ForgotPass.tsx @@ -1,4 +1,4 @@ -import { Button, Flex, Form, FormProps, Input, Typography } from "antd"; +import { Button, Flex, Form, FormProps, Typography } from "antd"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Link, useNavigate } from "react-router"; @@ -7,6 +7,7 @@ import { forgotPassword, requestOtp } from "@/api"; import { DigitsInput } from "@/components/digits-input"; import { Icon } from "@/components/icon"; import { OtpInput } from "@/components/otp-input"; +import { PasswordFields } from "@/components/password-fields"; import { routeTree } from "@/config"; import { useAntd } from "@/hooks"; import { ForgotPasswordParams } from "@/types"; @@ -130,40 +131,7 @@ export const ForgotPassPage = () => { - - label={t("newPass")} - name="password" - rules={[{ required: true }]} - > - - - - dependencies={["password"]} - label={t("confirmPass")} - name="confirmPassword" - rules={[ - { required: true }, - ({ getFieldValue }) => ({ - validator(_, value) { - if (!value || getFieldValue("password") === value) { - return Promise.resolve(); - } - - return Promise.reject(new Error(t("passsMismatch"))); - }, - }), - ]} - > - - + - - label={t("password")} - name="password" - rules={[{ required: true }]} - > - - - - dependencies={["password"]} - label={t("confirmPass")} - name="confirmPassword" - rules={[ - { required: true }, - ({ getFieldValue }) => ({ - validator(_, value) { - if (!value || getFieldValue("password") === value) { - return Promise.resolve(); - } - - return Promise.reject(new Error(t("passsMismatch"))); - }, - }), - ]} - > - - + } + extra={ + + + + } /> ); }; diff --git a/src/pages/roles/Roles.tsx b/src/pages/roles/Roles.tsx index 73ba7df..3fbfbe5 100644 --- a/src/pages/roles/Roles.tsx +++ b/src/pages/roles/Roles.tsx @@ -150,8 +150,12 @@ export const RolesPage = () => { ".ant-table-content", ); - if (scrollRegion) scrollRegion.tabIndex = 0; - }, [data]); + if (scrollRegion) { + scrollRegion.tabIndex = 0; + scrollRegion.setAttribute("role", "region"); + scrollRegion.setAttribute("aria-label", t("roles")); + } + }, [data, t]); return ( <>