diff --git a/.all-contributorsrc b/.all-contributorsrc index 145fd60415..5c3deaf428 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1,476 +1,371 @@ { - "files": [ - "README.md" - ], - "imageSize": 100, - "commit": false, - "contributors": [ - { - "login": "sergeysova", - "name": "Sergey Sova", - "avatar_url": "https://avatars.githubusercontent.com/u/5620073?v=4", - "profile": "https://sova.dev/", - "contributions": [ - "blog", - "doc", - "example", - "ideas", - "projectManagement", - "question", - "infra", - "research", - "eventOrganizing", - "tutorial", - "talk", - "maintenance" - ] - }, - { - "login": "azinit", - "name": "Ilya Azin", - "avatar_url": "https://avatars.githubusercontent.com/u/42924400?v=4", - "profile": "https://t.me/ilya_azin", - "contributions": [ - "doc", - "example", - "ideas", - "projectManagement", - "question", - "review", - "infra", - "userTesting", - "design", - "tutorial", - "talk", - "maintenance" - ] - }, - { - "login": "Rin-Akaia-eth", - "name": "Rin 🦊🪐😈 Akaia", - "avatar_url": "https://avatars.githubusercontent.com/u/8805308?v=4", - "profile": "https://github.com/Rin-Akaia-eth", - "contributions": [ - "doc", - "content", - "ideas", - "question", - "translation", - "talk", - "maintenance", - "research" - ] - }, - { - "login": "AlexandrHoroshih", - "name": "Alexander Khoroshikh", - "avatar_url": "https://avatars.githubusercontent.com/u/32790736?v=4", - "profile": "https://github.com/AlexandrHoroshih", - "contributions": [ - "doc", - "ideas", - "question", - "review", - "tool", - "security", - "talk", - "tutorial", - "maintenance" - ] - }, - { - "login": "unordinarity", - "name": "Bear Raytracer", - "avatar_url": "https://avatars.githubusercontent.com/u/23265008?v=4", - "profile": "https://github.com/unordinarity", - "contributions": [ - "doc", - "example", - "ideas", - "question", - "review", - "translation", - "design", - "infra", - "maintenance" - ] - }, - { - "login": "spotsccc", - "name": "spotsccc", - "avatar_url": "https://avatars.githubusercontent.com/u/80784519?v=4", - "profile": "https://github.com/spotsccc", - "contributions": [ - "doc", - "example", - "ideas", - "question", - "review", - "maintenance" - ] - }, - { - "login": "ilyaagarkov", - "name": "Ilya", - "avatar_url": "https://avatars.githubusercontent.com/u/10822601?v=4", - "profile": "https://github.com/ilyaagarkov", - "contributions": [ - "doc", - "ideas", - "talk", - "maintenance" - ] - }, - { - "login": "binjospookie", - "name": "Viktor Pasynok", - "avatar_url": "https://avatars.githubusercontent.com/u/8722478?v=4", - "profile": "https://binjo.ru/", - "contributions": [ - "doc", - "ideas", - "projectManagement", - "talk" - ] - }, - { - "login": "OlegBrony", - "name": "Oleh", - "avatar_url": "https://avatars.githubusercontent.com/u/19880334?v=4", - "profile": "https://github.com/OlegBrony", - "contributions": [ - "doc", - "ideas", - "tutorial" - ] - }, - { - "login": "niyazm524", - "name": "Niyaz", - "avatar_url": "https://avatars.githubusercontent.com/u/32315145?v=4", - "profile": "https://github.com/niyazm524", - "contributions": [ - "example", - "userTesting" - ] - }, - { - "login": "epodgaetskiy", - "name": "Evgeniy Podgaetskiy", - "avatar_url": "https://avatars.githubusercontent.com/u/15031623?v=4", - "profile": "https://start.reactwarriors.com/join", - "contributions": [ - "ideas" - ] - }, - { - "login": "Postamentovich", - "name": "Viacheslav Zinovev", - "avatar_url": "https://avatars.githubusercontent.com/u/22918007?v=4", - "profile": "https://github.com/Postamentovich", - "contributions": [ - "design", - "userTesting", - "review" - ] - }, - { - "login": "GhostMayor", - "name": "Alexandr", - "avatar_url": "https://avatars.githubusercontent.com/u/25086934?v=4", - "profile": "https://vk.com/id29842440", - "contributions": [ - "ideas", - "userTesting", - "review" - ] - }, - { - "login": "kof", - "name": "Oleg Isonen", - "avatar_url": "https://avatars.githubusercontent.com/u/52824?v=4", - "profile": "https://medium.com/@oleg008", - "contributions": [ - "ideas", - "research", - "userTesting" - ] - }, - { - "login": "Krakazybik", - "name": "Evgeniy", - "avatar_url": "https://avatars.githubusercontent.com/u/1334019?v=4", - "profile": "https://t.me/krakazybik", - "contributions": [ - "code", - "plugin", - "tool" - ] - }, - { - "login": "illright", - "name": "Lev Chelyadinov", - "avatar_url": "https://avatars.githubusercontent.com/u/15035286?v=4", - "profile": "https://github.com/illright", - "contributions": [ - "doc", - "content", - "ideas", - "design" - ] - }, - { - "login": "tednaaa", - "name": "And", - "avatar_url": "https://avatars.githubusercontent.com/u/79831859?v=4", - "profile": "https://github.com/tednaaa", - "contributions": [ - "infra", - "doc", - "code" - ] - }, - { - "login": "sarmong", - "name": "sarmong", - "avatar_url": "https://avatars.githubusercontent.com/u/42828375?v=4", - "profile": "https://github.com/sarmong", - "contributions": [ - "doc", - "translation" - ] - }, - { - "login": "julieobolenskaya", - "name": "Julie Obolenskaya", - "avatar_url": "https://avatars.githubusercontent.com/u/80626513?v=4", - "profile": "https://github.com/julieobolenskaya", - "contributions": [ - "translation" - ] - }, - { - "login": "Imperyall", - "name": "Roman Tikhiy", - "avatar_url": "https://avatars.githubusercontent.com/u/24413052?v=4", - "profile": "https://github.com/Imperyall", - "contributions": [ - "userTesting", - "doc" - ] - }, - { - "login": "igorkamyshev", - "name": "Igor Kamyshev", - "avatar_url": "https://avatars.githubusercontent.com/u/26767722?v=4", - "profile": "https://kamyshev.me/", - "contributions": [ - "bug", - "doc" - ] - }, - { - "login": "GTech1256", - "name": "Roman", - "avatar_url": "https://avatars.githubusercontent.com/u/18086485?v=4", - "profile": "https://gtech1256.github.io/PersonalPage/", - "contributions": [ - "userTesting", - "doc" - ] - }, - { - "login": "websega", - "name": "Sergey Vakhramov", - "avatar_url": "https://avatars.githubusercontent.com/u/56861782?v=4", - "profile": "https://github.com/websega", - "contributions": [ - "design" - ] - }, - { - "login": "mark-omarov", - "name": "Mark Omarov", - "avatar_url": "https://avatars.githubusercontent.com/u/15357910?v=4", - "profile": "https://github.com/mark-omarov", - "contributions": [ - "doc" - ] - }, - { - "login": "skrylnikov", - "name": "Дмитрий", - "avatar_url": "https://avatars.githubusercontent.com/u/9007486?v=4", - "profile": "https://dskr.dev/", - "contributions": [ - "business", - "userTesting" - ] - }, - { - "login": "Mihir50", - "name": "Mihir Shah", - "avatar_url": "https://avatars.githubusercontent.com/u/58292449?v=4", - "profile": "https://www.leetcode.com/Mihir64", - "contributions": [ - "design" - ] - }, - { - "login": "GlebHihoho", - "name": "Gleb", - "avatar_url": "https://avatars.githubusercontent.com/u/17951143?v=4", - "profile": "https://github.com/GlebHihoho", - "contributions": [ - "doc" - ] - }, - { - "login": "yesnoruly", - "name": "Roma Karvacky", - "avatar_url": "https://avatars.githubusercontent.com/u/64963734?v=4", - "profile": "https://github.com/yesnoruly", - "contributions": [ - "example" - ] - }, - { - "login": "oas89", - "name": "Aleksandr Osipov", - "avatar_url": "https://avatars.githubusercontent.com/u/5285065?v=4", - "profile": "https://github.com/oas89", - "contributions": [ - "userTesting" - ] - }, - { - "login": "mg901", - "name": "Maxim", - "avatar_url": "https://avatars.githubusercontent.com/u/7874664?v=4", - "profile": "https://t.me/mg901", - "contributions": [ - "userTesting" - ] - }, - { - "login": "Kelin2025", - "name": "Anton Kosykh", - "avatar_url": "https://avatars.githubusercontent.com/u/4208480?v=4", - "profile": "https://github.com/Kelin2025", - "contributions": [ - "userTesting" - ] - }, - { - "login": "samelm", - "name": "Vladislav Samatov", - "avatar_url": "https://avatars.githubusercontent.com/u/9926019?v=4", - "profile": "https://github.com/samelm", - "contributions": [ - "userTesting" - ] - }, - { - "login": "olegKusov", - "name": "Oleg Kusov", - "avatar_url": "https://avatars.githubusercontent.com/u/28058268?v=4", - "profile": "https://github.com/olegKusov", - "contributions": [ - "blog", - "userTesting" - ] - }, - { - "login": "sandrig", - "name": "Andrey Savelev", - "avatar_url": "https://avatars.githubusercontent.com/u/11439304?v=4", - "profile": "https://andreysavelev.com/", - "contributions": [ - "userTesting" - ] - }, - { - "login": "tavriaforever", - "name": "Nickolay Ilchenko", - "avatar_url": "https://avatars.githubusercontent.com/u/975906?v=4", - "profile": "http://twitter/tavriaforever", - "contributions": [ - "userTesting", - "eventOrganizing" - ] - }, - { - "login": "ledeneveugene", - "name": "Eugene Ledenev", - "avatar_url": "https://avatars.githubusercontent.com/u/51231845?v=4", - "profile": "https://github.com/ledeneveugene", - "contributions": [ - "data" - ] - }, - { - "login": "vladislavromanov", - "name": "Vladislav Romanov", - "avatar_url": "https://avatars.githubusercontent.com/u/63917524?v=4", - "profile": "https://github.com/vladislavromanov", - "contributions": [ - "data" - ] - }, - { - "login": "ainursharaev", - "name": "Ainur", - "avatar_url": "https://avatars.githubusercontent.com/u/33234903?v=4", - "profile": "https://github.com/ainursharaev", - "contributions": [ - "doc" - ] - }, - { - "login": "EliseyMartynov", - "name": "Elisey Martynov", - "avatar_url": "https://avatars.githubusercontent.com/u/66368523?v=4", - "profile": "https://github.com/EliseyMartynov", - "contributions": [ - "example" - ] - }, - { - "login": "baushonok", - "name": "Olga Pasynok", - "avatar_url": "https://avatars.githubusercontent.com/u/9272905?v=4", - "profile": "https://github.com/baushonok", - "contributions": [ - "eventOrganizing" - ] - }, - { - "login": "Affiction", - "name": "Max Kokosha", - "avatar_url": "https://avatars.githubusercontent.com/u/9825305?v=4", - "profile": "https://github.com/Affiction", - "contributions": [ - "example" - ] - }, - { - "login": "Zukhrik", - "name": "Зухриддин Камильжанов", - "avatar_url": "https://avatars.githubusercontent.com/u/67275391?v=4", - "profile": "https://github.com/Zukhrik", - "contributions": [ - "translation", - "promotion", - "doc" - ] - } - ], - "contributorsPerLine": 5, - "projectName": "documentation", - "projectOwner": "feature-sliced", - "repoType": "github", - "repoHost": "https://github.com", - "skipCi": true, - "commitConvention": "none", - "commitType": "docs" + "files": ["README.md"], + "imageSize": 100, + "commit": false, + "contributors": [ + { + "login": "sergeysova", + "name": "Sergey Sova", + "avatar_url": "https://avatars.githubusercontent.com/u/5620073?v=4", + "profile": "https://sergeysova.com/", + "contributions": [ + "blog", + "doc", + "example", + "ideas", + "projectManagement", + "question", + "infra", + "research", + "eventOrganizing", + "tutorial", + "talk", + "maintenance" + ] + }, + { + "login": "azinit", + "name": "Ilya Azin", + "avatar_url": "https://avatars.githubusercontent.com/u/42924400?v=4", + "profile": "https://t.me/ilya_azin", + "contributions": [ + "doc", + "example", + "ideas", + "projectManagement", + "question", + "review", + "infra", + "userTesting", + "design", + "tutorial", + "talk", + "maintenance" + ] + }, + { + "login": "Rin-Akaia-eth", + "name": "Rin 🦊🪐😈 Akaia", + "avatar_url": "https://avatars.githubusercontent.com/u/8805308?v=4", + "profile": "https://github.com/Rin-Akaia-eth", + "contributions": [ + "doc", + "content", + "ideas", + "question", + "translation", + "talk", + "maintenance", + "research" + ] + }, + { + "login": "AlexandrHoroshih", + "name": "Alexander Khoroshikh", + "avatar_url": "https://avatars.githubusercontent.com/u/32790736?v=4", + "profile": "https://github.com/AlexandrHoroshih", + "contributions": [ + "doc", + "ideas", + "question", + "review", + "tool", + "security", + "talk", + "tutorial", + "maintenance" + ] + }, + { + "login": "unordinarity", + "name": "Bear Raytracer", + "avatar_url": "https://avatars.githubusercontent.com/u/23265008?v=4", + "profile": "https://github.com/unordinarity", + "contributions": [ + "doc", + "example", + "ideas", + "question", + "review", + "translation", + "design", + "infra", + "maintenance" + ] + }, + { + "login": "spotsccc", + "name": "spotsccc", + "avatar_url": "https://avatars.githubusercontent.com/u/80784519?v=4", + "profile": "https://github.com/spotsccc", + "contributions": [ + "doc", + "example", + "ideas", + "question", + "review", + "maintenance" + ] + }, + { + "login": "ilyaagarkov", + "name": "Ilya", + "avatar_url": "https://avatars.githubusercontent.com/u/10822601?v=4", + "profile": "https://github.com/ilyaagarkov", + "contributions": ["doc", "ideas", "talk", "maintenance"] + }, + { + "login": "binjospookie", + "name": "Viktor Pasynok", + "avatar_url": "https://avatars.githubusercontent.com/u/8722478?v=4", + "profile": "https://binjo.ru/", + "contributions": ["doc", "ideas", "projectManagement", "talk"] + }, + { + "login": "OlegBrony", + "name": "Oleh", + "avatar_url": "https://avatars.githubusercontent.com/u/19880334?v=4", + "profile": "https://github.com/OlegBrony", + "contributions": ["doc", "ideas", "tutorial"] + }, + { + "login": "niyazm524", + "name": "Niyaz", + "avatar_url": "https://avatars.githubusercontent.com/u/32315145?v=4", + "profile": "https://github.com/niyazm524", + "contributions": ["example", "userTesting"] + }, + { + "login": "epodgaetskiy", + "name": "Evgeniy Podgaetskiy", + "avatar_url": "https://avatars.githubusercontent.com/u/15031623?v=4", + "profile": "https://start.reactwarriors.com/join", + "contributions": ["ideas"] + }, + { + "login": "Postamentovich", + "name": "Viacheslav Zinovev", + "avatar_url": "https://avatars.githubusercontent.com/u/22918007?v=4", + "profile": "https://github.com/Postamentovich", + "contributions": ["design", "userTesting", "review"] + }, + { + "login": "GhostMayor", + "name": "Alexandr", + "avatar_url": "https://avatars.githubusercontent.com/u/25086934?v=4", + "profile": "https://vk.com/id29842440", + "contributions": ["ideas", "userTesting", "review"] + }, + { + "login": "kof", + "name": "Oleg Isonen", + "avatar_url": "https://avatars.githubusercontent.com/u/52824?v=4", + "profile": "https://medium.com/@oleg008", + "contributions": ["ideas", "research", "userTesting"] + }, + { + "login": "Krakazybik", + "name": "Evgeniy", + "avatar_url": "https://avatars.githubusercontent.com/u/1334019?v=4", + "profile": "https://t.me/krakazybik", + "contributions": ["code", "plugin", "tool"] + }, + { + "login": "illright", + "name": "Lev Chelyadinov", + "avatar_url": "https://avatars.githubusercontent.com/u/15035286?v=4", + "profile": "https://github.com/illright", + "contributions": ["doc", "content", "ideas", "design"] + }, + { + "login": "tednaaa", + "name": "And", + "avatar_url": "https://avatars.githubusercontent.com/u/79831859?v=4", + "profile": "https://github.com/tednaaa", + "contributions": ["infra", "doc", "code"] + }, + { + "login": "sarmong", + "name": "sarmong", + "avatar_url": "https://avatars.githubusercontent.com/u/42828375?v=4", + "profile": "https://github.com/sarmong", + "contributions": ["doc", "translation"] + }, + { + "login": "julieobolenskaya", + "name": "Julie Obolenskaya", + "avatar_url": "https://avatars.githubusercontent.com/u/80626513?v=4", + "profile": "https://github.com/julieobolenskaya", + "contributions": ["translation"] + }, + { + "login": "Imperyall", + "name": "Roman Tikhiy", + "avatar_url": "https://avatars.githubusercontent.com/u/24413052?v=4", + "profile": "https://github.com/Imperyall", + "contributions": ["userTesting", "doc"] + }, + { + "login": "igorkamyshev", + "name": "Igor Kamyshev", + "avatar_url": "https://avatars.githubusercontent.com/u/26767722?v=4", + "profile": "https://kamyshev.me/", + "contributions": ["bug", "doc"] + }, + { + "login": "GTech1256", + "name": "Roman", + "avatar_url": "https://avatars.githubusercontent.com/u/18086485?v=4", + "profile": "https://gtech1256.github.io/PersonalPage/", + "contributions": ["userTesting", "doc"] + }, + { + "login": "websega", + "name": "Sergey Vakhramov", + "avatar_url": "https://avatars.githubusercontent.com/u/56861782?v=4", + "profile": "https://github.com/websega", + "contributions": ["design"] + }, + { + "login": "mark-omarov", + "name": "Mark Omarov", + "avatar_url": "https://avatars.githubusercontent.com/u/15357910?v=4", + "profile": "https://github.com/mark-omarov", + "contributions": ["doc"] + }, + { + "login": "skrylnikov", + "name": "Дмитрий", + "avatar_url": "https://avatars.githubusercontent.com/u/9007486?v=4", + "profile": "https://dskr.dev/", + "contributions": ["business", "userTesting"] + }, + { + "login": "Mihir50", + "name": "Mihir Shah", + "avatar_url": "https://avatars.githubusercontent.com/u/58292449?v=4", + "profile": "https://www.leetcode.com/Mihir64", + "contributions": ["design"] + }, + { + "login": "GlebHihoho", + "name": "Gleb", + "avatar_url": "https://avatars.githubusercontent.com/u/17951143?v=4", + "profile": "https://github.com/GlebHihoho", + "contributions": ["doc"] + }, + { + "login": "yesnoruly", + "name": "Roma Karvacky", + "avatar_url": "https://avatars.githubusercontent.com/u/64963734?v=4", + "profile": "https://github.com/yesnoruly", + "contributions": ["example"] + }, + { + "login": "oas89", + "name": "Aleksandr Osipov", + "avatar_url": "https://avatars.githubusercontent.com/u/5285065?v=4", + "profile": "https://github.com/oas89", + "contributions": ["userTesting"] + }, + { + "login": "mg901", + "name": "Maxim", + "avatar_url": "https://avatars.githubusercontent.com/u/7874664?v=4", + "profile": "https://t.me/mg901", + "contributions": ["userTesting"] + }, + { + "login": "Kelin2025", + "name": "Anton Kosykh", + "avatar_url": "https://avatars.githubusercontent.com/u/4208480?v=4", + "profile": "https://github.com/Kelin2025", + "contributions": ["userTesting"] + }, + { + "login": "samelm", + "name": "Vladislav Samatov", + "avatar_url": "https://avatars.githubusercontent.com/u/9926019?v=4", + "profile": "https://github.com/samelm", + "contributions": ["userTesting"] + }, + { + "login": "olegKusov", + "name": "Oleg Kusov", + "avatar_url": "https://avatars.githubusercontent.com/u/28058268?v=4", + "profile": "https://github.com/olegKusov", + "contributions": ["blog", "userTesting"] + }, + { + "login": "sandrig", + "name": "Andrey Savelev", + "avatar_url": "https://avatars.githubusercontent.com/u/11439304?v=4", + "profile": "https://andreysavelev.com/", + "contributions": ["userTesting"] + }, + { + "login": "tavriaforever", + "name": "Nickolay Ilchenko", + "avatar_url": "https://avatars.githubusercontent.com/u/975906?v=4", + "profile": "http://twitter/tavriaforever", + "contributions": ["userTesting", "eventOrganizing"] + }, + { + "login": "ledeneveugene", + "name": "Eugene Ledenev", + "avatar_url": "https://avatars.githubusercontent.com/u/51231845?v=4", + "profile": "https://github.com/ledeneveugene", + "contributions": ["data"] + }, + { + "login": "vladislavromanov", + "name": "Vladislav Romanov", + "avatar_url": "https://avatars.githubusercontent.com/u/63917524?v=4", + "profile": "https://github.com/vladislavromanov", + "contributions": ["data"] + }, + { + "login": "ainursharaev", + "name": "Ainur", + "avatar_url": "https://avatars.githubusercontent.com/u/33234903?v=4", + "profile": "https://github.com/ainursharaev", + "contributions": ["doc"] + }, + { + "login": "EliseyMartynov", + "name": "Elisey Martynov", + "avatar_url": "https://avatars.githubusercontent.com/u/66368523?v=4", + "profile": "https://github.com/EliseyMartynov", + "contributions": ["example"] + }, + { + "login": "baushonok", + "name": "Olga Pasynok", + "avatar_url": "https://avatars.githubusercontent.com/u/9272905?v=4", + "profile": "https://github.com/baushonok", + "contributions": ["eventOrganizing"] + }, + { + "login": "Affiction", + "name": "Max Kokosha", + "avatar_url": "https://avatars.githubusercontent.com/u/9825305?v=4", + "profile": "https://github.com/Affiction", + "contributions": ["example"] + }, + { + "login": "Zukhrik", + "name": "Зухриддин Камильжанов", + "avatar_url": "https://avatars.githubusercontent.com/u/67275391?v=4", + "profile": "https://github.com/Zukhrik", + "contributions": ["translation", "promotion", "doc"] + } + ], + "contributorsPerLine": 5, + "projectName": "documentation", + "projectOwner": "feature-sliced", + "repoType": "github", + "repoHost": "https://github.com", + "skipCi": true, + "commitConvention": "none", + "commitType": "docs" } diff --git a/.env.sample b/.env.sample index c986685eb3..2b5184551b 100644 --- a/.env.sample +++ b/.env.sample @@ -1,5 +1,3 @@ ALGOLIA_KEY= ALGOLIA_ID= -GA_ID= -HOTJAR_ID= OG_EXP= diff --git a/.eslintrc.js b/.eslintrc.js index f76c16812e..f5af7a6c4a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,7 +5,12 @@ module.exports = { es6: true, }, parser: "@typescript-eslint/parser", - extends: ["@eslint-kit/patch", "@eslint-kit/base", "@eslint-kit/react", "@eslint-kit/prettier"], + extends: [ + "@eslint-kit/patch", + "@eslint-kit/base", + "@eslint-kit/react", + "prettier", + ], plugins: ["@typescript-eslint"], rules: { // Sometime harmful =( @@ -36,8 +41,14 @@ module.exports = { alias: { map: [ ["@site", "."], - ["@docusaurus", "./node_modules/@docusaurus/core/lib/client/exports"], - ["@theme", "./node_modules/@docusaurus/theme-classic/src/theme"], + [ + "@docusaurus", + "./node_modules/@docusaurus/core/lib/client/exports", + ], + [ + "@theme", + "./node_modules/@docusaurus/theme-classic/src/theme", + ], ], extensions: [".js", ".jsx", ".json", ".tsx", ".ts"], }, diff --git a/.github/assets/README-banner-dark.svg b/.github/assets/README-banner-dark.svg index 6fb47d332c..80c02e0f4f 100644 --- a/.github/assets/README-banner-dark.svg +++ b/.github/assets/README-banner-dark.svg @@ -1,126 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/.github/assets/README-banner-light.svg b/.github/assets/README-banner-light.svg index b0150ea7c4..00d10dc715 100644 --- a/.github/assets/README-banner-light.svg +++ b/.github/assets/README-banner-light.svg @@ -1,126 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/.github/assets/README-discord.svg b/.github/assets/README-discord.svg index bd4216e4c4..3317155c02 100644 --- a/.github/assets/README-discord.svg +++ b/.github/assets/README-discord.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/.github/assets/README-telegram.svg b/.github/assets/README-telegram.svg index 247599ca23..e227c6bfac 100644 --- a/.github/assets/README-telegram.svg +++ b/.github/assets/README-telegram.svg @@ -1,17 +1 @@ - - - - - - - - - - - - - - - - - + diff --git a/.github/assets/README-website.svg b/.github/assets/README-website.svg index 3ef4b4f925..70a124e6e2 100644 --- a/.github/assets/README-website.svg +++ b/.github/assets/README-website.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8bba0e88d4..f2ae9ec7a7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -12,33 +12,38 @@ jobs: env: ALGOLIA_KEY: ${{ secrets.ALGOLIA_KEY }} ALGOLIA_ID: ${{ secrets.ALGOLIA_ID }} - GA_ID: ${{ secrets.GA_ID }} - HOTJAR_ID: ${{ secrets.HOTJAR_ID }} steps: - - uses: actions/checkout@v3 - with: - persist-credentials: false + - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v2 - with: - version: 8.3.1 + - run: | + npm install -g corepack@latest + corepack enable - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x cache: pnpm - name: Install dependencies run: pnpm install working-directory: . + - name: Retrieve Docusaurus build cache + uses: actions/cache@v4 + with: + path: node_modules/.cache/webpack + key: docusaurus-${{ github.ref_name }} + restore-keys: | + docusaurus-${{ github.ref_name }} + docusaurus-master + - name: Build static run: pnpm build working-directory: . - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@4.1.3 + uses: JamesIves/github-pages-deploy-action@v4.6.1 with: branch: gh-pages folder: ./build diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 45aec0fb61..b40c9f0b8e 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -10,26 +10,32 @@ jobs: env: ALGOLIA_KEY: ${{ secrets.ALGOLIA_KEY }} ALGOLIA_ID: ${{ secrets.ALGOLIA_ID }} - GA_ID: ${{ secrets.GA_ID }} - HOTJAR_ID: ${{ secrets.HOTJAR_ID }} steps: - - uses: actions/checkout@v3 - with: - persist-credentials: false + - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v2 - with: - version: 8.3.1 + - run: | + npm install -g corepack@latest + corepack enable - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x + cache: pnpm - name: Install dependencies run: pnpm install working-directory: . + - name: Retrieve Docusaurus build cache + uses: actions/cache@v4 + with: + path: node_modules/.cache/webpack + key: docusaurus-${{ github.ref_name }} + restore-keys: | + docusaurus-${{ github.ref_name }} + docusaurus-master + - name: Check build run: pnpm build working-directory: . @@ -38,22 +44,33 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v3 - with: - persist-credentials: false + - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v2 - with: - version: 8.3.1 + - run: | + npm install -g corepack@latest + corepack enable - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x + cache: pnpm - name: Install dependencies run: pnpm install working-directory: . + - name: Retrieve linter cache + uses: actions/cache@v4 + with: + path: | + ./.eslintcache + ./node_modules/.cache/prettier/.prettier-cache + ./stylelintcache + key: linters-${{ github.ref_name }} + restore-keys: | + linters-${{ github.ref_name }} + linters-master + - name: Check linting run: pnpm run test:lint working-directory: . diff --git a/.gitignore b/.gitignore index faf4827678..0efb134844 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ build .env.production.local npm-debug.log* + +.eslintcache +.stylelintcache diff --git a/.markdownlint.json b/.markdownlint.json index d445fad693..bf25847116 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -6,4 +6,4 @@ // Лучше оставлять таблицы в "расширяемом" виде, чтобы можно было модифицировать позднее // https://www.tablesgenerator.com/markdown_tables "MD010": false -} \ No newline at end of file +} diff --git a/.prettierignore b/.prettierignore index f71c4be140..495f6a538f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,3 +6,4 @@ public/ .workflows *.md *.mdx +pnpm-lock.yaml diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..0a02bcefda --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "tabWidth": 4 +} diff --git a/.prettierrc.js b/.prettierrc.js deleted file mode 100644 index b8f8f45b4c..0000000000 --- a/.prettierrc.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - printWidth: 100, - semi: true, - singleQuote: false, - tabWidth: 4, - quoteProps: "consistent", - endOfLine: "lf", - importOrder: ["^[./]"], - trailingComma: "all", - arrowParens: "always", - parser: "typescript", -}; diff --git a/.stylelintrc.js b/.stylelintrc.js index b65a577b7e..a2d6caeb49 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -1,16 +1,11 @@ module.exports = { - // TODO: add later - // plugins: [ - // "stylelint-scss" - // ], extends: [ - "stylelint-config-standard", "stylelint-config-recommended", + "stylelint-config-standard-scss", "stylelint-config-recess-order", ], rules: { - "indentation": 4, "color-hex-length": "long", - "at-rule-no-unknown": true, + "selector-class-pattern": null, }, }; diff --git a/.vscode/extensions.json b/.vscode/extensions.json index e3b51c0d4e..73887a32e4 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,3 @@ { - "recommendations": [ - "jounqin.vscode-mdx" - ] + "recommendations": ["jounqin.vscode-mdx"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index e48e3c1af4..c833e606b9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,10 @@ { - "files.eol": "\n", - "[mdx]": { - "files.trimTrailingWhitespace": false - }, - "[markdown]": { - "files.trimTrailingWhitespace": false - } + "files.eol": "\n", + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[mdx]": { + "files.trimTrailingWhitespace": false + }, + "[markdown]": { + "files.trimTrailingWhitespace": false + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c2fb94306..04848838f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,71 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [2.1.0] - 2024-10-31 + +The new revision of Feature-Sliced Design is here! The main difference with FSD 2.0 is the new approach to decomposition — “pages first”. + +### What's “pages-first”? + +You do “pages first” by keeping more code in pages. For example, large blocks of UI, forms and data logic that are not reused on other pages should now stay in the slice of the page that they are used in. The division by segments (`ui`, `api`, `model`, etc.) still applies to all this code, and we encourage you to further split and organize code into folders inside of segments — don't just pile all the code into a single file. + +In the same way, widgets are no longer just a compositional layer, instead they should also store code that isn't currently needed outside of that widget, including its own stores, business logic, and API interactions. + +When you have a need to reuse code in several widgets or pages, consider putting it in Shared. If that code involves business logic (i. e. managing specific modal dialogs), consider breaking it up into infrastructural code, like the modal manager, and the business code, like the content of the modals. The infrastructure can then go to Shared, and the content can stay in the pages that use this infrastructure. + +### How is it different? + +In FSD 2.0 we explained how to identify entities and features in your application, and then combine them in widgets and pages. Over time we started disliking this approach, mostly for the following reasons: + +- Code cohesion is much worse in this approach + - You need to jump around several folders just to make changes to a single user flow + - Unused code is harder to delete because it's somewhere else +- Finding entities and features is still an advanced skill that needs to be developed over time + - It requires understanding of the business context, which not all developers want to bother with + - On the other hand, splitting by pages is natural and requires little training + - Different developers have different understandings of these concepts, which leads to everyone having their own idea of FSD, which causes conflict and misunderstanding + +### Is it hard to migrate from FSD 2.0? + +This is a non-breaking change, so you don’t even necessarily need to migrate your current FSD projects to FSD 2.1, but we still think the new way of thinking will lead to a more cohesive and less opinionated structure. We’ve compiled a few steps you can take in [the migration guide](https://feature-sliced.github.io/documentation/docs/guides/migration/from-v2-0). + +### What else happened since the last release? + +The cross-import notation (`@x`) that was an experimental proposal for a long time has now been standardized! Its official name is **Public API for cross-imports**. You can use it to create explicit connections between entities. There's [a new section in our documentation all about this new notation](https://feature-sliced.github.io/documentation/docs/reference/public-api#public-api-for-cross-imports). + +Another exciting new thing in the FSD ecosystem is our architectural linter, [Steiger](https://github.com/feature-sliced/steiger). It's still in active development, but it is production-ready. + +A couple more minor clarifications to the docs were made as well: + +1. Application-aware things like the route constants, the API calls, or company logo, are now explicitly allowed in Shared. Business logic is still not allowed, but these things are not considered to be business logic. +2. Imports between segments in App and Shared were always allowed, but it's been made explicit too. + +And here's what happened to the documentation website: + +#### Added + +- Slightly rewritten and expanded overview page to give some details about FSD right away (#685). +- New partial translations: Korean (#739, #736, #735, #742, #732, #730, #715), Japanese (#728). +- The tutorial was rewritten. Technical details were stripped out, more FSD theory has been added (#665). +- Guides on how to deal with common frontend issues like page layouts (#708), types (#701), authentication (#693). +- Guides on how to use FSD with Nuxt (#710, #689, #683, #679), SvelteKit (#698), Next.js (#699, #664, #644), and TanStack Query (#673). +- A new feedback widget, powered by PushFeedback! Go give it a try and let us know what you think of the new pages (#695). +- Comparison of FSD with Atomic Design (#671). + +#### Changed + +- The migration guide from a custom architecture (formerly known as "from legacy") has been actualized (#725). + +#### Removed + +- The decomposition cheatsheet is now unlisted for an undefined period of time. It proved to be more harmful than useful, but maybe it can be saved later (#649). + ## [2.0.0] - 2023-10-01 > **Note** -> This release note is retrospective, meaning that prior to this release, the Feature-Sliced Design project did not keep a changelog. Below is a summary of the most prominent recent changes, but there is no FSD v1. Prior to FSD, there has been a project called ["Feature Slices"](https://featureslices.dev/v1.0.html), and it is considered to be the v1 of FSD. +> This release note is retrospective, meaning that prior to this release, the Feature-Sliced Design project did not keep a changelog. Below is a summary of the most prominent recent changes, but there is no FSD v1. Prior to FSD, there has been a project called ["Feature Slices"](https://feature-sliced.github.io/featureslices.dev/v1.0.html), and it is considered to be the v1 of FSD. ### Deprecated @@ -34,4 +95,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The overview page has been rewritten to be more concise and informative (#512, #515, #516). - FSD has updated its branding, and there are now guidelines to the brand usage. The standard spelling of the name is now "Feature-Sliced Design" (#496, #499, #500, #465). +[since-last-release]: https://github.com/feature-sliced/documentation/compare/v2.1.0...HEAD +[2.1.0]: https://github.com/feature-sliced/documentation/releases/tag/v2.1.0 [2.0.0]: https://github.com/feature-sliced/documentation/releases/tag/v2.0.0 diff --git a/CNAME b/CNAME deleted file mode 100644 index f806fe67d0..0000000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -feature-sliced.design diff --git a/CODE_OF_CONDUCT.ru.md b/CODE_OF_CONDUCT.ru.md index 91afeec14b..08ec104649 100644 --- a/CODE_OF_CONDUCT.ru.md +++ b/CODE_OF_CONDUCT.ru.md @@ -41,9 +41,9 @@ ## Атрибуция Данный Кодекс Поведения составлен на основе [Соглашения о Контрибьюторах][homepage], версия 1.4, -доступного по ссылке: +доступного по ссылке: [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) [homepage]: https://www.contributor-covenant.org Ответы на наиболее частые вопросы, касающиеся данного Кодекса Поведения, можно найти по ссылке: - +[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq) diff --git a/DEV.md b/DEV.md index c9c70bdf51..c540678a38 100644 --- a/DEV.md +++ b/DEV.md @@ -7,6 +7,7 @@ This website is built using [Docusaurus 2](https://docusaurus.io/), a modern sta - [Russian docs version](i18n/ru) - [English docs version](i18n/en) - [Uzbek docs version](i18n/uz) +- [Japanese docs version](i18n/ja) ## Installation @@ -20,6 +21,8 @@ pnpm install pnpm start # for default locale pnpm start:ru # for RU locale pnpm start:en # for EN locale +pnpm start:uz # for UZ locale +pnpm start:ja # for JA locale ``` > About [docusaurus/i18n commands](https://docusaurus.io/docs/i18n/git#translate-the-files) diff --git a/LICENSE b/LICENSE index 18727f3ff5..176e491760 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018-2023 Feature-Sliced Design core-team +Copyright (c) 2018-2025 Feature-Sliced Design core-team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index bf8ce5220b..85eb5b0292 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Discord (English language)Telegram (Russian language)WebsiteFeature-Sliced Design, an architectural methodology for frontend projectsFeature-Sliced Design, an architectural methodology for frontend projects +DiscordTelegramWebsiteFeature-Sliced Design, an architectural methodology for frontend projectsFeature-Sliced Design, an architectural methodology for frontend projects @@ -41,7 +41,7 @@ This methodology is not tied to a particular stack — it can be used for web or To show off that your project uses FSD, you can use the GitHub topic `feature-sliced` and one of the following badges: -[![Feature-Sliced Design][shields-fsd-white]](https://feature-sliced.design/) [![Feature-Sliced Design][shields-fsd-pain]](https://feature-sliced.design/) [![Feature-Sliced Design][shields-fsd-domain]](https://feature-sliced.design/) [![Feature-Sliced Design][shields-fsd-feature]](https://feature-sliced.design/) +[![Feature-Sliced Design][shields-fsd-white]](https://feature-sliced.github.io/documentation/) [![Feature-Sliced Design][shields-fsd-pain]](https://feature-sliced.github.io/documentation/) [![Feature-Sliced Design][shields-fsd-domain]](https://feature-sliced.github.io/documentation/) [![Feature-Sliced Design][shields-fsd-feature]](https://feature-sliced.github.io/documentation/) [shields-fsd-white]: https://img.shields.io/badge/Feature--Sliced-Design?style=for-the-badge&labelColor=262224&color=F2F2F2&logoWidth=10&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAALFAAACxQGJ1n/vAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAA/SURBVHgB7dKxCgAgCIThs/d/51JoNQIdDrxvqMXlR4FmFs92KDIX/wI7JSdDN+eHtkxIycnQvMNW8hN/crsDc5QgGX9NvT0AAAAASUVORK5CYII= @@ -55,28 +55,28 @@ To show off that your project uses FSD, you can use the GitHub topic `feature-sl ```markdown White: -[![Feature-Sliced Design][shields-fsd-white]](https://feature-sliced.design/) +[![Feature-Sliced Design][shields-fsd-white]](https://feature-sliced.github.io/documentation/) [shields-fsd-white]: https://img.shields.io/badge/Feature--Sliced-Design?style=for-the-badge&labelColor=262224&color=F2F2F2&logoWidth=10&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAALFAAACxQGJ1n/vAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAA/SURBVHgB7dKxCgAgCIThs/d/51JoNQIdDrxvqMXlR4FmFs92KDIX/wI7JSdDN+eHtkxIycnQvMNW8hN/crsDc5QgGX9NvT0AAAAASUVORK5CYII= ---- Pain (red): -[![Feature-Sliced Design][shields-fsd-pain]](https://feature-sliced.design/) +[![Feature-Sliced Design][shields-fsd-pain]](https://feature-sliced.github.io/documentation/) [shields-fsd-pain]: https://img.shields.io/badge/Feature--Sliced-Design?style=for-the-badge&labelColor=262224&color=F2F2F2&logoWidth=10&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAALFAAACxQGJ1n/vAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABHSURBVHgB7dKxCQAgDETR08ZNHNBBHNBNrBQFuyCCKQK5V6QMfBJAWVij5zLwKbW6d0VYx2TZyXnBKxvEZJnDx2bylf1kdRM6tiAZsruQ/QAAAABJRU5ErkJggg== ---- Domain (blue): -[![Feature-Sliced Design][shields-fsd-domain]](https://feature-sliced.design/) +[![Feature-Sliced Design][shields-fsd-domain]](https://feature-sliced.github.io/documentation/) [shields-fsd-domain]: https://img.shields.io/badge/Feature--Sliced-Design?style=for-the-badge&color=F2F2F2&labelColor=262224&logoWidth=10&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAALFAAACxQGJ1n/vAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABISURBVHgB7dKxCQAgDETR0w2cws0cys2cwhEUBbsggikCuVekDHwSQFlYo7Q+8KnmtHdFWMdk2cl5wSsbxGSZw8dm8pX9ZHUTMBUgGU2F718AAAAASUVORK5CYII= ---- Feature (green): -[![Feature-Sliced Design][shields-fsd-feature]](https://feature-sliced.design/) +[![Feature-Sliced Design][shields-fsd-feature]](https://feature-sliced.github.io/documentation/) [shields-fsd-feature]: https://img.shields.io/badge/Feature--Sliced-Design?style=for-the-badge&labelColor=262224&color=F2F2F2&logoWidth=10&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAALFAAACxQGJ1n/vAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABISURBVHgB7dKxCQAgDETR00EcwYEc0IEcwUUUBbsggikCuVekDHwSQFlYo/Y88KmktndFWMdk2cl5wSsbxGSZw8dm8pX9ZHUTdIYgGbPdU2QAAAAASUVORK5CYII= ``` @@ -112,7 +112,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d - + diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index dd249ac168..0000000000 --- a/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: [require.resolve("@docusaurus/core/lib/babel/preset")], -}; diff --git a/blog/2022-06-04-rebranding.md b/blog/2022-06-04-rebranding.md index abe4bf384c..345116bb99 100644 --- a/blog/2022-06-04-rebranding.md +++ b/blog/2022-06-04-rebranding.md @@ -8,12 +8,14 @@ authors: url: https://github.com/azinit image_url: https://github.com/azinit.png tags: [brand, promo] -image: https://feature-sliced.design/img/blog/rebranding-stable.png +image: https://feature-sliced.github.io/documentation/img/blog/rebranding-stable.png hide_table_of_contents: false --- +import useBaseUrl from "@docusaurus/useBaseUrl"; +
- logo-primary + logo-primary
TLDR: diff --git a/blog/2022-07-25-international-community.md b/blog/2022-07-25-international-community.md index 06b96d8ba7..a06963d8dc 100644 --- a/blog/2022-07-25-international-community.md +++ b/blog/2022-07-25-international-community.md @@ -8,12 +8,14 @@ authors: url: https://github.com/unordinarity image_url: https://github.com/unordinarity.png tags: [community, discord, promo] -image: https://feature-sliced.design/img/blog/international-community.png +image: https://feature-sliced.github.io/documentation/img/blog/international-community.png hide_table_of_contents: false --- +import useBaseUrl from "@docusaurus/useBaseUrl"; +
- post-cover + post-cover
TLDR: diff --git a/config/docusaurus/announcementBar.js b/config/docusaurus/announcementBar.js deleted file mode 100644 index 6a6174590d..0000000000 --- a/config/docusaurus/announcementBar.js +++ /dev/null @@ -1,23 +0,0 @@ -const NBSP = " "; - -/** @type {import('@docusaurus/types').DocusaurusConfig["themeConfig"]["announcementBar"]} */ -const announcementBar = { - id: "bar", // Any value that will identify this message. - // content: `WIP: Текущая версия методологии находится на стадии разработки и некоторые детали могут измениться`, - // backgroundColor: '#e6a700', // As caution by docusaurus (defaults was `#fff`) - // FIXME: (i18n) translate by locale later (how to?) - // content: `If you are using the methodology (v0 / v1 / v2) at work or in personal projects, tell, us!`, - // content: `📚 Documentation refinements are in progress. Stay tuned for updates and share your feedback`, - // backgroundColor: "#5c9cb5", // As primary theme - // backgroundColor: "#0367d2", - content: [ - `🍰 We're rebranding!`, - `❔Help`, - `☮️ Stop the war in Ukraine! #NoWar`, // #nowar - ].join(`${NBSP + NBSP}|${NBSP + NBSP}`), - backgroundColor: "#000000", // #nowar - textColor: "#fff", // Defaults to `#000`. - isCloseable: false, // Defaults to `true`. -}; - -module.exports = { announcementBar }; diff --git a/config/docusaurus/consts.js b/config/docusaurus/consts.js index be596bbf0a..59761c2196 100644 --- a/config/docusaurus/consts.js +++ b/config/docusaurus/consts.js @@ -1,14 +1,14 @@ const CATEGORIES = { "get-started": "🚀 Get Started", - "guides": "🎯 Guides", - "reference": "📚 Reference", - "about": "🍰 About", - "community": "💫 Community", - "examples": "🛠 Examples", + guides: "🎯 Guides", + reference: "📚 Reference", + about: "🍰 About", + community: "💫 Community", + examples: "🛠 Examples", }; module.exports = { - DOMAIN: "https://feature-sliced.design/", + DOMAIN: "https://feature-sliced.github.io/", GITHUB_ORG: "https://github.com/feature-sliced", GITHUB_DOCS: "https://github.com/feature-sliced/documentation", TELEGRAM: "https://t.me/feature_sliced", diff --git a/config/docusaurus/extensions.js b/config/docusaurus/extensions.js index 53a8e3f2fe..1909f746b1 100644 --- a/config/docusaurus/extensions.js +++ b/config/docusaurus/extensions.js @@ -9,31 +9,16 @@ const DOCUSAURUS_PLUGIN_OG = [ }, ]; -// We use metrics only for analyze and refinement website discovery experience -// @see Privacy -const metrics = { - gtag: { - trackingID: process.env.GA_ID, // the Google Analytics Tracking ID - anonymizeIP: true, // Should IPs be anonymized? - }, - googleAnalytics: { - trackingID: process.env.GA_ID, // the Google Analytics Tracking ID - anonymizeIP: true, // Should IPs be anonymized? - }, - // to integrate Hotjar feedback - // @see https://github.com/symblai/docusaurus-plugin-hotjar - hotjar: { - applicationId: process.env.HOTJAR_ID, - }, -}; - /** * Hide category index pages from sidebar () * TODO: Remove custom generator after issue fix * https://github.com/facebook/docusaurus/issues/5689 * @see https://docusaurus.io/docs/sidebar/autogenerated#customize-the-sidebar-items-generator */ -async function sidebarItemsGenerator({ defaultSidebarItemsGenerator, ...args }) { +async function sidebarItemsGenerator({ + defaultSidebarItemsGenerator, + ...args +}) { const sidebarItems = await defaultSidebarItemsGenerator(args); const isCategoryIndex = (it) => it.type === "doc" && (it.id === "index" || it.id?.includes("/index")); @@ -58,7 +43,7 @@ const presets = [ showLastUpdateTime: true, versions: { current: { - label: `v2.0.0 🍰`, + label: `v2.1`, }, }, sidebarItemsGenerator, @@ -66,6 +51,7 @@ const presets = [ blog: { showReadingTime: true, editUrl: `${GITHUB_DOCS}/edit/master/blog/`, + onInlineAuthors: "ignore", }, theme: { customCss: require.resolve("../../src/app/index.scss"), @@ -75,8 +61,6 @@ const presets = [ changefreq: "weekly", priority: 0.5, }, - gtag: process.env.GA_ID ? metrics.gtag : undefined, - googleAnalytics: process.env.GA_ID ? metrics.googleAnalytics : undefined, }, ], ]; @@ -124,7 +108,6 @@ const plugins = [ }, ], "plugin-image-zoom", - process.env.HOTJAR_ID && "docusaurus-plugin-hotjar", // For preventing crashing // FIXME: Docusaurus Open Graph Plugin Experimental. process.env.OG_EXP && DOCUSAURUS_PLUGIN_OG, ].filter(Boolean); @@ -134,9 +117,7 @@ const algolia = { appId: process.env.ALGOLIA_ID, apiKey: process.env.ALGOLIA_KEY, indexName: "feature-sliced", - // FIXME: При включении отрубает поиск (исправить поздней) - // Для поиска с учетом версий (на будущее) - contextualSearch: false, + contextualSearch: true, }; -module.exports = { presets, plugins, algolia, metrics }; +module.exports = { presets, plugins, algolia }; diff --git a/config/docusaurus/footer.js b/config/docusaurus/footer.js index bbf956dbf0..cef2fd23a9 100644 --- a/config/docusaurus/footer.js +++ b/config/docusaurus/footer.js @@ -45,7 +45,6 @@ const footer = { label: "License", href: `${GITHUB_DOCS}/blob/master/LICENSE`, }, - { label: "Privacy", href: "/docs/privacy" }, ], }, ], diff --git a/config/docusaurus/i18n.js b/config/docusaurus/i18n.js index c9c525b406..2cef5f9050 100644 --- a/config/docusaurus/i18n.js +++ b/config/docusaurus/i18n.js @@ -3,7 +3,7 @@ const { DEFAULT_LOCALE } = require("./consts"); /** @type {import('@docusaurus/types').DocusaurusConfig["i18n"]} */ const i18n = { defaultLocale: DEFAULT_LOCALE, - locales: ["ru", "en", "uz"], + locales: ["ru", "en", "uz", "kr", "ja"], localeConfigs: { ru: { label: "Русский", @@ -14,6 +14,12 @@ const i18n = { uz: { label: "O'zbekcha", }, + kr: { + label: "한국어", + }, + ja: { + label: "日本語", + }, }, }; diff --git a/config/docusaurus/index.js b/config/docusaurus/index.js index c4d5b77061..81ef3b9caa 100644 --- a/config/docusaurus/index.js +++ b/config/docusaurus/index.js @@ -1,14 +1,12 @@ -const { announcementBar } = require("./announcementBar"); const { navbar } = require("./navbar"); const { footer } = require("./footer"); const consts = require("./consts"); const { metadata } = require("./metadata"); const { i18n } = require("./i18n"); -const { presets, plugins, algolia, metrics } = require("./extensions"); +const { presets, plugins, algolia } = require("./extensions"); const { LEGACY_ROUTES, REDIRECTS } = require("./routes"); module.exports = { - announcementBar, navbar, footer, consts, @@ -17,7 +15,6 @@ module.exports = { presets, plugins, algolia, - metrics, LEGACY_ROUTES, REDIRECTS, }; diff --git a/config/docusaurus/metadata.js b/config/docusaurus/metadata.js index 057dee42d0..85ee4dc227 100644 --- a/config/docusaurus/metadata.js +++ b/config/docusaurus/metadata.js @@ -4,6 +4,7 @@ * @see https://docusaurus.io/docs/api/themes/configuration#metadata */ const metadata = [ + { name: "keywords", content: "architecture, frontend, project structure" }, { name: "twitter:site", content: "@feature_sliced" }, { name: "twitter:card", content: "summary_large_image" }, // NOTE: uncomment if need diff --git a/config/docusaurus/navbar.js b/config/docusaurus/navbar.js index 3600776b7c..4230a532ef 100644 --- a/config/docusaurus/navbar.js +++ b/config/docusaurus/navbar.js @@ -37,11 +37,11 @@ const navbar = { dropdownActiveClassDisabled: true, dropdownItemsAfter: [ { - to: "https://featureslices.dev/v1.0.html", + to: "https://feature-sliced.github.io/featureslices.dev/v1.0.html", label: "v1.0", }, { - to: "https://featureslices.dev/v0.1.html", + to: "https://feature-sliced.github.io/featureslices.dev/v0.1.html", label: "v0.1", }, { @@ -66,15 +66,15 @@ const navbar = { ], }, { - "href": DISCORD, - "position": "right", - "className": "ext-link discord", + href: DISCORD, + position: "right", + className: "ext-link discord", "aria-label": "Discord community server", }, { - "href": GITHUB_DOCS, - "position": "right", - "className": "ext-link github", + href: GITHUB_DOCS, + position: "right", + className: "ext-link github", "aria-label": "GitHub repository", }, ], diff --git a/config/docusaurus/routes.js b/config/docusaurus/routes.js index f9ce494798..1a657eabbe 100644 --- a/config/docusaurus/routes.js +++ b/config/docusaurus/routes.js @@ -1,3 +1,4 @@ +/* eslint-disable sonarjs/no-duplicate-string */ // Custom "not-docusaurus-related" config for routes setup // @see https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-client-redirects @@ -9,7 +10,7 @@ const SECTIONS = { }, MIGRATION: { shortPath: "/docs/guides/migration", - fullPath: "/docs/guides/migration/from-legacy", + fullPath: "/docs/guides/migration/from-custom", }, }; @@ -46,7 +47,8 @@ const LEGACY_ROUTES = [ }, { group: "🍰 Alternatives", - details: "Moved and merged to /about/alternatives as advanced materials", + details: + "Moved and merged to /about/alternatives as advanced materials", children: [ { title: "Architecture approaches alternatives", @@ -107,17 +109,17 @@ const LEGACY_ROUTES = [ { title: "Decouple of entities", from: "/docs/concepts/decouple-entities", - to: "/docs/reference/isolation/decouple-entities", + to: "/docs/reference/layers#import-rule-on-layers", }, { title: "Low Coupling & High Cohesion", from: "/docs/concepts/low-coupling", - to: "/docs/reference/isolation/coupling-cohesion", + to: "/docs/reference/slices-segments#zero-coupling-high-cohesion", }, { title: "Cross-communication", from: "/docs/concepts/cross-communication", - to: "/docs/reference/isolation", + to: "/docs/reference/layers#import-rule-on-layers", }, { title: "App splitting", @@ -214,7 +216,8 @@ const LEGACY_ROUTES = [ }, { group: "🎯 Examples", - details: "Grouped and simplified into /guides/examples as practical examples", + details: + "Grouped and simplified into /guides/examples as practical examples", children: [ { title: "Viewer logic", @@ -235,7 +238,8 @@ const LEGACY_ROUTES = [ }, { group: "🎯 Migration", - details: "Grouped and simplified into /guides/migration as migration guidelines", + details: + "Grouped and simplified into /guides/migration as migration guidelines", children: [ { title: "Migration from V1", @@ -245,7 +249,7 @@ const LEGACY_ROUTES = [ { title: "Migration from Legacy", from: "/docs/guides/migration-from-legacy", - to: "/docs/guides/migration/from-legacy", + to: "/docs/guides/migration/from-custom", }, ], }, @@ -260,6 +264,30 @@ const LEGACY_ROUTES = [ }, ], }, + { + group: "Rename 'legacy' to 'custom'", + details: + "'Legacy' is derogatory, we don't get to call people's projects legacy", + children: [ + { + title: "Rename 'legacy' to custom", + from: "/docs/guides/migration/from-legacy", + to: "/docs/guides/migration/from-custom", + }, + ], + }, + { + group: "Deduplication of Reference", + details: + "Cleaned up the Reference section and deduplicated the material", + children: [ + { + title: "Isolation of modules", + from: "/docs/reference/isolation", + to: "/docs/reference/layers#import-rule-on-layers", + }, + ], + }, ]; // @returns { from, to }[] @@ -270,10 +298,12 @@ const LEGACY_ROUTES_REDIRECTS = LEGACY_ROUTES.reduce((acc, group) => { // FIXME: temp, resolve later // @returns { from, to }[] -const SECTIONS_REDIRECTS = Object.values(SECTIONS).map(({ shortPath, fullPath }) => ({ - from: shortPath, - to: fullPath, -})); +const SECTIONS_REDIRECTS = Object.values(SECTIONS).map( + ({ shortPath, fullPath }) => ({ + from: shortPath, + to: fullPath, + }), +); // !!! FIXME: refactor later! // UPD: Removed new docs routes for simplifying @@ -308,11 +338,10 @@ const _TOTAL_ROUTES = [ "/docs/guides/examples/theme", "/docs/guides/examples/types", "/docs/guides/examples/white-labels", - "/docs/guides/migration/from-legacy", + "/docs/guides/migration/from-custom", "/docs/guides/migration/from-v1", "/docs/guides/tech/with-nextjs", "/docs/", - "/docs/privacy", "/docs/reference", ]; // from: "/en/docs/*" to "/docs/*" @@ -322,7 +351,11 @@ const I18N_REDIRECTS = _TOTAL_ROUTES.map((route) => ({ })); // NOTE: temp redirects, resolve later // https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-client-redirects -const REDIRECTS = [...SECTIONS_REDIRECTS, ...LEGACY_ROUTES_REDIRECTS, ...I18N_REDIRECTS]; +const REDIRECTS = [ + ...SECTIONS_REDIRECTS, + ...LEGACY_ROUTES_REDIRECTS, + ...I18N_REDIRECTS, +]; module.exports = { LEGACY_ROUTES, diff --git a/config/docusaurus/sidebars.community.js b/config/docusaurus/sidebars.community.js index dc9c0bc1c9..4aea2d3122 100644 --- a/config/docusaurus/sidebars.community.js +++ b/config/docusaurus/sidebars.community.js @@ -23,8 +23,11 @@ module.exports = { items: [ { type: "autogenerated", dirName: "." }, { type: "link", href: "/examples", label: "Examples" }, - // eslint-disable-next-line prettier/prettier - { type: "link", href: "https://github.com/feature-sliced/awesome", label: "Awesome Resources" }, + { + type: "link", + href: "https://github.com/feature-sliced/awesome", + label: "Awesome Resources", + }, { type: "link", label: "Discord community server", diff --git a/config/og/basic/template.json b/config/og/basic/template.json index 80fc4f6161..ea655de367 100644 --- a/config/og/basic/template.json +++ b/config/og/basic/template.json @@ -1,11 +1,11 @@ { - "image": "preview.png", - "font": "RobotoMono-Bold.ttf", - "layout": [ - { - "name": "title", - "top": 400, - "left": 210 - } - ] + "image": "preview.png", + "font": "RobotoMono-Bold.ttf", + "layout": [ + { + "name": "title", + "top": 400, + "left": 210 + } + ] } diff --git a/config/og/config.json b/config/og/config.json index 80b8556b4f..fd527152e2 100644 --- a/config/og/config.json +++ b/config/og/config.json @@ -1,12 +1,12 @@ { - "outputDir": "assets/og", - "textWidthLimit": 1100, - "quality": 70, - "rules": [ - { - "name": "basic", - "priority": 0, - "pattern": "." - } - ] + "outputDir": "assets/og", + "textWidthLimit": 1100, + "quality": 70, + "rules": [ + { + "name": "basic", + "priority": 0, + "pattern": "." + } + ] } diff --git a/declaration.d.ts b/declaration.d.ts index 3f644554d8..8d3e96e6d4 100644 --- a/declaration.d.ts +++ b/declaration.d.ts @@ -1,29 +1,28 @@ -declare module '*.scss' { +declare module "*.scss" { const content: Record; export default content; -}; +} -declare module '@site/static/img/*' { +declare module "@site/static/img/*" { export default string; -}; +} + +declare module "@theme/IdealImage" { + import type { ReactNode } from "react"; -declare module '@theme/IdealImage' { - import type {ReactNode} from 'react'; - export interface Props { - readonly className?: string; - readonly children?: ReactNode; - readonly img: string; - readonly alt: string; + readonly className?: string; + readonly children?: ReactNode; + readonly img: string; + readonly alt: string; } export default function Image(props: Props): JSX.Element; } -declare module '@docusaurus/plugin-content-docs/client' { +declare module "@docusaurus/plugin-content-docs/client" { export interface Version { - readonly label: string; - readonly name: string; + readonly label: string; + readonly name: string; } export function useLatestVersion(): Version; } - diff --git a/docusaurus.config.js b/docusaurus.config.js index 7191063587..88a6d76507 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -1,7 +1,8 @@ require("dotenv").config(); +const { themes: prismThemes } = require("prism-react-renderer"); const cfg = require("./config/docusaurus"); -/** @typedef {import('@docusaurus/types').DocusaurusConfig} Config */ +/** @typedef {import('@docusaurus/types').Config} Config */ /** * Custom fields (for access on code-level) @@ -11,6 +12,7 @@ const customFields = { legacyRoutes: cfg.LEGACY_ROUTES, // FIXME: Open Graph Experimental Mode. isOGExperimental: process.env.OG_EXP, + pushFeedbackProjectId: "5i2vbxcpaz", }; /** @type {Config} */ @@ -22,7 +24,7 @@ module.exports = { projectName: "documentation", // Usually your repo name. url: cfg.consts.DOMAIN, favicon: "img/favicon/classic.png", - baseUrl: "/", + baseUrl: "/documentation/", // Extensions i18n: cfg.i18n, presets: cfg.presets, @@ -39,17 +41,24 @@ module.exports = { // @see https://docusaurus.io/docs/sidebar#hideable-sidebar docs: { sidebar: { hideable: true, autoCollapseCategories: true } }, colorMode: { respectPrefersColorScheme: true }, - announcementBar: cfg.announcementBar, navbar: cfg.navbar, footer: cfg.footer, algolia: cfg.algolia, metadata: cfg.metadata, - hotjar: cfg.metrics.hotjar, + imageZoom: { + selector: ".markdown :not(a) > img", options: { background: "rgb(255 255 255 / 0.3)", }, }, + prism: { + theme: prismThemes.oneLight, + darkTheme: prismThemes.oneDark, + }, + }, + future: { + experimental_faster: true, }, }; @@ -57,6 +66,3 @@ module.exports = { if (!process.env.ALGOLIA_KEY || !process.env.ALGOLIA_ID) { delete module.exports.themeConfig.algolia; } -if (!process.env.HOTJAR_ID) { - delete module.exports.themeConfig.hotjar; -} diff --git a/global.d.ts b/global.d.ts deleted file mode 100644 index 7059c7b05c..0000000000 --- a/global.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -declare global { - interface Window { - essss: string; - ga: (eventType: string, options: { - hitType: string, - eventCategory: string, - eventAction: string, - eventLabel: string, - eventValue: number, - }) => void; - } -} - -export {}; diff --git a/i18n/en/code.json b/i18n/en/code.json index b21fa655f8..64379880c6 100644 --- a/i18n/en/code.json +++ b/i18n/en/code.json @@ -127,18 +127,6 @@ "message": "But there are redirects from old links for compatibility", "description": "NavPage section=legacy subdetails" }, - "features.cookie-consent.alert": { - "message": "🍰 We use cookies for analytics", - "description": "Cookie Consent alert" - }, - "features.cookie-consent.accept": { - "message": "OK", - "description": "Cookie Consent accept label" - }, - "features.cookie-consent.reason": { - "message": "why?", - "description": "Cookie Consent reason label" - }, "features.feedback-badge.label": { "message": "Share your feedback about documentation 🤙", "description": "Feedback share button label" @@ -147,17 +135,61 @@ "message": "https://forms.gle/nsYua6bMMG5iBB3v7", "description": "Feedback share form url" }, - "features.feedback-doc.thanks": { - "message": "Thank you for letting us know!", - "description": "DocFeedback block=Thanks" + "features.feedback-doc.button-text": { + "message": "Feedback", + "description": "The text on a floating button to leave feedback about the docs" + }, + "features.feedback-doc.email-placeholder": { + "message": "Your email (optional)", + "description": "The placeholder for email input" + }, + "features.feedback-doc.error-message": { + "message": "Something went wrong. Please try again later.", + "description": "The error message displayed when feedback form submission fails" + }, + "features.feedback-doc.modal-title-error-403": { + "message": "The request URL does not match the one defined in PushFeedback for this project.", + "description": "The title of the modal displayed when feedback form submission fails with 403 error" + }, + "features.feedback-doc.modal-title-error-404": { + "message": "We could not find the provided project id in PushFeedback.", + "description": "The title of the modal displayed when feedback form submission fails with 404 error" + }, + "features.feedback-doc.message-placeholder": { + "message": "Comments", + "description": "The placeholder for message input" + }, + "features.feedback-doc.modal-title": { + "message": "Share your feedback", + "description": "The title of the modal to leave feedback about the docs" + }, + "features.feedback-doc.modal-title-error": { + "message": "Oops!", + "description": "The title of the modal displayed when feedback form submission fails" + }, + "features.feedback-doc.modal-title-success": { + "message": "Thanks for your feedback!", + "description": "The title of the modal displayed when feedback form submission succeeds" + }, + "features.feedback-doc.screenshot-button-text": { + "message": "Take a Screenshot", + "description": "The text on a button to take a screenshot" + }, + "features.feedback-doc.screenshot-topbar-text": { + "message": "SELECT AN ELEMENT ON THE PAGE", + "description": "The text displayed in the top bar when selecting an element to take a screenshot" + }, + "features.feedback-doc.send-button-text": { + "message": "Send", + "description": "The text on a button to send feedback" }, - "features.feedback-doc.title": { + "features.feedback-doc.rating-placeholder": { "message": "Was this page helpful?", - "description": "DocFeedback block=Title" + "description": "The placeholder for rating input" }, - "features.feedback-doc.subtitle": { - "message": "Your feedback helps us improve the docs", - "description": "DocFeedback block=Subtitle" + "features.feedback-doc.rating-stars-placeholder": { + "message": "How would you rate this page", + "description": "The placeholder for rating stars input" }, "features.hero.tagline": { "message": "Architectural methodology for frontend projects", diff --git a/i18n/en/docusaurus-plugin-content-docs/current.json b/i18n/en/docusaurus-plugin-content-docs/current.json index 9af1eb6531..599055a3cf 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current.json +++ b/i18n/en/docusaurus-plugin-content-docs/current.json @@ -1,6 +1,6 @@ { "version.label": { - "message": "v2.0.0 🍰", + "message": "v2.1", "description": "The label for version current" }, "sidebar.getstartedSidebar.category.Tutorials": { diff --git a/i18n/en/docusaurus-plugin-content-docs/current/about/alternatives.mdx b/i18n/en/docusaurus-plugin-content-docs/current/about/alternatives.mdx index 3d84b7c96e..558d36f156 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/about/alternatives.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/about/alternatives.mdx @@ -70,7 +70,7 @@ How are they similar (to many), how are they different - [(Thread) About use-case/interactor in the methodology](https://t.me/feature_sliced/3897) - [(Thread) About DI in the methodology](https://t.me/feature_sliced/4592) -- [(Article) Alexander Bespalov - Clean Architecture on frontend](https://bespoyasov.ru/blog/clean-architecture-on-frontend/) +- [(Article) Alex Bespoyasov - Clean Architecture on frontend](https://bespoyasov.me/blog/clean-architecture-on-frontend/) - [(Article) DDD, Hexagonal, Onion, Clean, CQRS, ... How I put it all together](https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/) - [(Talk) Ilya Azin - Feature-Sliced Design (fragment about Clean Architecture, DDD)](https://youtu.be/SnzPAr_FJ7w?t=528) - [(Article) Misconceptions of Clean Architecture](http://habr.com/ru/company/mobileup/blog/335382/) @@ -90,18 +90,57 @@ Framework-agnostic, conventional-approach ## Atomic Design - +### What is it? +In Atomic Design, the scope of responsibility is divided into standardized layers. -> About the approach; About applicability in the frontend; FSD position +Atomic Design is broken down into **5 layers** (from top to bottom): + +1. `pages` - Functionality similar to the `pages` layer in FSD. +2. `templates` - Components that define the structure of a page without tying to specific content. +3. `organisms` - Modules consisting of molecules that have business logic. +4. `molecules` - More complex components that generally do not contain business logic. +5. `atoms` - UI components without business logic. + +Modules at one layer interact only with modules in the layers below, similar to FSD. +That is, molecules are built from atoms, organisms from molecules, templates from organisms, and pages from templates. +Atomic Design also implies the use of Public API within modules for isolation. + +### Applicability to frontend + +Atomic Design is relatively common in projects. Atomic Design is more popular among web designers than in development. +Web designers often use Atomic Design to create scalable and easily maintainable designs. +In development, Atomic Design is often mixed with other architectural methodologies. -About compatibility, applicability in methodology and scope of responsibility, mapping layers +However, since Atomic Design focuses on UI components and their composition, a problem arises with implementing +business logic within the architecture. + +The problem is that Atomic Design does not provide a clear level of responsibility for business logic, +leading to its distribution across various components and levels, complicating maintenance and testing. +The business logic becomes blurred, making it difficult to clearly separate responsibilities and rendering +the code less modular and reusable. + +### How does it relate to FSD? + +In the context of FSD, some elements of Atomic Design can be applied to create flexible and scalable UI components. +The `atoms` and `molecules` layers can be implemented in `shared/ui` in FSD, simplifying the reuse and +maintenance of basic UI elements. + +``` +├── shared +│ ├── ui +│ │ ├── atoms +│ │ ├── molecules +│ ... +``` +A comparison of FSD and Atomic Design shows that both methodologies strive for modularity and reusability +but focus on different aspects. Atomic Design is oriented towards visual components and their composition. +FSD focuses on dividing the application's functionality into independent modules and their interconnections. - [Atomic Design Methodology](https://atomicdesign.bradfrost.com/table-of-contents/) - [(Thread) About applicability in shared / ui](https://t.me/feature_sliced/1653) - [(Video) Briefly about Atomic Design](https://youtu.be/Yi-A20x2dcA) - [(Talk) Ilya Azin - Feature-Sliced Design (fragment about Atomic Design)](https://youtu.be/SnzPAr_FJ7w?t=587) - ## Feature Driven diff --git a/i18n/en/docusaurus-plugin-content-docs/current/about/motivation.md b/i18n/en/docusaurus-plugin-content-docs/current/about/motivation.md index 5c6abf21d3..999836bc74 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/about/motivation.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/about/motivation.md @@ -24,7 +24,7 @@ More details [discussed in the discussion][disc-src] > - *"All the problems are solved by good project documentation, tests, and structured processes"* > - *"Problems would not have happened if all developers are following all the above"* > - *"Everything was invented before you, you just can't use it"* -> - *"Take {FRAMEWORK_NAME} - everything has already been decided for you there"* +> - *"Take \{FRAMEWORK_NAME\} - everything has already been decided for you there"* ### Principles alone are not enough diff --git a/i18n/en/docusaurus-plugin-content-docs/current/about/understanding/architecture.md b/i18n/en/docusaurus-plugin-content-docs/current/about/understanding/architecture.md index e81d360bf0..ca33d1f708 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/about/understanding/architecture.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/about/understanding/architecture.md @@ -49,7 +49,7 @@ Therefore, it seems logical to present the desired *requirements for an ideal ar :::note -Wherever it says "easy", it means "relatively easy for a wide range of developers", because it is clear that [it will not be possible to make an ideal solution for absolutely everyone](/docs/about/mission#restrictions) +Wherever it says "easy", it means "relatively easy for a wide range of developers", because it is clear that [it will not be possible to make an ideal solution for absolutely everyone](/docs/about/mission#limitations) ::: diff --git a/i18n/en/docusaurus-plugin-content-docs/current/branding.md b/i18n/en/docusaurus-plugin-content-docs/current/branding.md index 1756a80c3b..1a8a2be675 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/branding.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/branding.md @@ -1,3 +1,5 @@ +import useBaseUrl from '@docusaurus/useBaseUrl'; + # Branding Guidelines FSD's visual identity is based on its core-concepts: `Layered`, `Sliced self-contained parts`, `Parts & Compose`, `Segmented`. @@ -41,30 +43,30 @@ FSD has few variations of logo for different context, but it recommended to pref - + - + - + - +
Sergey Sova
Sergey Sova

📝 📖 💡 🤔 📆 💬 🚇 🔬 📋 📢 🚧
Sergey Sova
Sergey Sova

📝 📖 💡 🤔 📆 💬 🚇 🔬 📋 📢 🚧
Ilya Azin
Ilya Azin

📖 💡 🤔 📆 💬 👀 🚇 📓 🎨 📢 🚧
Rin 🦊🪐😈 Akaia
Rin 🦊🪐😈 Akaia

📖 🖋 🤔 💬 🌍 📢 🚧 🔬
Alexander Khoroshikh
Alexander Khoroshikh

📖 🤔 💬 👀 🔧 🛡️ 📢 🚧
primary
(#29BEDC, #517AED)
logo-primarylogo-primary Preferred in most cases
flat
(#3193FF)
logo-flatlogo-flat For one-color context
monochrome
(#FFF)
logo-monocrhomelogo-monocrhome For grayscale context
square
(#3193FF)
logo-squarelogo-square For square boundaries
## Banners & Schemes -banner-primary -banner-monochrome +banner-primary +banner-monochrome ## Social Preview @@ -78,4 +80,3 @@ Work in progress... - [Discussion (github)](https://github.com/feature-sliced/documentation/discussions/399) - [History of development with references (figma)](https://www.figma.com/file/RPphccpoeasVB0lMpZwPVR/FSD-Brand?node-id=0%3A1) -- [Rebranding demo](https://rebrand-sliced.netlify.app/en/) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/get-started/cheatsheet.mdx b/i18n/en/docusaurus-plugin-content-docs/current/get-started/cheatsheet.mdx index 204c8e329c..6323490ad0 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/get-started/cheatsheet.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/get-started/cheatsheet.mdx @@ -1,5 +1,6 @@ --- -sidebar_position: 3 +# sidebar_position: 3 +unlisted: true --- # Decomposition cheatsheet diff --git a/i18n/en/docusaurus-plugin-content-docs/current/get-started/faq.md b/i18n/en/docusaurus-plugin-content-docs/current/get-started/faq.md index daecfad948..2f9b4bda5b 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/get-started/faq.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/get-started/faq.md @@ -13,7 +13,7 @@ You can ask your question in our [Telegram chat][telegram], [Discord community][ ### Is there a toolkit or a linter? -There is an official ESLint config — [@feature-sliced/eslint-config][eslint-config-official], and an ESLint plugin — [@conarti/eslint-plugin-feature-sliced][eslint-plugin-conarti], created by Aleksandr Belous, a community member. You're welcome to contribute to these projects or start your own! +Yes! We have a linter called [Steiger][ext-steiger] to check your project's architecture and [folder generators][ext-tools] through a CLI or IDEs. ### Where to store the layout/template of pages? @@ -24,7 +24,7 @@ If you need plain markup layouts, you can keep them in `shared/ui`. If you need ### What is the difference between a feature and an entity? -An _entity_ is a real-life concept that your app is working with. A _feature_ is an nteraction that provides real-life value to your app’s users, the thing people want to do with your entities. +An _entity_ is a real-life concept that your app is working with. A _feature_ is an interaction that provides real-life value to your app’s users, the thing people want to do with your entities. For more information, along with examples, see the Reference page on [slices][reference-entities]. @@ -42,7 +42,7 @@ For example, Atomic Design [can be applied well](https://t.me/feature_sliced/165 ### Are there any useful resources/articles/etc. about FSD? -Yes! +Yes! https://github.com/feature-sliced/awesome ### Why do I need Feature-Sliced Design? @@ -58,10 +58,10 @@ Rather yes than no Answered [here](/docs/guides/examples/auth) +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools [import-rule-layers]: /docs/reference/layers#import-rule-on-layers [reference-entities]: /docs/reference/layers#entities -[eslint-config-official]: https://github.com/feature-sliced/eslint-config -[eslint-plugin-conarti]: https://github.com/conarti/eslint-plugin-feature-sliced [motivation]: /docs/about/motivation [telegram]: https://t.me/feature_sliced [discord]: https://discord.gg/S8MzWTUsmp diff --git a/i18n/en/docusaurus-plugin-content-docs/current/get-started/index.mdx b/i18n/en/docusaurus-plugin-content-docs/current/get-started/index.mdx index 0fe374a13c..847414b023 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/get-started/index.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/get-started/index.mdx @@ -3,19 +3,15 @@ hide_table_of_contents: true pagination_prev: intro --- -# 🚀 Get Started +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { RocketOutlined, PlaySquareOutlined, QuestionCircleOutlined } from "@ant-design/icons"; -LEARNING-ORIENTED +# 🚀 Get Started

Welcome! This section helps you to get acquainted with the application of Feature-Sliced Design and the basics of the methodology. You will also understand the key advantages of the methodology and the reasons for its creation.

-## Main - -import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" -import { RocketOutlined, BuildOutlined, PlaySquareOutlined } from "@ant-design/icons"; - +{/* +/> */} diff --git a/i18n/en/docusaurus-plugin-content-docs/current/get-started/overview.md b/i18n/en/docusaurus-plugin-content-docs/current/get-started/overview.md deleted file mode 100644 index 778e5a4dd7..0000000000 --- a/i18n/en/docusaurus-plugin-content-docs/current/get-started/overview.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Overview - -**Feature-Sliced Design** (FSD) is an architectural methodology for scaffolding front-end applications. Simply put, it's a compilation of rules and conventions on organizing code. The main purpose of this methodology is to make the project more understandable and structured in the face of ever-changing business requirements. - -## Is it right for me? - -FSD can be implemented in projects and teams of any size, but there are a few things to keep in mind: - -- This methodology is for front-end projects only. If you're looking for a back-end architecture, consider [Clean Architecture][refs-clean-architecture]. -- This methodology is for user-facing applications only. For inspiration on how to architect a large UI kit, see [Material UI][ext-material-ui]. -- A very simple app of a single page might not need the benefits of FSD and suffer from the overhead. However, FSD promotes a nice way of thinking, so feel free to use it on the tiniest projects if you want. -- A huge app, the size of the Google Cloud admin dashboard, will require a custom architecture. It could still be based on FSD, by the way. - -FSD doesn't enforce a particular programming language, UI framework or state manager — bring your own or see some [examples][refs-examples]. - -If you have an existing project, fear not — FSD can be adopted incrementally. Just make sure that your team is **in pain** from the current architecture, otherwise a switch might not be worth it. For migration guidance, see the [Migration section][refs-migration]. - -## Basics - -In FSD, a project consists of layers, each layer is made up of slices and each slice is made up of segments. - -![themed--scheme](/img/visual_schema.jpg) - -The **layers** are standardized across all projects and vertically arranged. Modules on one layer can only interact with modules from the layers strictly below. There are currently seven of them (bottom to top): - -1. `shared` — reusable functionality, detached from the specifics of the project/business. - (e.g. UIKit, libs, API) -2. `entities` — business entities. - (e.g., User, Product, Order) -3. `features` — user interactions, actions that bring business value to the user. - (e.g. SendComment, AddToCart, UsersSearch) -4. `widgets` — compositional layer to combine entities and features into meaningful blocks. - (e.g. IssuesList, UserProfile) -5. `pages` — compositional layer to construct full pages from entities, features and widgets. -6. `processes` (deprecated) — complex inter-page scenarios. - (e.g., authentication) -7. `app` — app-wide settings, styles and providers. - - -Then there are **slices**, which partition the code by business domain. This makes your codebase easy to navigate by keeping logically related modules close together. Slices cannot use other slices on the same layer, and that helps with high cohesion and low coupling. - -Each slice, in turn, consists of **segments**. These are tiny modules that are meant to help with separating code within a slice by its technical purpose. The most common segments are `ui`, `model` (store, actions), `api` and `lib` (utils/hooks), but you can omit some or add more, as you see fit. - -:::note - -In most cases, [it is recommended][ext-disc-api] to place `api` and `config` only in the shared layer, unless your API client is also your storage (GraphQL, TanStack Query, etc.) - -::: - -## Example - -Let's consider a social network application. - -* `app/` contains setup of routing, store and global styles. -* `pages/` contains the route components for each page in the app, mostly composition, hardly any logic. - -Within that application, let's consider a post card in a news feed. - -* `widgets/` contains the "assembled" post card, with content and interactive buttons that are wired up to the relevant calls on the back-end. -* `features/` contains the interactivity of the card (e.g., like button) and the logic of processing those interactions. -* `entities/` contains the shell of the card with slots for content and the interactive elements. The tile representing the post author is also here, but in a different slice. - -## Advantages - -- **Uniformity** - The code is organized by scope of influence (layers), by domain (slices), and by technical purpose (segments). - This creates a standardized architecture that is easy to comprehend for newcomers. - -- **Controlled reuse of logic** - Each architectural component has its purpose and predictable dependencies. - This keeps a balance between following the **DRY** principle and adaptation possibilities. - -- **Stability in face of changes and refactoring** - A module on a particular layer cannot use other modules on the same layer, or the layers above. - This enables isolated modifications without unforeseen consequences. - -- **Orientation to business and users needs** - When the app is split into business domains, you can navigate the code to discover and deeper understand all the project features. - -## Incremental adoption - -The power of FSD lies in _structured_ decomposition. At its finest, it enables to locate any part of code near-deterministically. However, the level of decomposition is a parameter, and each team can tweak it to strike a balance between simple adoption and the amount of benefits. - -Here's a proposed strategy to migrate an existing codebase to FSD, based on experience: - -1. Start by outlining the `app` and `shared` layers to create a foundation. Usually, these layers are the smallest. - -2. Distribute all of the existing UI across `widgets` and `pages`, even if they have dependencies that violate the rules of FSD. - -3. Start gradually increasing the precision of decomposition by separating `features` and `entities`, turning pages and widgets from logic-bearing layers into purely compositional layers. - -It's advised to refrain from adding new large entities while refactoring or refactoring only certain parts of the project. - -[refs-clean-architecture]: https://medium.com/codex/clean-architecture-for-dummies-df6561d42c94 -[ext-disc-api]: https://github.com/feature-sliced/documentation/discussions/66 -[ext-material-ui]: https://github.com/mui/material-ui -[refs-examples]: /examples -[refs-migration]: /docs/guides/migration diff --git a/i18n/en/docusaurus-plugin-content-docs/current/get-started/overview.mdx b/i18n/en/docusaurus-plugin-content-docs/current/get-started/overview.mdx new file mode 100644 index 0000000000..7df43e0d98 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/current/get-started/overview.mdx @@ -0,0 +1,143 @@ +--- +sidebar_position: 1 +--- + +# Overview + +**Feature-Sliced Design** (FSD) is an architectural methodology for scaffolding front-end applications. Simply put, it's a compilation of rules and conventions on organizing code. The main purpose of this methodology is to make the project more understandable and stable in the face of ever-changing business requirements. + +Apart from a set of conventions, FSD is also a toolchain. We have a [linter][ext-steiger] to check your project's architecture, [folder generators][ext-tools] through a CLI or IDEs, as well as a rich library of [examples][examples]. + +## Is it right for me? {#is-it-right-for-me} + +FSD can be implemented in projects and teams of any size. It is right for your project if: + +- You're doing **frontend** (UI on web, mobile, desktop, etc.) +- You're building an **application**, not a library + +And that's it! There are no restrictions on what programming language, UI framework, or state manager you use. You can also adopt FSD incrementally, use it in monorepos, and scale to great lengths by breaking your app into packages and implementing FSD individually within them. + +If you already have an architecture and you're considering a switch to FSD, make sure that the current architecture is **causing trouble** in your team. For example, if your project has grown too large and inter-connected to efficiently implement new features, or if you're expecting a lot of new members to join the team. If the current architecture works, maybe it's not worth changing. But if you do decide to migrate, see the [Migration][migration] section for guidance. + +## Basic example {#basic-example} + +Here is a simple project that implements FSD: + +- `📁 app` +- `📁 pages` +- `📁 shared` + +These top-level folders are called _layers_. Let's look deeper: + +- `📂 app` + - `📁 routes` + - `📁 analytics` +- `📂 pages` + - `📁 home` + - `📂 article-reader` + - `📁 ui` + - `📁 api` + - `📁 settings` +- `📂 shared` + - `📁 ui` + - `📁 api` + +Folders inside `📂 pages` are called _slices_. They divide the layer by domain (in this case, by pages). + +Folders inside `📂 app`, `📂 shared`, and `📂 pages/article-reader` are called _segments_, and they divide slices (or layers) by technical purpose, i.e. what the code is for. + +## Concepts {#concepts} + +Layers, slices, and segments form a hierarchy like this: + +
+ ![Hierarchy of FSD concepts, described below](/img/visual_schema.jpg) + +
+

Pictured above: three pillars, labeled left to right as "Layers", "Slices", and "Segments" respectively.

+

The "Layers" pillar contains seven divisions arranged top to bottom and labeled "app", "processes", "pages", "widgets", "features", "entities", and "shared". The "processes" division is crossed out. The "entities" division is connected to the second pillar "Slices" in a way that conveys that the second pillar is the content of "entities".

+

The "Slices" pillar contains three divisions arranged top to bottom and labeled "user", "post", and "comment". The "post" division is connected to the third pillar "Segments" in the same way such that it's the content of "post".

+

The "Segments" pillar contains three divisions, arranged top to bottom and labeled "ui", "model", and "api".

+
+
+ +### Layers {#layers} + +Layers are standardized across all FSD projects. You don't have to use all of the layers, but their names are important. There are currently seven of them (from top to bottom): + +1. **App** — everything that makes the app run — routing, entrypoints, global styles, providers. +2. **Processes** (deprecated) — complex inter-page scenarios. +3. **Pages** — full pages or large parts of a page in nested routing. +4. **Widgets** — large self-contained chunks of functionality or UI, usually delivering an entire use case. +5. **Features** — _reused_ implementations of entire product features, i.e. actions that bring business value to the user. +6. **Entities** — business entities that the project works with, like `user` or `product`. +7. **Shared** — reusable functionality, especially when it's detached from the specifics of the project/business, though not necessarily. + +:::warning + +Layers **App** and **Shared**, unlike other layers, do not have slices and are divided into segments directly. + +However, all other layers — **Entities**, **Features**, **Widgets**, and **Pages**, retain the structure in which you must first create slices, inside which you create the segments. + +::: +The trick with layers is that modules on one layer can only know about and import from modules from the layers strictly below. + +### Slices {#slices} + +Next up are slices, which partition the code by business domain. You're free to choose any names for them, and create as many as you wish. Slices make your codebase easier to navigate by keeping logically related modules close together. + +Slices cannot use other slices on the same layer, and that helps with high cohesion and low coupling. + +### Segments {#segments} + +Slices, as well as layers App and Shared, consist of segments, and segments group your code by its purpose. Segment names are not constrained by the standard, but there are several conventional names for the most common purposes: + +- `ui` — everything related to UI display: UI components, date formatters, styles, etc. +- `api` — backend interactions: request functions, data types, mappers, etc. +- `model` — the data model: schemas, interfaces, stores, and business logic. +- `lib` — library code that other modules on this slice need. +- `config` — configuration files and feature flags. + +Usually these segments are enough for most layers, you would only create your own segments in Shared or App, but this is not a rule. + +## Advantages {#advantages} + +- **Uniformity** + Since the structure is standardized, projects become more uniform, which makes onboarding new members easier for the team. + +- **Stability in face of changes and refactoring** + A module on one layer cannot use other modules on the same layer, or the layers above. + This allows you to make isolated modifications without unforeseen consequences to the rest of the app. + +- **Controlled reuse of logic** + Depending on the layer, you can make code very reusable or very local. + This keeps a balance between following the **DRY** principle and practicality. + +- **Orientation to business and users needs** + The app is split into business domains and usage of the business language is encouraged in naming, so that you can do useful product work without fully understanding all other unrelated parts of the project. + +## Incremental adoption {#incremental-adoption} + +If you have an existing codebase that you want to migrate to FSD, we suggest the following strategy. We found it useful in our own migration experience. + +1. Start by slowly shaping up the App and Shared layers module-by-module to create a foundation. + +2. Distribute all of the existing UI across Widgets and Pages using broad strokes, even if they have dependencies that violate the rules of FSD. + +3. Start gradually resolving import violations and also extracting Entities and possibly even Features. + +It's advised to refrain from adding new large entities while refactoring or refactoring only certain parts of the project. + +## Next steps {#next-steps} + +- **Want to get a good grasp of how to think in FSD?** Check out the [Tutorial][tutorial]. +- **Prefer to learn from examples?** We have a lot in the [Examples][examples] section. +- **Have questions?** Drop by our [Telegram chat][ext-telegram] and get help from the community. + +[tutorial]: /docs/get-started/tutorial +[examples]: /examples +[migration]: /docs/guides/migration/from-custom +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools +[ext-telegram]: https://t.me/feature_sliced + diff --git a/i18n/en/docusaurus-plugin-content-docs/current/get-started/tutorial.md b/i18n/en/docusaurus-plugin-content-docs/current/get-started/tutorial.md index 725f325f06..662020d924 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/get-started/tutorial.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/get-started/tutorial.md @@ -1,829 +1,2262 @@ --- sidebar_position: 2 --- - # Tutorial -Let's consider the application of **Feature-Sliced Design** on the example of TodoApp +## Part 1. On paper -- At first, we will prepare application basely (bootstrap, routing, styles) -- Then we will consider - how the concepts of the methodology help *flexibly and effectively design business logic* without unnecessary costs +This tutorial will examine the Real World App, also known as Conduit. Conduit is a basic [Medium](https://medium.com/) clone — it lets you read and write articles as well as comment on the articles of others. -> There is [codesandbox-insert with the final solution][ext-sandbox], which can help to clarify the implementation details at the end of the article +![Conduit home page](/img/tutorial/realworld-feed-anonymous.jpg) -**Stack**: React, Effector, TypeScript, Sass, AntDesign +This is a pretty small application, so we will keep it simple and avoid excessive decomposition. It’s highly likely that the entire app will fit into just three layers: **App**, **Pages**, and **Shared**. If not, we will introduce additional layers as we go. Ready? -:::note +### Start by listing the pages -The tutorial is designed to **reveal the practical idea of the methodology itself**. Therefore, the practices described here are largely suitable for other technological stacks of frontend projects +If we look at the screenshot above, we can assume at least the following pages: -::: +- Home (article feed) +- Sign in and sign up +- Article reader +- Article editor +- User profile viewer +- User profile editor (user settings) -## 1. Preparation +Every one of these pages will become its own *slice* on the Pages *layer*. Recall from the overview that slices are simply folders inside of layers and layers are simply folders with predefined names like `pages`. -### 1.1 Initializing the project +As such, our Pages folder will look like this: -At the moment, there are many ways to generate and run a project template +``` +📂 pages/ + 📁 feed/ + 📁 sign-in/ + 📁 article-read/ + 📁 article-edit/ + 📁 profile/ + 📁 settings/ +``` -We will not focus too much on this step, but for quick initialization, you can use [CRA (for React)](https://create-react-app.dev/docs/getting-started): +The key difference of Feature-Sliced Design from an unregulated code structure is that pages cannot reference each other. That is, one page cannot import code from another page. This is due to the **import rule on layers**: -```cmd -$ npx create-react-app todo-app --template typescript -``` +*A module (file) in a slice can only import other slices when they are located on layers strictly below.* -### 1.2 Preparing the structure +In this case, a page is a slice, so modules (files) inside this page can only reference code from layers below, not from the same layer, Pages. -We received the following blank for the project +### Close look at the feed -```sh -└── src/ - ├── App.css - ├── App.test.tsx - ├── App.tsx - ├── index.css - ├── index.ts - ├── logo.svg - ├── react-app-env.d.ts - ├── reportWebVitals.ts - ├── setupTests.ts - └── index.tsx/ -``` +
+ ![Anonymous user’s perspective](/img/tutorial/realworld-feed-anonymous.jpg) +
+ _Anonymous user’s perspective_ +
+
-#### How it usually happens +
+ ![Authenticated user’s perspective](/img/tutorial/realworld-feed-authenticated.jpg) +
+ _Authenticated user’s perspective_ +
+
-And usually most projects at this stage [turn into something like this][ext-pluralsight--flat]: +There are three dynamic areas on the feed page: -```sh -└── src/ - ├── api/ - ├── components/ - ├── containers/ - ├── helpers/ - ├── pages/ - ├── routes/ - ├── store/ - ├── App.tsx - └── index.tsx/ -``` +1. Sign-in links with an indication if you are signed in +2. List of tags that triggers filtering in the feed +3. One/two feeds of articles, each article with a like button + +The sign-in links are a part of a header that is common to all pages, we will revisit it separately. + +#### List of tags + +To build the list of tags, we need to fetch the available tags, render each tag as a chip, and store the selected tags in a client-side storage. These operations fall into categories “API interaction”, “user interface”, and “storage”, respectively. In Feature-Sliced Design, code is separated by purpose using *segments*. Segments are folders in slices, and they can have arbitrary names that describe the purpose, but some purposes are so common that there’s a convention for certain segment names: -*They can become such immediately, or after a long development* +- 📂 `api/` for backend interactions +- 📂 `ui/` for code that handles rendering and appearance +- 📂 `model/` for storage and business logic +- 📂 `config/` for feature flags, environment variables and other forms of configuration -At the same time, if we look inside we will most likely find: +We will place code that fetches tags into `api`, the tag component into `ui`, and the storage interaction into `model`. -- Highly coupled directories by nesting -- Strongly connected components with each other -- A huge number of dissimilar components / containers in their respective folders, linked thoughtlessly +#### Articles -#### How can it be done otherwise +Using the same grouping principles, we can decompose the feed of articles into the same three segments: -Anyone who has been developing frontend projects for at least a long time understands the advantages and disadvantages of this approach. +- 📂 `api/`: fetch paginated articles with like count; like an article +- 📂 `ui/`: + - tab list that can render an extra tab if a tag is selected + - individual article + - functional pagination +- 📂 `model/`: client-side storage of the currently loaded articles and current page (if needed) -However, most frontend projects are still something like this, since **there is no proven flexible and extensible alternative** +### Reuse generic code -*Multiply this by the free adaptations of the structure for each project, without a ban from the framework-and [we get "projects as unique as snowflakes"][refs-motivation]* +Most pages are very different in intent, but certain things stay the same across the entire app — for example, the UI kit that conforms to the design language, or the convention on the backend that everything is done with a REST API with the same authentication method. Since slices are meant to be isolated, code reuse is facilitated by a lower layer, **Shared**. -**The purpose of this tutorial** is to show a different view of the usual practices in designing +Shared is different from other layers in the sense that it contains segments, not slices. In this way, the Shared layer can be thought of as a hybrid between a layer and a slice. -#### Adapting the structure to the desired view +Usually, the code in Shared is not planned ahead of time, but rather extracted during development, because only during development does it become clear which parts of code are actually shared. However, it’s still helpful to keep a mental note of what kind of code naturally belongs in Shared: -```sh -└── src/ - ├── app/ # Initializing application logic - | ├── index.tsx # Entrypoint for connecting the application (formerly App. tsx) - | └── index.css # Global application styles - ├── pages/ # - ├── widgets/ # - ├── features/ # - ├── entities/ # - ├── shared/ # - └── index.tsx # Connecting and rendering the application +- 📂 `ui/` — the UI kit, pure appearance, no business logic. For example, buttons, modal dialogs, form inputs. +- 📂 `api/` — convenience wrappers around request making primitives (like `fetch()` on the Web) and, optionally, functions for triggering particular requests according to the backend specification. +- 📂 `config/` — parsing environment variables +- 📂 `i18n/` — configuration of language support +- 📂 `router/` — routing primitives and route constants + +Those are just a few examples of segment names in Shared, but you can omit any of them or create your own. The only important thing to remember when creating new segments is that segment names should describe **purpose (the why), not essence (the what)**. Names like “components”, “hooks”, “modals” *should not* be used because they describe what these files are, but don’t help to navigate the code inside. This requires people on the team to dig through every file in such folders and also keeps unrelated code close, which leads to broad areas of code being affected by refactoring and thus makes code review and testing harder. + +### Define a strict public API + +In the context of Feature-Sliced Design, the term *public API* refers to a slice or segment declaring what can be imported from it by other modules in the project. For example, in JavaScript that can be an `index.js` file re-exporting objects from other files in the slice. This enables freedom in refactoring code inside a slice as long as the contract with the outside world (i.e. the public API) stays the same. + +For the Shared layer that has no slices, it’s usually more convenient to define a separate public API for each segment as opposed to defining one single index of everything in Shared. This keeps imports from Shared naturally organized by intent. For other layers that have slices, the opposite is true — it’s usually more practical to define one index per slice and let the slice decide its own set of segments that is unknown to the outside world because other layers usually have a lot less exports. + +Our slices/segments will appear to each other as follows: + +``` +📂 pages/ + 📂 feed/ + 📄 index + 📂 sign-in/ + 📄 index + 📂 article-read/ + 📄 index + 📁 … +📂 shared/ + 📂 ui/ + 📄 index + 📂 api/ + 📄 index + 📁 … ``` -At first glance the structure may seem strange, but over time you will notice that **you use familiar abstractions, but in a consistent and ordered form.** +Whatever is inside folders like `pages/feed` or `shared/ui` is only known to those folders, and other files should not rely on the internal structure of these folders. + +### Large reused blocks in the UI + +Earlier we made a note to revisit the header that appears on every page. Rebuilding it from scratch on every page would be impractical, so it’s only natural to want to reuse it. We already have Shared to facilitate code reuse, however, there’s a caveat to putting large blocks of UI in Shared — the Shared layer is not supposed to know about any of the layers above. + +Between Shared and Pages there are three other layers: Entities, Features, and Widgets. Some projects may have something in those layers that they need in a large reusable block, and that means we can’t put that reusable block in Shared, or else it would be importing from upper layers, which is prohibited. That’s where the Widgets layer comes in. It is located above Shared, Entities, and Features, so it can use them all. + +In our case, the header is very simple — it’s a static logo and top-level navigation. The navigation needs to make a request to the API to determine if the user is currently logged in or not, but that can be handled by a simple import from the `api` segment. Therefore, we will keep our header in Shared. + +### Close look at a page with a form + +Let’s also examine a page that’s intended for editing, not reading. For example, the article writer: + +![Conduit post editor](/img/tutorial/realworld-editor-authenticated.jpg) + +It looks trivial, but contains several aspects of application development that we haven’t explored yet — form validation, error states, and data persistence. + +If we were to build this page, we would grab some inputs and buttons from Shared and put together a form in the `ui` segment of this page. Then, in the `api` segment, we would define a mutation request to create the article on the backend. + +To validate the request before sending, we need a validation schema, and a good place for it is the `model` segment, since it’s the data model. There we will produce error messages and display them using another component in the `ui` segment. + +To improve user experience, we could also persist the inputs to prevent accidental data loss. This is also a job of the `model` segment. + +### Summary + +We have examined several pages and outlined a preliminary structure for our application: + +1. Shared layer + 1. `ui` will contain our reusable UI kit + 2. `api` will contain our primitive interactions with the backend + 3. The rest will be arranged on demand +2. Pages layer — each page is a separate slice + 1. `ui` will contain the page itself and all of its parts + 2. `api` will contain more specialized data fetching, using `shared/api` + 3. `model` might contain client-side storage of the data that we will display + +Let’s get building! + +## Part 2. In code + +Now that we have a plan, let’s put it to practice. We will use React and [Remix](https://remix.run). + +There's a template ready for this project, clone it from GitHub to get a headstart: [https://github.com/feature-sliced/tutorial-conduit/tree/clean](https://github.com/feature-sliced/tutorial-conduit/tree/clean). + +Install dependencies with `npm install` and start the development server with `npm run dev`. Open [http://localhost:3000](http://localhost:3000) and you should see a blank app. -**Also, we enable support for absolute imports for convenience** +### Lay out the pages -```ts title=tsconfig.json -{ - "compilerOptions": { - "baseUrl": "./src", - // Or aliases, if it's more convenient +Let’s start by creating blank components for all our pages. Run the following command in your project: + +```bash +npx fsd pages feed sign-in article-read article-edit profile settings --segments ui ``` -Here's how it will help us in the future +This will create folders like `pages/feed/ui/` and an index file, `pages/feed/index.ts`, for every page. + +### Connect the feed page + +Let’s connect the root route of our application to the feed page. Create a component, `FeedPage.tsx` in `pages/feed/ui` and put the following inside it: -```diff -- import App from "../app" -- import Button from "../../shared/ui/button"; -+ import App from "app" -+ import Button from "shared/ui/button"; + +```tsx title="pages/feed/ui/FeedPage.tsx" +export function FeedPage() { + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+
+ ); +} ``` -#### Layers: app +Then re-export this component in the feed page’s public API, the `pages/feed/index.ts` file: + + -As you can see , we have moved all the basic logic to the `app/` directory +```ts title="pages/feed/index.ts" +export { FeedPage } from "./ui/FeedPage"; +``` -It is there, according to the methodology, that all the preparatory logic should be placed: +Now connect it to the root route. In Remix, routing is file-based, and the route files are located in the `app/routes` folder, which nicely coincides with Feature-Sliced Design. -- connecting global styles (`/app/styles/**` + `/app/index.css`) -- providers and HOCs with initializing logic (`/app/providers/**`) +Use the `FeedPage` component in `app/routes/_index.tsx`: -For now, we will transfer all the existing logic there, and leave the other directories empty, as in the diagram above. +```tsx title="app/routes/_index.tsx" +import type { MetaFunction } from "@remix-run/node"; +import { FeedPage } from "pages/feed"; -```tsx title=app/index.tsx -import "./index.css"; +export const meta: MetaFunction = () => { + return [{ title: "Conduit" }]; +}; -const App = () => {...} +export default FeedPage; ``` -### 1.3 Enabling global styles +Then, if you run the dev server and open the application, you should see the Conduit banner! + +![The banner of Conduit](/img/tutorial/conduit-banner.jpg) -#### Install dependencies +### API client -In the tutorial, we install sass, but you can also take any other preprocessor that supports imports +To talk to the RealWorld backend, let’s create a convenient API client in Shared. Create two segments, `api` for the client and `config` for variables like the backend base URL: -```cmd -$ npm i sass +```bash +npx fsd shared --segments api config ``` -#### Creating files for styles +Then create `shared/config/backend.ts`: -##### For css variables +```tsx title="shared/config/backend.ts" +export const backendBaseUrl = "https://api.realworld.io/api"; +``` -```scss title=app/styles/vars.scss -:root { - --color-dark: #242424; - --color-primary: #108ee9; - ... -} +```tsx title="shared/config/index.ts" +export { backendBaseUrl } from "./backend"; ``` -##### To normalize styles +Since the RealWorld project conveniently provides an [OpenAPI specification](https://github.com/gothinkster/realworld/blob/main/api/openapi.yml), we can take advantage of auto-generated types for our client. We will use [the `openapi-fetch` package](https://openapi-ts.pages.dev/openapi-fetch/) that comes with an additional type generator. -```scss title=app/styles/normalize.scss -html { - scroll-behavior: smooth; -} -... +Run the following command to generate up-to-date API typings: + +```bash +npm run generate-api-types ``` -##### Connecting all styles +This will create a file `shared/api/v1.d.ts`. We will use this file to create a typed API client in `shared/api/client.ts`: + +```tsx title="shared/api/client.ts" +import createClient from "openapi-fetch"; + +import { backendBaseUrl } from "shared/config"; +import type { paths } from "./v1"; -```scss title=app/styles/index.scss -@import "./normalize.scss"; -@import "./vars.scss"; -... +export const { GET, POST, PUT, DELETE } = createClient({ baseUrl: backendBaseUrl }); ``` -```scss title=app/index.scss -@import "./styles/index.scss"; -... +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; ``` -```tsx title=app/index.tsx -import "./index.scss" +### Real data in the feed -const App = () => {...} +We can now proceed to adding articles to the feed, fetched from the backend. Let’s begin by implementing an article preview component. + +Create `pages/feed/ui/ArticlePreview.tsx` with the following content: + +```tsx title="pages/feed/ui/ArticlePreview.tsx" +export function ArticlePreview({ article }) { /* TODO */ } ``` -### 1.4 Adding routing +Since we’re writing in TypeScript, it would be nice to have a typed article object. If we explore the generated `v1.d.ts`, we can see that the article object is available through `components["schemas"]["Article"]`. So let’s create a file with our data models in Shared and export the models: -#### Install dependencies +```tsx title="shared/api/models.ts" +import type { components } from "./v1"; -```cmd -$ npm i react-router react-router-dom compose-function -$ npm i -D @types/react-router @types/react-router-dom @types/compose-function +export type Article = components["schemas"]["Article"]; ``` -#### Add HOC to initialize the router +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; -```tsx title=app/providers/with-router.tsx -import { Suspense } from "react"; -import { BrowserRouter } from "react-router-dom"; +export type { Article } from "./models"; +``` -export const withRouter = (component: () => React.ReactNode) => () => ( - - - {component()} - - -); +Now we can come back to the article preview component and fill the markup with data. Update the component with the following content: + +```tsx title="pages/feed/ui/ArticlePreview.tsx" +import { Link } from "@remix-run/react"; +import type { Article } from "shared/api"; + +interface ArticlePreviewProps { + article: Article; +} + +export function ArticlePreview({ article }: ArticlePreviewProps) { + return ( +
+
+ + + +
+ + {article.author.username} + + + {new Date(article.createdAt).toLocaleDateString(undefined, { + dateStyle: "long", + })} + +
+ +
+ +

{article.title}

+

{article.description}

+ Read more... +
    + {article.tagList.map((tag) => ( +
  • + {tag} +
  • + ))} +
+ +
+ ); +} ``` -```ts title=app/providers/index.ts -import compose from "compose-function"; -import { withRouter } from "./with-router"; +The like button doesn’t do anything for now, we will fix that when we get to the article reader page and implement the liking functionality. + +Now we can fetch the articles and render out a bunch of these cards. Fetching data in Remix is done with *loaders* — server-side functions that fetch exactly what a page needs. Loaders interact with the API on the page’s behalf, so we will put them in the `api` segment of a page: + +```tsx title="pages/feed/api/loader.ts" +import { json } from "@remix-run/node"; + +import { GET } from "shared/api"; + +export const loader = async () => { + const { data: articles, error, response } = await GET("/articles"); + + if (error !== undefined) { + throw json(error, { status: response.status }); + } -export const withProviders = compose(withRouter); + return json({ articles }); +}; +``` + +To connect it to the page, we need to export it with the name `loader` from the route file: + +```tsx title="pages/feed/index.ts" +export { FeedPage } from "./ui/FeedPage"; +export { loader } from "./api/loader"; +``` + +```tsx title="app/routes/_index.tsx" +import type { MetaFunction } from "@remix-run/node"; +import { FeedPage } from "pages/feed"; + +export { loader } from "pages/feed"; + +export const meta: MetaFunction = () => { + return [{ title: "Conduit" }]; +}; + +export default FeedPage; ``` -```tsx title=app/index.tsx -import { withProviders } from "./providers"; -... +And the final step is to render these cards in the feed. Update your `FeedPage` with the following code: + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { useLoaderData } from "@remix-run/react"; + +import type { loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; -const App = () => {...} +export function FeedPage() { + const { articles } = useLoaderData(); -export default withProviders(App); + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ {articles.articles.map((article) => ( + + ))} +
+
+
+
+ ); +} ``` -#### Let's add real pages +### Filtering by tag -:::note +Regarding the tags, our job is to fetch them from the backend and to store the currently selected tag. We already know how to do fetching — it’s another request from the loader. We will use a convenience function `promiseHash` from a package `remix-utils`, which is already installed. -This is just one of the routing implementations +Update the loader file, `pages/feed/api/loader.ts`, with the following code: -- You can declare it declaratively or through the list of routes (+ react-router-config) -- You can declare it at the pages or app level +```tsx title="pages/feed/api/loader.ts" +import { json } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; -The methodology does not yet regulate the implementation of this logic in any way +import { GET } from "shared/api"; -::: +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; -##### Temporary page, only for checking the routing + if (error !== undefined) { + throw json(error, { status: response.status }); + } -You can delete it later + return data as NonNullable; +} -```tsx title=pages/test/index.tsx -const TestPage = () => { - return
Test Page
; +export const loader = async () => { + return json( + await promiseHash({ + articles: throwAnyErrors(GET("/articles")), + tags: throwAnyErrors(GET("/tags")), + }), + ); }; +``` -export default TestPage; +You might notice that we extracted the error handling into a generic function `throwAnyErrors`. It looks pretty useful, so we might want to reuse it later, but for now let’s just keep an eye on it. + +Now, to the list of tags. It needs to be interactive — clicking on a tag should make that tag selected. By Remix convention, we will use the URL search parameters as our storage for the selected tag. Let the browser take care of storage while we focus on more important things. + +Update `pages/feed/ui/FeedPage.tsx` with the following code: + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { Form, useLoaderData } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import type { loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; + +export function FeedPage() { + const { articles, tags } = useLoaderData(); + + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ {articles.articles.map((article) => ( + + ))} +
+ +
+
+

Popular Tags

+ +
+ +
+ {tags.tags.map((tag) => ( + + ))} +
+ +
+
+
+
+
+ ); +} ``` -##### Let's form the routes +Then we need to use the `tag` search parameter in our loader. Change the `loader` function in `pages/feed/api/loader.ts` to the following: -```tsx title=pages/index.tsx -// Or use @loadable/component, as part of the tutorial - uncritically -import { lazy } from "react"; -import { Route, Routes, Navigate } from "react-router-dom"; +```tsx title="pages/feed/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; -const TestPage = lazy(() => import("./test")); +import { GET } from "shared/api"; -export const Routing = () => { - return ( - - } /> - } /> - - ); +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const selectedTag = url.searchParams.get("tag") ?? undefined; + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles", { params: { query: { tag: selectedTag } } }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); }; ``` -##### Connecting the routing to the application +That’s it, no `model` segment necessary. Remix is pretty neat. -```tsx title=app/index.tsx -import { Routing } from "pages"; +### Pagination -const App = () => ( - // Potentially you can insert here - // A single header for the entire application - // Or do it on separate pages - -) -... -``` +In a similar fashion, we can implement the pagination. Feel free to give it a shot yourself or just copy the code below. There’s no one to judge you anyway. -#### Layers: app, pages +```tsx title="pages/feed/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; -Here we used several layers at once: +import { GET } from "shared/api"; -- `app` - to initialize the router *(HOC: withRouter)* -- `pages` - for storing page modules +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; -### 1.5 Let's connect UIKit + if (error !== undefined) { + throw json(error, { status: response.status }); + } -To simplify the tutorial, we will use the ready-made UIKit from [AntDesign](https://ant.design/components/overview/) + return data as NonNullable; +} -```cmd -$ npm i antd @ant-design/icons +/** Amount of articles on one page. */ +export const LIMIT = 20; + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const selectedTag = url.searchParams.get("tag") ?? undefined; + const page = parseInt(url.searchParams.get("page") ?? "", 10); + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles", { + params: { + query: { + tag: selectedTag, + limit: LIMIT, + offset: !Number.isNaN(page) ? page * LIMIT : undefined, + }, + }, + }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); +}; ``` -:::tip +```tsx title="pages/feed/ui/FeedPage.tsx" +import { Form, useLoaderData, useSearchParams } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import { LIMIT, type loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; -But you can use **any other UIKit** or **create your own** by placing the components in `shared/ui` - this is where it is recommended to place UIKit of application: +export function FeedPage() { + const [searchParams] = useSearchParams(); + const { articles, tags } = useLoaderData(); + const pageAmount = Math.ceil(articles.articlesCount / LIMIT); + const currentPage = parseInt(searchParams.get("page") ?? "1", 10); -```ts -import { Checkbox } from "antd"; // ~ "shared/ui/checkbox" -import { Card } from "antd"; // ~ "shared/ui/card" + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ {articles.articles.map((article) => ( + + ))} + +
+ +
    + {Array(pageAmount) + .fill(null) + .map((_, index) => + index + 1 === currentPage ? ( +
  • + {index + 1} +
  • + ) : ( +
  • + +
  • + ), + )} +
+ +
+ +
+
+

Popular Tags

+ +
+ +
+ {tags.tags.map((tag) => ( + + ))} +
+ +
+
+
+
+
+ ); +} ``` -::: +So that’s also done. There’s also the tab list that can be similarly implemented, but let’s hold on to that until we implement authentication. Speaking of which! + +### Authentication + +Authentication involves two pages — one to login and another to register. They are mostly the same, so it makes sense to keep them in the same slice, `sign-in`, so that they can reuse code if needed. + +Create `RegisterPage.tsx` in the `ui` segment of `pages/sign-in` with the following content: + +```tsx title="pages/sign-in/ui/RegisterPage.tsx" +import { Form, Link, useActionData } from "@remix-run/react"; -## 2. Implementing business logic +import type { register } from "../api/register"; -:::note +export function RegisterPage() { + const registerData = useActionData(); + + return ( +
+
+
+
+

Sign up

+

+ Have an account? +

+ + {registerData?.error && ( +
    + {registerData.error.errors.body.map((error) => ( +
  • {error}
  • + ))} +
+ )} + +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+ ); +} +``` -We will try to focus not on the implementation of each module, but on their sequential composition +We have a broken import to fix now. It involves a new segment, so create that: -::: +```bash +npx fsd pages sign-in -s api +``` -### 2.1 Let's analyze the functionality +However, before we can implement the backend part of registering, we need some infrastructure code for Remix to handle sessions. That goes to Shared, in case any other page needs it. -Before starting the code, we need to decide - [what value we want to convey to the end user][refs-needs] +Put the following code in `shared/api/auth.server.ts`. This is highly Remix-specific, so don’t worry too much about it, just copy-paste: -To do this, we decompose our functionality *by responsibility scopes [(layers)][refs-layers]* +```tsx title="shared/api/auth.server.ts" +import { createCookieSessionStorage, redirect } from "@remix-run/node"; +import invariant from "tiny-invariant"; -![layers-flow-themed](/img/layers_flow.png) +import type { User } from "./models"; -#### Pages +invariant( + process.env.SESSION_SECRET, + "SESSION_SECRET must be set for authentication to work", +); -We will outline the basic necessary pages, and user expectations from them: +const sessionStorage = createCookieSessionStorage<{ + user: User; +}>({ + cookie: { + name: "__session", + httpOnly: true, + path: "/", + sameSite: "lax", + secrets: [process.env.SESSION_SECRET], + secure: process.env.NODE_ENV === "production", + }, +}); -1. `TasksListPage` - the "Task List" page +export async function createUserSession({ + request, + user, + redirectTo, +}: { + request: Request; + user: User; + redirectTo: string; +}) { + const cookie = request.headers.get("Cookie"); + const session = await sessionStorage.getSession(cookie); + + session.set("user", user); + + return redirect(redirectTo, { + headers: { + "Set-Cookie": await sessionStorage.commitSession(session, { + maxAge: 60 * 60 * 24 * 7, // 7 days + }), + }, + }); +} - - View the task list - - Go to the page of a specific task - - *Mark a specific task completed/unfulfilled* - - Set filtering by completed / unfulfilled tasks +export async function getUserFromSession(request: Request) { + const cookie = request.headers.get("Cookie"); + const session = await sessionStorage.getSession(cookie); -2. `TaskDetailsPage` - page "Task card" + return session.get("user") ?? null; +} - - View information about the task - - *Mark a specific task as completed/unfulfilled* - - Go back to the task list +export async function requireUser(request: Request) { + const user = await getUserFromSession(request); -Each of the described features is a part of the functionality + if (user === null) { + throw redirect("/login"); + } -##### Usual approach + return user; +} +``` -And there is a great temptation +And also export the `User` model from the `models.ts` file right next to it: -- or implement all the logic in the directory of each specific page. -- or put all" possibly reused "modules in the shared folder `src/components` or similar +```tsx title="shared/api/models.ts" +import type { components } from "./v1"; -But if such a solution would be suitable for a small and short-lived project, then in real corporate development, it **can put an end** to the further development of the project, turning it into **"another dense legacy"** +export type Article = components["schemas"]["Article"]; +export type User = components["schemas"]["User"]; +``` -This is due to the usual conditions of the project development: +Before this code can work, the `SESSION_SECRET` environment variable needs to be set. Create a file called `.env` in the root of the project, write `SESSION_SECRET=` and then mash some keys on your keyboard to create a long random string. You should get something like this: -- requirements change quite often -- there are new circumstances -- the technical debt is accumulating every day and it is becoming more difficult to add new features -- it is necessary to scale both the project itself and its team +```bash title=".env" +SESSION_SECRET=dontyoudarecopypastethis +``` -##### Alternative approach +Finally, add some exports to the public API to make use of this code: -Even with the basic partitioning, we see that: +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; -- there are common entities between the pages and their display *(Task)* -- there are common features *between the pages (Mark the task completed / unfulfilled)* +export type { Article } from "./models"; -Accordingly, it seems logical to continue to decompose the task, but already based on the above-mentioned features for the user. +export { createUserSession, getUserFromSession, requireUser } from "./auth.server"; +``` -#### Features +Now we can write the code that will talk to the RealWorld backend to actually do the registration. We will keep that in `pages/sign-in/api`. Create a file called `register.ts` and put the following code inside: -Parts of functionality that bring value to the user +```tsx title="pages/sign-in/api/register.ts" +import { json, type ActionFunctionArgs } from "@remix-run/node"; -- `` - (component) Mark a task as completed / unfulfilled -- `` - (component) Set filtering for the task list +import { POST, createUserSession } from "shared/api"; -#### Entities +export const register = async ({ request }: ActionFunctionArgs) => { + const formData = await request.formData(); + const username = formData.get("username")?.toString() ?? ""; + const email = formData.get("email")?.toString() ?? ""; + const password = formData.get("password")?.toString() ?? ""; -Business entities on which a higher-level logic will be built + const { data, error } = await POST("/users", { + body: { user: { email, password, username } }, + }); -- `` - (component) Task card, with information display -- `getTasksListFx({ filters })` - (effect) Loading the task list with parameters -- `getTaskByIdFx(taskId: number)`- (effect) Uploading a task by ID + if (error) { + return json({ error }, { status: 400 }); + } else { + return createUserSession({ + request: request, + user: data.user, + redirectTo: "/", + }); + } +}; +``` -#### Shared +```tsx title="pages/sign-in/index.ts" +export { RegisterPage } from './ui/RegisterPage'; +export { register } from './api/register'; +``` -Reused shared modules, without binding to the domain scopes +Almost done! Just need to connect the page and action to the `/register` route. Create `register.tsx` in `app/routes`: -- `` - (component) UIKit component - - *At the same time, you can either implement your own UIKit for the project, or use a ready-made one* -- `getTasksList({ filters })` - (api) Loading the task list with parameters -- `getTaskById(taskId: number)` - (api) Loading a task by ID +```tsx title="app/routes/register.tsx" +import { RegisterPage, register } from "pages/sign-in"; -#### What is the profit? +export { register as action }; -Now all modules can be designed with [low coupling][refs-low-coupling] and [with their own scope of responsibility][refs-layers], as well as distributed across the team without conflicts during development +export default RegisterPage; +``` -*And most importantly, now each module serves to build a specific business value, which reduces the risks for creating ["features for the sake of features"][refs-needs]* +Now if you go to [http://localhost:3000/register](http://localhost:3000/register), you should be able to create a user! The rest of the application won’t react to this yet, we’ll address that momentarily. -### 2.2 What else is worth remembering +In a very similar way, we can implement the login page. Give it a try or just grab the code and move on: -#### Layers and responsibilities +```tsx title="pages/sign-in/api/sign-in.ts" +import { json, type ActionFunctionArgs } from "@remix-run/node"; -As described above, thanks to the layered structure, we can **predictably distribute the complexity of the application** according to [scopes of responsibility, i.e. layers][refs-layers]. +import { POST, createUserSession } from "shared/api"; -At the same time, a higher-level logic is built on the basis of the underlying layers: +export const signIn = async ({ request }: ActionFunctionArgs) => { + const formData = await request.formData(); + const email = formData.get("email")?.toString() ?? ""; + const password = formData.get("password")?.toString() ?? ""; -```tsx -// (shared) => (entities) + (features) => (pages) - + => + => + const { data, error } = await POST("/users/login", { + body: { user: { email, password } }, + }); + + if (error) { + return json({ error }, { status: 400 }); + } else { + return createUserSession({ + request: request, + user: data.user, + redirectTo: "/", + }); + } +}; ``` -#### Preparing modules for use +```tsx title="pages/sign-in/ui/SignInPage.tsx" +import { Form, Link, useActionData } from "@remix-run/react"; + +import type { signIn } from "../api/sign-in"; -Each implemented module must provide its own [public interface][refs-public-api] for use: +export function SignInPage() { + const signInData = useActionData(); + + return ( +
+
+
+
+

Sign in

+

+ Need an account? +

+ + {signInData?.error && ( +
    + {signInData.error.errors.body.map((error) => ( +
  • {error}
  • + ))} +
+ )} + +
+
+ +
+
+ +
+ +
+
+
+
+
+ ); +} +``` -```ts title={layer}/foo/index.ts -export { Card as FooCard, Thumbnail as FooThumbnail, ... } from "./ui"; -export * as fooModel from "./model"; +```tsx title="pages/sign-in/index.ts" +export { RegisterPage } from './ui/RegisterPage'; +export { register } from './api/register'; +export { SignInPage } from './ui/SignInPage'; +export { signIn } from './api/sign-in'; ``` -:::info +```tsx title="app/routes/login.tsx" +import { SignInPage, signIn } from "pages/sign-in"; + +export { signIn as action }; -If you need named namespace exports for the Public API declaration, you can look aside [@babel/plugin-proposal-export-namespace-from](https://babeljs.io/docs/en/babel-plugin-proposal-export-namespace-from) +export default SignInPage; +``` -Or, as an alternative, use a more detailed design +Now let’s give the users a way to actually get to these pages. -```ts title={layer}/foo/index.ts -import { Card as FooCard, Thumbnail as FooThumbnail, ... } from "./ui"; -import * as fooModel from "./model"; +### Header -export { FooCard, FooThumbnail, fooModel }; +As we discussed in part 1, the app header is commonly placed either in Widgets or in Shared. We will put it in Shared because it’s very simple and all the business logic can be kept outside of it. Let’s create a place for it: + +```bash +npx fsd shared ui ``` -::: +Now create `shared/ui/Header.tsx` with the following contents: -### 2.3 Let's display the basic task list +```tsx title="shared/ui/Header.tsx" +import { useContext } from "react"; +import { Link, useLocation } from "@remix-run/react"; -#### (entities) Task card +import { CurrentUser } from "../api/currentUser"; -```tsx title=entities/task/ui/task-row/index.tsx -import { Link } from "react-router-dom"; -import cn from "classnames"; // we can safely use the analogy -import { Row } from "antd"; // ~ "shared/ui/row" +export function Header() { + const currentUser = useContext(CurrentUser); + const { pathname } = useLocation(); -export const TaskRow = ({ data, titleHref }: TaskRowProps) => { - return ( - - {titleHref ? {data.title} : data.title} - - ) + return ( + + ); } ``` -#### (entities) Loading the task list +Export this component from `shared/ui`: -You can split it by the type of entity, or store everything in the duck-modular style +```tsx title="shared/ui/index.ts" +export { Header } from "./Header"; +``` -> For more information about the implementation of the API according to the tutorial, see [here][ext-source-api] +In the header, we rely on the context that’s kept in `shared/api`. Create that as well: -```ts title=entities/task/model/index.ts -import { createStore, combine, createEffect, createEvent } from "effector"; -import { useStore } from "effector-react"; +```tsx title="shared/api/currentUser.ts" +import { createContext } from "react"; -import { typicodeApi } from "shared/api"; -import type { Task } from "shared/api"; +import type { User } from "./models"; -// Each effect can also have its own additional. processing -const getTasksListFx = createEffect((params?: typicodeApi.tasks.GetTasksListParams) => { - // There may also be an additional processing the effect - return typicodeApi.tasks.getTasksList(params); -}); +export const CurrentUser = createContext(null); +``` -// Can also be stored in a normalized form -export const $tasks = createStore([]) - .on(getTasksListFx.doneData, (_, payload) => ...) +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; -export const $tasksList = combine($tasks, (tasks) => Object.values(tasks)); -// You can also add other things like `isEmpty`, `isLoading`, ... +export type { Article } from "./models"; + +export { createUserSession, getUserFromSession, requireUser } from "./auth.server"; +export { CurrentUser } from "./currentUser"; ``` -#### (pages) Let's connect all the logic on the page +Now let’s add the header to the page. We want it to be on every single page, so it makes sense to simply add it to the root route and wrap the outlet (the place where the page will be rendered) with the `CurrentUser` context provider. This way our entire app and also the header has access to the current user object. We will also add a loader to actually obtain the current user object from cookies. Drop the following into `app/root.tsx`: + +```tsx title="app/root.tsx" +import { cssBundleHref } from "@remix-run/css-bundle"; +import type { LinksFunction, LoaderFunctionArgs } from "@remix-run/node"; +import { + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration, + useLoaderData, +} from "@remix-run/react"; -```tsx title=pages/tasks-list/index.tsx -import { useEffect } from "react"; -// If you feel confident with @effector/reflect - can use it -// Within the tutorial non-critical -import { useStore } from "effector"; -import { Layout, Row, Col, Typography, Spin, Empty } from "antd"; // ~ "shared/ui/{...}" +import { Header } from "shared/ui"; +import { getUserFromSession, CurrentUser } from "shared/api"; -import { TaskRow, taskModel } from "entities/task"; -import styles from "./styles.module.scss"; +export const links: LinksFunction = () => [ + ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []), +]; -const TasksListPage = () => { - const tasks = useStore(taskModel.$tasksList); - const isLoading = useStore(taskModel.$tasksListLoading); - const isEmpty = useStore(taskModel.$tasksListEmpty); +export const loader = ({ request }: LoaderFunctionArgs) => + getUserFromSession(request); - /** - * Requesting data when loading the page - * @remark is a bad practice in the effector world and is presented here-just for a visual demonstration - * It is better to fetch via event.pageMounted or reflect - */ - useEffect(() => taskModel.getTasksListFx(), []); +export default function App() { + const user = useLoaderData(); return ( - - - - Tasks List - - {/* TODO: TasksFilters */} - - - - {isLoading && } - {!isLoading && tasks.map((task) => ( - - - + + + + + + + + + + + + + +
+ + + + + + + + ); +} +``` + +At this point, you should end up with the following on the home page: + +
+ ![The feed page of Conduit, including the header, the feed, and the tags. The tabs are still missing.](/img/tutorial/realworld-feed-without-tabs.jpg) + +
The feed page of Conduit, including the header, the feed, and the tags. The tabs are still missing.
+
+ +### Tabs + +Now that we can detect the authentication state, let’s also quickly implement the tabs and post likes to be done with the feed page. We need another form, but this page file is getting kind of large, so let’s move these forms into adjacent files. We will create `Tabs.tsx`, `PopularTags.tsx`, and `Pagination.tsx` with the following content: + +```tsx title="pages/feed/ui/Tabs.tsx" +import { useContext } from "react"; +import { Form, useSearchParams } from "@remix-run/react"; + +import { CurrentUser } from "shared/api"; + +export function Tabs() { + const [searchParams] = useSearchParams(); + const currentUser = useContext(CurrentUser); + + return ( +
+
+
    + {currentUser !== null && ( +
  • + +
  • + )} +
  • + +
  • + {searchParams.has("tag") && ( +
  • + + {searchParams.get("tag")} + +
  • + )} +
+
+
+ ); +} +``` + +```tsx title="pages/feed/ui/PopularTags.tsx" +import { Form, useLoaderData } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import type { loader } from "../api/loader"; + +export function PopularTags() { + const { tags } = useLoaderData(); + + return ( +
+

Popular Tags

+ +
+ +
+ {tags.tags.map((tag) => ( + ))} - {!isLoading && isEmpty && } - - - +
+ +
); -}; +} ``` -### 2.4 Adding task status switching +```tsx title="pages/feed/ui/Pagination.tsx" +import { Form, useLoaderData, useSearchParams } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; -#### (entities) Switching the task status +import { LIMIT, type loader } from "../api/loader"; -```ts title=entities/task/model/index.ts -export const toggleTask = createEvent(); +export function Pagination() { + const [searchParams] = useSearchParams(); + const { articles } = useLoaderData(); + const pageAmount = Math.ceil(articles.articlesCount / LIMIT); + const currentPage = parseInt(searchParams.get("page") ?? "1", 10); + + return ( +
+ +
    + {Array(pageAmount) + .fill(null) + .map((_, index) => + index + 1 === currentPage ? ( +
  • + {index + 1} +
  • + ) : ( +
  • + +
  • + ), + )} +
+ + ); +} +``` -export const $tasks = createStore(...) - ... - .on(toggleTask, (state, taskId) => produce(state, draft => { - const task = draft[taskId]; - task.completed = !task.completed; - console.log(1, { taskId, state, draft: draft[taskId].completed }); - })) +And now we can significantly simplify the feed page itself: +```tsx title="pages/feed/ui/FeedPage.tsx" +import { useLoaderData } from "@remix-run/react"; -// We make a hook to get involved in updates react -// @see In the case of effector, using a hook is an extreme measure, since computed stores are more preferable -export const useTask = (taskId: number): import("shared/api").Task | undefined => { - return useStoreMap({ - store: $tasks, - keys: [taskId], - fn: (tasks, [id]) => tasks[id] ?? null - }); +import type { loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; +import { Tabs } from "./Tabs"; +import { PopularTags } from "./PopularTags"; +import { Pagination } from "./Pagination"; + +export function FeedPage() { + const { articles } = useLoaderData(); + + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ + + {articles.articles.map((article) => ( + + ))} + + +
+ +
+ +
+
+
+
+ ); +} +``` + +We also need to account for the new tab in the loader function: + +```tsx title="pages/feed/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET, requireUser } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + /* unchanged */ +} + +/** Amount of articles on one page. */ +export const LIMIT = 20; + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const selectedTag = url.searchParams.get("tag") ?? undefined; + const page = parseInt(url.searchParams.get("page") ?? "", 10); + + if (url.searchParams.get("source") === "my-feed") { + const userSession = await requireUser(request); + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles/feed", { + params: { + query: { + limit: LIMIT, + offset: !Number.isNaN(page) ? page * LIMIT : undefined, + }, + }, + headers: { Authorization: `Token ${userSession.token}` }, + }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); + } + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles", { + params: { + query: { + tag: selectedTag, + limit: LIMIT, + offset: !Number.isNaN(page) ? page * LIMIT : undefined, + }, + }, + }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); }; ``` -#### (features) Checkbox for the task +Before we leave the feed page, let’s add some code that handles likes to posts. Change your `ArticlePreview.tsx` to the following: -```tsx title=features/toggle-task/ui.tsx -import { Checkbox } from "antd"; // ~ "shared/ui/checkbox" -import { taskModel } from "entities/task"; +```tsx title="pages/feed/ui/ArticlePreview.tsx" +import { Form, Link } from "@remix-run/react"; +import type { Article } from "shared/api"; -// resolve / unresolve -export const ToggleTask = ({ taskId }: ToggleTaskProps) => { - const task = taskModel.useTask(taskId); - if (!task) return null; +interface ArticlePreviewProps { + article: Article; +} - return ( - taskModel.toggleTask(taskId)} - checked={task.completed} - /> - ) +export function ArticlePreview({ article }: ArticlePreviewProps) { + return ( +
+
+ + + +
+ + {article.author.username} + + + {new Date(article.createdAt).toLocaleDateString(undefined, { + dateStyle: "long", + })} + +
+
+ +
+
+ +

{article.title}

+

{article.description}

+ Read more... +
    + {article.tagList.map((tag) => ( +
  • + {tag} +
  • + ))} +
+ +
+ ); } ``` -#### (pages) Embedding the checkbox in the page +This code will send a POST request to `/article/:slug` with `_action=favorite` to mark the article as favorite. It won’t work yet, but as we start working on the article reader, we will implement this too. -What is noteworthy is that the task card does not know at all about the page where it is used, nor about what action buttons can be inserted into it (the same can be said about the feature itself) +And with that we are officially done with the feed! Yay! -This approach allows you to simultaneously **competently share responsibility** and **flexibly reuse logic during implementation** +### Article reader -```tsx title=pages/tasks-list/index.tsx -import { ToggleTask } from "features/toggle-task"; -import { TaskRow, taskModel } from "entities/task"; -... - - } - /> - +First, we need data. Let’s create a loader: + +```bash +npx fsd pages article-read -s api ``` -### 2.5 Adding task filtering +```tsx title="pages/article-read/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import invariant from "tiny-invariant"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; -#### (entities) Filtering at the data level +import { GET, getUserFromSession } from "shared/api"; -```ts title=entities/task/model/index.ts -import { combine, createEvent, createStore } from "effector"; +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; -export type QueryConfig = { completed?: boolean }; + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +export const loader = async ({ request, params }: LoaderFunctionArgs) => { + invariant(params.slug, "Expected a slug parameter"); + const currentUser = await getUserFromSession(request); + const authorization = currentUser + ? { Authorization: `Token ${currentUser.token}` } + : undefined; + + return json( + await promiseHash({ + article: throwAnyErrors( + GET("/articles/{slug}", { + params: { + path: { slug: params.slug }, + }, + headers: authorization, + }), + ), + comments: throwAnyErrors( + GET("/articles/{slug}/comments", { + params: { + path: { slug: params.slug }, + }, + headers: authorization, + }), + ), + }), + ); +}; +``` -const setQueryConfig = createEvent(); +```tsx title="pages/article-read/index.ts" +export { loader } from "./api/loader"; +``` -// Can be moved to a separate directory (for storing multiple models) -export const $queryConfig = createStore({}) - .on(setQueryConfig, (_, payload) => payload); +Now we can connect it to the route `/article/:slug` by creating the a route file called `article.$slug.tsx`: -/** - * Filtered Tasks - * @remark Can be handled at the effects level - but then you need to connect additional logic to the store - * > For example, hide / show the task at the `toggleTask` event - */ -export const $tasksFiltered = combine( - $tasksList, - $queryConfig, - (tasksList, config) => { - return tasksList.filter(task => ( - config.completed === undefined || - task.completed === config.completed - ))}, -); +```tsx title="app/routes/article.$slug.tsx" +export { loader } from "pages/article-read"; ``` -#### (features) UI controls for filters +The page itself consists of three main blocks — the article header with actions (repeated twice), the article body, and the comments section. This is the markup for the page, it’s not particularly interesting: -```tsx title=features/tasks-filters/ui.tsx -// If you feel confident with @effector/reflect, you can immediately use it -// As part of tutorial uncritically -import { useStore } from "effector"; -import { Radio } from "antd"; // ~ "shared/ui/radio" +```tsx title="pages/article-read/ui/ArticleReadPage.tsx" +import { useLoaderData } from "@remix-run/react"; -import { taskModel } from "entities/task"; -import { filtersList, getFilterById, DEFAULT_FILTER } from "./config"; +import type { loader } from "../api/loader"; +import { ArticleMeta } from "./ArticleMeta"; +import { Comments } from "./Comments"; -export const const TasksFilters = () => { - const isLoading = useStore($tasksListLoading); +export function ArticleReadPage() { + const { article } = useLoaderData(); return ( - - {filtersList.map(({ title, id }) => ( - taskModel.setQueryConfig(getFilterById(id).config)} - value={id} - disabled={isLoading} - > - {title} - - ))} - +
+
+
+

{article.article.title}

+ + +
+
+ +
+
+
+

{article.article.body}

+
    + {article.article.tagList.map((tag) => ( +
  • + {tag} +
  • + ))} +
+
+
+ +
+ +
+ +
+ +
+ +
+
+
); +} +``` + +What’s more interesting is the `ArticleMeta` and `Comments`. They contain write operations such as liking an article, leaving a comment, etc. To get them to work, we first need to implement the backend part. Create `action.ts` in the `api` segment of the page: + +```tsx title="pages/article-read/api/action.ts" +import { redirect, type ActionFunctionArgs } from "@remix-run/node"; +import { namedAction } from "remix-utils/named-action"; +import { redirectBack } from "remix-utils/redirect-back"; +import invariant from "tiny-invariant"; + +import { DELETE, POST, requireUser } from "shared/api"; + +export const action = async ({ request, params }: ActionFunctionArgs) => { + const currentUser = await requireUser(request); + + const authorization = { Authorization: `Token ${currentUser.token}` }; + + const formData = await request.formData(); + + return namedAction(formData, { + async delete() { + invariant(params.slug, "Expected a slug parameter"); + await DELETE("/articles/{slug}", { + params: { path: { slug: params.slug } }, + headers: authorization, + }); + return redirect("/"); + }, + async favorite() { + invariant(params.slug, "Expected a slug parameter"); + await POST("/articles/{slug}/favorite", { + params: { path: { slug: params.slug } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async unfavorite() { + invariant(params.slug, "Expected a slug parameter"); + await DELETE("/articles/{slug}/favorite", { + params: { path: { slug: params.slug } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async createComment() { + invariant(params.slug, "Expected a slug parameter"); + const comment = formData.get("comment"); + invariant(typeof comment === "string", "Expected a comment parameter"); + await POST("/articles/{slug}/comments", { + params: { path: { slug: params.slug } }, + headers: { ...authorization, "Content-Type": "application/json" }, + body: { comment: { body: comment } }, + }); + return redirectBack(request, { fallback: "/" }); + }, + async deleteComment() { + invariant(params.slug, "Expected a slug parameter"); + const commentId = formData.get("id"); + invariant(typeof commentId === "string", "Expected an id parameter"); + const commentIdNumeric = parseInt(commentId, 10); + invariant( + !Number.isNaN(commentIdNumeric), + "Expected a numeric id parameter", + ); + await DELETE("/articles/{slug}/comments/{id}", { + params: { path: { slug: params.slug, id: commentIdNumeric } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async followAuthor() { + const authorUsername = formData.get("username"); + invariant( + typeof authorUsername === "string", + "Expected a username parameter", + ); + await POST("/profiles/{username}/follow", { + params: { path: { username: authorUsername } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async unfollowAuthor() { + const authorUsername = formData.get("username"); + invariant( + typeof authorUsername === "string", + "Expected a username parameter", + ); + await DELETE("/profiles/{username}/follow", { + params: { path: { username: authorUsername } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + }); }; ``` -#### (pages) Implementing filtering in the page +Export that from the slice and then from the route. While we’re at it, let’s also connect the page itself: -And we implemented the logic again, without asking too many questions: +```tsx title="pages/article-read/index.ts" +export { ArticleReadPage } from "./ui/ArticleReadPage"; +export { loader } from "./api/loader"; +export { action } from "./api/action"; +``` -- And where to put the filtering logic? -- Can these filters be reused in the future? -- Can filters know about the page context? +```tsx title="app/routes/article.$slug.tsx" +import { ArticleReadPage } from "pages/article-read"; -We just divided the logic according to the scopes of responsibility (layers) +export { loader, action } from "pages/article-read"; -```tsx title=pages/tasks-list/index.tsx -import { TasksFilters } from "features/tasks-filters"; -... - - ... - - - - +export default ArticleReadPage; ``` -:::note +Now, even though we haven’t implemented the like button on the reader page yet, the like button in the feed will start working! That’s because it’s been sending “like” requests to this route. Give that a try. -**At the current stage, such a division may seem superfluous - "Why not put everything at once at the page / feature level"?** +`ArticleMeta` and `Comments` are, again, a bunch of forms. We’ve done this before, let’s grab their code and move on: -But then let's try to ask questions ourselves: +```tsx title="pages/article-read/ui/ArticleMeta.tsx" +import { Form, Link, useLoaderData } from "@remix-run/react"; +import { useContext } from "react"; -- Where are the guarantees that the complexity of the page will not increase in the future so much that all aspects of logic will be strongly intertwined? How can I add new functionality at no extra cost? -- Where are the guarantees that a new person who has joined the team (or even you, if you leave the project for six months) will understand what is happening here? -- How to build logic so as not to disrupt the data flow / reactivity with other features? -- What if this filtering logic is so strongly attached to the context of the page that it will be impossible to use it on other pages? +import { CurrentUser } from "shared/api"; +import type { loader } from "../api/loader"; -This is why we **divide the responsibility** so that each layer is engaged in only one task, and so that each of the developers understands this +export function ArticleMeta() { + const currentUser = useContext(CurrentUser); + const { article } = useLoaderData(); -::: + return ( +
+
+ + + + +
+ + {article.article.author.username} + + {article.article.createdAt} +
+ + {article.article.author.username == currentUser?.username ? ( + <> + + Edit Article + +    + + + ) : ( + <> + + +    + + + )} +
+
+ ); +} +``` -### 2.6 Task Page +```tsx title="pages/article-read/ui/Comments.tsx" +import { useContext } from "react"; +import { Form, Link, useLoaderData } from "@remix-run/react"; -We implement the task page in the same way: +import { CurrentUser } from "shared/api"; +import type { loader } from "../api/loader"; -- We highlight the shared logic -- We highlight the entities logic -- We highlight the features logic -- We highlight the pages logic +export function Comments() { + const { comments } = useLoaderData(); + const currentUser = useContext(CurrentUser); -#### (pages) The"Task Card" page + return ( +
+ {currentUser !== null ? ( +
+
+ +
+
+ + +
+
+ ) : ( +
+
+

+ Sign in +   or   + Sign up +   to add comments on this article. +

+
+
+ )} + + {comments.comments.map((comment) => ( +
+
+

{comment.body}

+
+ +
+ + + +   + + {comment.author.username} + + {comment.createdAt} + {comment.author.username === currentUser?.username && ( + +
+ + +
+
+ )} +
+
+ ))} +
+ ); +} +``` -```tsx title=pages/task-details/index.tsx -import { ToggleTask } from "features/toggle-task"; -import { TaskCard, taskModel } from "entities/task"; -import { Layout, Button } from "antd"; // ~ "shared/ui/{...}" -import styles from "./styles.module.scss"; +And with that our article reader is also complete! The buttons to follow the author, like a post, and leave a comment should now function as expected. -const TaskDetailsPage = (props: Props) => { - const taskId = Number(props.match?.params.taskId); - const task = taskModel.useTask(taskId); - const isLoading = useStore(taskModel.$taskDetailsLoading); +
+ ![Article reader with functioning buttons to like and follow](/img/tutorial/realworld-article-reader.jpg) - /** - * Requesting data on the task - * @remark is a bad practice in the effector world and is presented here-just for a visual demonstration - * It is better to fetch via event.pageMounted or reflect - */ - useEffect(() => taskModel.getTaskByIdFx({ taskId }), [taskId]); +
Article reader with functioning buttons to like and follow
+
- // You can transfer part of the logic to entity/task/card (as a container) - if (!task && !isLoading) { - return ... - } +### Article editor - return ( - - - Back to TasksList} - actions={[ - - ]} - /> - - - ) -}; +This is the last page that we will cover in this tutorial, and the most interesting part here is how we’re going to validate form data. + +The page itself, `article-edit/ui/ArticleEditPage.tsx`, will be quite simple, extra complexity stowed away into two other components: + +```tsx title="pages/article-edit/ui/ArticleEditPage.tsx" +import { Form, useLoaderData } from "@remix-run/react"; + +import type { loader } from "../api/loader"; +import { TagsInput } from "./TagsInput"; +import { FormErrors } from "./FormErrors"; + +export function ArticleEditPage() { + const article = useLoaderData(); + + return ( +
+
+
+
+ + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+
+
+
+
+
+ ); +} ``` -### 2.7 What's next? +This page gets the current article (unless we’re writing from scratch) and fills in the corresponding form fields. We’ve seen this before. The interesting part is `FormErrors`, because it will receive the validation result and display it to the user. Let’s take a look: + +```tsx title="pages/article-edit/ui/FormErrors.tsx" +import { useActionData } from "@remix-run/react"; +import type { action } from "../api/action"; + +export function FormErrors() { + const actionData = useActionData(); + + return actionData?.errors != null ? ( +
    + {actionData.errors.map((error) => ( +
  • {error}
  • + ))} +
+ ) : null; +} +``` + +Here we are assuming that our action will return the `errors` field, an array of human-readable error messages. We will get to the action shortly. + +Another component is the tags input. It’s just a plain input field with an additional preview of chosen tags. Not much to see here: + +```tsx title="pages/article-edit/ui/TagsInput.tsx" +import { useEffect, useRef, useState } from "react"; -And then new tasks arrive, new requirements are identified +export function TagsInput({ + name, + defaultValue, +}: { + name: string; + defaultValue?: Array; +}) { + const [tagListState, setTagListState] = useState(defaultValue ?? []); -At the same time, the old code base does not require significant rework + function removeTag(tag: string): void { + const newTagList = tagListState.filter((t) => t !== tag); + setTagListState(newTagList); + } -#### Has the functionality tied to the user appeared? + const tagsInput = useRef(null); + useEffect(() => { + tagsInput.current && (tagsInput.current.value = tagListState.join(",")); + }, [tagListState]); -=> Adding `entities/user` + return ( + <> + + setTagListState(e.target.value.split(",").filter(Boolean)) + } + /> +
+ {tagListState.map((tag) => ( + + + [" ", "Enter"].includes(e.key) && removeTag(tag) + } + onClick={() => removeTag(tag)} + >{" "} + {tag} + + ))} +
+ + ); +} +``` -#### Did you need to change the filtering logic? +Now, for the API part. The loader should look at the URL, and if it contains an article slug, that means we’re editing an existing article, and its data should be loaded. Otherwise, return nothing. Let’s create that loader: -=> Changing the processing at the `entities` or `pages` level, depending on the scale +```ts title="pages/article-edit/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; -#### Do you need to add more features to the task card, but at the same time, so that it can be used in the old way? +import { GET, requireUser } from "shared/api"; -=> Add features and insert them into the card only on the desired **page** +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; -#### Has a module become too complex to support? + if (error !== undefined) { + throw json(error, { status: response.status }); + } - => Thanks to the embedded architecture, we can only factor this module in isolation-without implicit side effects for others [(and even rewrite it from scratch)](https://youtu.be/BWAeYuWFHhs?t=1625) + return data as NonNullable; +} -## Summary +export const loader = async ({ params, request }: LoaderFunctionArgs) => { + const currentUser = await requireUser(request); -### We have learned how to apply the methodology for basic cases + if (!params.slug) { + return { article: null }; + } -Obviously, the world is much more complicated, but now we have already caught on to some controversial points and resolved them in such a way that the project remains supported and extensible. + return throwAnyErrors( + GET("/articles/{slug}", { + params: { path: { slug: params.slug } }, + headers: { Authorization: `Token ${currentUser.token}` }, + }), + ); +}; +``` -### We got a scalable and flexible codebase +The action will take the new field values, run them through our data schema, and if everything is correct, commit those changes to the backend, either by updating an existing article or creating a new one: -1. Reused and expandable modules +```tsx title="pages/article-edit/api/action.ts" +import { json, redirect, type ActionFunctionArgs } from "@remix-run/node"; - - *shared, features, entities* +import { POST, PUT, requireUser } from "shared/api"; +import { parseAsArticle } from "../model/parseAsArticle"; -1. Uniform and predictable distribution of logic +export const action = async ({ request, params }: ActionFunctionArgs) => { + try { + const { body, description, title, tags } = parseAsArticle( + await request.formData(), + ); + const tagList = tags?.split(",") ?? []; + + const currentUser = await requireUser(request); + const payload = { + body: { + article: { + title, + description, + body, + tagList, + }, + }, + headers: { Authorization: `Token ${currentUser.token}` }, + }; + + const { data, error } = await (params.slug + ? PUT("/articles/{slug}", { + params: { path: { slug: params.slug } }, + ...payload, + }) + : POST("/articles", payload)); + + if (error) { + return json({ errors: error }, { status: 422 }); + } - - *Since the composition goes in the same direction (the overlying layers use the underlying ones) , we can predictably track and modify it without fear of unforeseen consequences* + return redirect(`/article/${data.article.slug ?? ""}`); + } catch (errors) { + return json({ errors }, { status: 400 }); + } +}; +``` -1. The structure of the application, which tells about the business logic for itself +The schema doubles as a parsing function for `FormData`, which allows us to conveniently get the clean fields or just throw the errors to handle at the end. Here’s how that parsing function could look: + +```tsx title="pages/article-edit/model/parseAsArticle.ts" +export function parseAsArticle(data: FormData) { + const errors = []; + + const title = data.get("title"); + if (typeof title !== "string" || title === "") { + errors.push("Give this article a title"); + } + + const description = data.get("description"); + if (typeof description !== "string" || description === "") { + errors.push("Describe what this article is about"); + } + + const body = data.get("body"); + if (typeof body !== "string" || body === "") { + errors.push("Write the article itself"); + } + + const tags = data.get("tags"); + if (typeof tags !== "string") { + errors.push("The tags must be a string"); + } + + if (errors.length > 0) { + throw errors; + } + + return { title, description, body, tags: data.get("tags") ?? "" } as { + title: string; + description: string; + body: string; + tags: string; + }; +} +``` - - What pages are there? - - `TasksList`, `TaskDetails` - - What features are there? What can the user do? - - `ToggleTask` `TasksFilters` - - What are the business entities? What is the work being done with? - - `Task (TaskCard, ...)` - - What can be reused from the auxiliary? - - `UIKit (Card, ...)` `API (tasksApi)` +Arguably, it’s a bit lengthy and repetitive, but that’s the price we pay for human-readable errors. This could also be a Zod schema, for example, but then we would have to render error messages on the frontend, and this form is not worth the complication. -### Example +One last step — connect the page, the loader, and the action to the routes. Since we neatly support both creation and editing, we can export the same thing from both `editor._index.tsx` and `editor.$slug.tsx`: -Below in [Codesandbox][ext-sandbox] is an example of the resulting TodoApp, where you can study in detail the final structure of the application +```tsx title="pages/article-edit/index.ts" +export { ArticleEditPage } from "./ui/ArticleEditPage"; +export { loader } from "./api/loader"; +export { action } from "./api/action"; +``` - +```tsx title="app/routes/editor._index.tsx, app/routes/editor.$slug.tsx (same content)" +import { ArticleEditPage } from "pages/article-edit"; -## See also +export { loader, action } from "pages/article-edit"; -- [(Overview) How to Organize Your React + Redux Codebase][ext-pluralsight] - - Analysis of several approaches to structuring React projects -- [Guides and examples of the methodology application (+ Migration from v1)][refs-guides] -- [Reference material on the methodology][refs-reference] +export default ArticleEditPage; +``` -[refs-motivation]: /docs/about/motivation +We’re done now! Log in and try creating a new article. Or “forget” to write the article and see the validation kick in. -[refs-needs]: /docs/about/understanding/needs-driven -[refs-public-api]: /docs/reference/public-api +
+ ![The Conduit article editor, with the title field saying “New article” and the rest of the fields empty. Above the form there are two errors: “**Describe what this article is about” and “Write the article itself”.**](/img/tutorial/realworld-article-editor.jpg) -[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion -[refs-guides]: /docs/guides -[refs-reference]: /docs/reference -[refs-layers]: /docs/reference/layers +
The Conduit article editor, with the title field saying “New article” and the rest of the fields empty. Above the form there are two errors: **“Describe what this article is about”** and **“Write the article itself”**.
+
-[ext-pluralsight]: https://www.pluralsight.com/guides/how-to-organize-your-react-+-redux-codebase -[ext-pluralsight--flat]: https://www.pluralsight.com/guides/how-to-organize-your-react-+-redux-codebase#module-theflatstructure -[ext-sandbox]: https://codesandbox.io/s/github/feature-sliced/examples/tree/master/todo-app -[ext-source-api]: https://github.com/feature-sliced/examples/tree/master/todo-app/src/shared/api +The profile and settings pages are very similar to the article reader and editor, they are left as an exercise for the reader, that’s you :) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/auth.md b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/auth.md new file mode 100644 index 0000000000..bf8a33b9e2 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/auth.md @@ -0,0 +1,225 @@ +--- +sidebar_position: 1 +--- + +# Authentication + +Broadly, authentication consists of the following steps: + +1. Get the credentials from the user +1. Send them to the backend +1. Store the token to make authenticated requests + +## How to get credentials from the user + +We are assuming that your app is responsible for getting credentials. If you have authentication via OAuth, you can simply create a login page with a link to the OAuth provider's login page and skip to [step 3](#how-to-store-the-token-for-authenticated-requests). + +### Dedicated page for login + +Usually, websites have dedicated pages for login, where you enter your username and password. These pages are quite simple, so they don't require decomposition. Login and registration forms are quite similar in appearance, so they can even be grouped into one page. Create a slice for your login/registration page on the Pages layer: + +- 📂 pages + - 📂 login + - 📂 ui + - 📄 LoginPage.tsx (or your framework's component file format) + - 📄 RegisterPage.tsx + - 📄 index.ts + - other pages… + +Here we created two components and exported them both in the index file of the slice. These components will contain forms that are responsible for presenting the user with understandable controls to get their credentials. + +### Dialog for login + +If your app has a dialog for login that can be used on any page, consider making that dialog a widget. That way, you can still avoid too much decomposition, but have the freedom to reuse this dialog on any page. + +- 📂 widgets + - 📂 login-dialog + - 📂 ui + - 📄 LoginDialog.tsx + - 📄 index.ts + - other widgets… + +The rest of this guide is written for the dedicated page approach, but the same principles apply to the dialog widget. + +### Client-side validation + +Sometimes, especially for registration, it makes sense to perform client-side validation to let the user know quickly that they made a mistake. Validation can take place in the `model` segment of the login page. Use a schema validation library, for example, [Zod][ext-zod] for JS/TS, and expose that schema to the `ui` segment: + +```ts title="pages/login/model/registration-schema.ts" +import { z } from "zod"; + +export const registrationData = z.object({ + email: z.string().email(), + password: z.string().min(6), + confirmPassword: z.string(), +}).refine((data) => data.password === data.confirmPassword, { + message: "Passwords do not match", + path: ["confirmPassword"], +}); +``` + +Then, in the `ui` segment, you can use this schema to validate the user input: + +```tsx title="pages/login/ui/RegisterPage.tsx" +import { registrationData } from "../model/registration-schema"; + +function validate(formData: FormData) { + const data = Object.fromEntries(formData.entries()); + try { + registrationData.parse(data); + } catch (error) { + // TODO: Show error message to the user + } +} + +export function RegisterPage() { + return ( +
validate(new FormData(e.target))}> + + + + + + + + +
+ ) +} +``` + +## How to send credentials to the backend + +Create a function that makes a request to your backend's login endpoint. This function can either be called directly in the component code using a mutation library (e.g. TanStack Query), or it can be called as a side effect in a state manager. + +### Where to store the request function + +There are two places you can put this function: in `shared/api`, or in the `api` segment of the page. + +#### In `shared/api` + +This approach goes well with when you put all your API requests in `shared/api`, grouped by endpoint, for example. The file structure might look like this: + +- 📂 shared + - 📂 api + - 📂 endpoints + - 📄 login.ts + - other endpoint functions… + - 📄 client.ts + - 📄 index.ts + +The `📄 client.ts` file contains a wrapper around your request-making primitive (for example, `fetch()`). This wrapper would know about the base URL of your backend, set necessary headers, serialize data correctly, etc. + +```ts title="shared/api/endpoints/login.ts" +import { POST } from "../client"; + +export function login({ email, password }: { email: string, password: string }) { + return POST("/login", { email, password }); +} +``` + +```ts title="shared/api/index.ts" +export { login } from "./endpoints/login"; +``` + +#### In the `api` segment of the page + +If you don't keep all your requests in one place, consider stashing the login request in the `api` segment of the login page. + +- 📂 pages + - 📂 login + - 📂 api + - 📄 login.ts + - 📂 ui + - 📄 LoginPage.tsx + - 📄 index.ts + - other pages… + +```ts title="pages/login/api/login.ts" +import { POST } from "shared/api"; + +export function login({ email, password }: { email: string, password: string }) { + return POST("/login", { email, password }); +} +``` + +You don't have to export the `login()` function in the page's public API, because it's unlikely that any other place in the app will need this request. + +### Two-factor authentication + +If your app supports two-factor authentication (2FA), you might have to redirect to another page where a user can enter a one-time password. Usually your `POST /login` request would return the user object with a flag indicating that the user has 2FA enabled. If that flag is set, redirect the user to the 2FA page. + +Since this page is very related to logging in, you can also keep it in the same slice, `login` on the Pages layer. + +You would also need another request function, similar to `login()` that we created above. Place them together, either in Shared, or in the `api` segment of the `login` page. + +## How to store the token for authenticated requests {#how-to-store-the-token-for-authenticated-requests} + +Regardless of the authentication scheme you have, be it a simple login & password, OAuth, or two-factor authentication, at the end you will receive a token. This token should be stored so that subsequent requests can identify themselves. + +The ideal token storage for a web app is a **cookie** — it requires no manual token storage or handling. As such, cookie storage needs almost no consideration from the frontend architecture side. If your frontend framework has a server side (for example, [Remix][ext-remix]), then you should store the server-side cookie infrastructure in `shared/api`. There is an example in [the Authentication section of the tutorial][tutorial-authentication] of how to do that with Remix. + +Sometimes, however, cookie storage is not an option. In this case, you will have to store the token manually. Apart from storing the token, you may also need to set up logic for refreshing your token when it expires. With FSD, there are several places where you can store the token, as well as several ways to make it available for the rest of the app. + +### In Shared + +This approach plays well with an API client defined in `shared/api` because the token is freely available for other request functions that require authentication to succeed. You can make the API client hold state, either with a reactive store or simply a module-level variable, and update that state in your `login()`/`logout()` functions. + +Automatic token refresh can be implemented as a middleware in the API client — something that can execute every time you make any request. It can work like this: + +- Authenticate and store the access token as well as the refresh token +- Make any request that requires authentication +- If the request fails with a status code that indicates token expiration, and there is a token in the store, make a refresh request, store the new tokens, and retry the original request + +One of the drawbacks of this approach is that the logic of managing and refreshing the token doesn't have a dedicated place. This can be fine for some apps or teams, but if the token management logic is more complex, it may be preferable to separate responsibilities of making requests and managing tokens. You can do that by keeping your requests and API client in `shared/api`, but the token store and management logic in `shared/auth`. + +Another drawback of this approach is that if your backend returns an object of your current user's information along with the token, you have to store that somewhere or discard that information and request it again from an endpoint like `/me` or `/users/current`. + +### In Entities + +It's common for FSD projects to have an entity for a user and/or an entity for the current user. It can even be the same entity for both. + +:::note + +The **current user** is also sometimes called "viewer" or "me". This is to distinguish the single authenticated user, with permissions and private information, from a list of all users with publicly accessible information. + +::: + +To store the token in the User entity, create a reactive store in the `model` segment. That store can contain both the token and the user object. + +Since the API client is usually defined in `shared/api` or spreaded across the entities, the main challenge to this approach is making the token available to other requests that need it without breaking [the import rule on layers][import-rule-on-layers]: + +> A module (file) in a slice can only import other slices when they are located on layers strictly below. + +There are several solutions to this challenge: + +1. **Pass the token manually every time you make a request** + This is the simplest solution, but it quickly becomes cumbersome, and if you don't have type safety, it's easy to forget. It's also not compatible with middlewares pattern for the API client in Shared. +1. **Expose the token to the entire app with a context or a global store like `localStorage`** + The key to retrieve the token will be kept in `shared/api` so that the API client can access it. The reactive store of the token will be exported from the User entity, and the context provider (if needed) will be set up on the App layer. This gives more freedom for designing the API client, however, this creates an implicit dependency on higher layers to provide context. When following this approach, consider providing helpful error messages if the context or `localStorage` are not set up correctly. +1. **Inject the token into the API client every time it changes** + If your store is reactive, you can create a subscription that will update the API client's token store every time the store in the entity changes. This is similar to the previous solution in that they both create an implicit dependency on higher layers, but this one is more imperative ("push"), while the previous one is more declarative ("pull"). + +Once you overcome the challenge of exposing the token that is stored in the entity's model, you can encode more business logic related to token management. For example, the `model` segment can contain logic to invalidate the token after a certain period of time, or to refresh the token when it expires. To actually make requests to the backend, use the `api` segment of the User entity or `shared/api`. + +### In Pages/Widgets (not recommended) + +It is discouraged to store app-wide state like an access token in pages or widgets. Avoid placing your token store in the `model` segment of the login page, instead choose from the first two solutions, Shared or Entities. + +## Logout and token invalidation + +Usually, apps don't have an entire page for logging out, but the logout functionality is still very important. It consists of an authenticated request to the backend and an update to the token store. + +If you store all your requests in `shared/api`, keep the logout request function there, close to the login function. Otherwise, consider keeping the logout request function next to the button that triggers it. For example, if you have a header widget that appears on every page and contains the logout link, put that request in the `api` segment of that widget. + +The update to the token store will have to be triggered from the place of the logout button, like a header widget. You can combine the request and the store update in the `model` segment of that widget. + +### Automatic logout + +Don't forget to build failsafes for when a request to log out fails, or a request to refresh a login token fails. In both of these cases, you should clear the token store. If you keep your token in Entities, this code can be placed in the `model` segment as it is pure business logic. If you keep your token in Shared, placing this logic in `shared/api` might bloat the segment and dilute its purpose. If you're noticing that your API segment contains two several unrelated things, consider splitting out the token management logic into another segment, for example, `shared/auth`. + +[tutorial-authentication]: /docs/get-started/tutorial#authentication +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-remix]: https://remix.run +[ext-zod]: https://zod.dev + diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/auth.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/auth.mdx deleted file mode 100644 index 1e6441d8ec..0000000000 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/auth.mdx +++ /dev/null @@ -1,404 +0,0 @@ ---- -sidebar_position: 1 ---- - -import WIP from '@site/src/shared/ui/wip/tmpl.mdx' - - -# Auth - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -Every application has business logic related **with the current authorized user.** - -> Usually such an entity is called `Viewer` / `Principle` / `Session` - but within the framework of the article, we will focus on `viewer`, but it all depends on your project and team - -Also, this is one of the illustrative examples when a business entity generates business features, then pages, and even business processes - -Let's look at them in more detail below with examples - -:::note - -1. The names of the directories inside the segments (ui, model) may differ from project to project - - *The methodology does not regulate this level of nesting in any way yet* - -2. It should also be understood that the examples below are abstract and synthetic, and are formed to demonstrate only the concepts of the methodology - - *FSD does not regulate the best practices of a particular data-fetcher or state-manager* - - -::: - -## Entities - -**The business entity of the user** - -- Represents the most atomic abstraction for design -- Here the authorization context is formed, which is then usually relied on by all the overlying layers of the application - -:::info - -It should be understood that often there is a public "external" user (`entities/user`) in the application, and there is an authorized "internal" user (`entities/viewer`) - -*Do not forget to take this difference into account when designing architecture and logic* - -::: - -### Examples - -```sh -# Segments can be both files and directories -| -├── entities/viewer # Layer: Business entities -| | # Slice: Current user -| ├── ui/ # Segment: UI-logic (components) -| ├── lib/ # Segment: Infrastructure-logic (helpers/utils) -| ├── model/ # Segment: Business Logic -| └── index.ts # [Public API Declaration] -| ... -``` - -- `entities/viewer` - the entity of the current user *(Session / Principle)* -- `entities/user` - the essence of public user *(not necessarily associated with the current)* - - *Depending on the complexity of your application - you can use the `user` for naming the current user* - - *But it can cause serious problems when/if I have to separate the logic of a normal user and current, who came into the system* - -### `index.ts` - -The usual [Public API of the module][refs-public-api] - -*Largely repeats the API declaration for the layers described below* - -```ts title=entities/user/ui/index.ts -export { ViewerCard } from "./card"; -export { ViewerAvatar } from "./avatar"; -... -``` - - - - -In redux, the [redux-ducks](https://github.com/erikras/ducks-modular-redux) approach is generally accepted when its units (selectors/actions/...) they lie side by side and are clearly decomposed - -But explicit decomposition is not required - -```ts title=entities/user/model/index.ts -export * as selectors from "./selectors"; -export * as events from "./events"; -export * as stores from "./stores"; -... -``` - - - -The effector model will most often consist of a single file - because it is customary to store all units side by side there - -If the units in the model can be schematically divided into several submodels, then [you can explicitly do this](https://github.com/feature-sliced/examples/pull/1#discussion_r654841332) denote in the Public API - -```ts title=entities/user/model/index.ts -export * as submodel1 from "./submodel1" -export * as submodel2 from "./submodel2" -... -``` - - - -```ts title=entities/user/index.ts -export * from "./ui" -export * as viewerModel from "./model"; -``` - -### `ui` - -It may contain components that are not related to a specific page/feature, but directly to the user's entity - -```tsx title=entities/user/ui/card/index.tsx -import { Card } from "shared/ui/card"; - -// It is considered a good practice not to link ui components from entitites directly to the model -// So that it can be used not only for the current model, -// But also for externally received props - -export type UserCardProps = { - data: User; - className?: string; - // And other card-props -}; - -export const UserCard = ({ data, ... }: UserCardProps) => { - return ( - - ) -} -``` - -### `model` - -At this level, the entity of the current user is usually created, with the re-export of hooks/contracts/selectors for use by the overlying layers - - - - -```ts -// entities/viewer/model/selectors.ts -export const useViewer = () => { - return useSelector((store) => store.entities.userSlice); -} -export const useAuth = () => { - const viewer = useViewer(); - return !!viewer -} -// entities/viewer/model/store.ts -export const userSlice = createSlice(...) -``` - - - -```ts -// entities/viewer/model/index.ts -export const $viewer = createStore(...); -export const $isAuth = $viewer.map((viewer) => !!viewer); -// **/**/ui.tsx -const viewer = useStore($viewer); -``` - - - -Also, other logic can be implemented here - -- `updateUserDetails` -- `logoutUser` -- ... - -## Features - -**Features tied to the current user** - -- Uses business entities (often `entities/viewer`) and shared resources in the implementation -- Features may not be directly related to the viewer, but they can use its context when implementing logic - -### Examples - -```sh -# Segments can be both files and directories -| -├── features/auth # Layer: Business features -| | # Slice Group: The structural group "User authorization" -| ├── by-phone/ # Slice: Feature "Authorization by phone" -| | ├── ui/ # Segment: UI-logic (components) -| | ├── lib/ # Segment: Infrastructure-logic (helpers/utils) -| | ├── model/ # Segment: Business Logic -| | └── index.ts # [Public API Declaration] -| | -| ├── by-oauth/ # Slice: Feature "Authorization by an external resource" -| ... -``` - -- `features/auth/{by-phone, by-oauth, logout ...}` - **structural** group of authorization features *(by phone, by external resource, logout,...)* -- `features/wallet/{add-funds,...}` - **structural** group of features for working with the user's internal account *(adding funds to the account,...)* - -### `ui` - -- Authorization by an external resource - -```tsx title=features/auth/by-oauth/ui.tsx -import { viewerModel } from "entities/viewer"; - -export const AuthByOAuth = () => { - return ( - viewerModel.setUser(user)} - /> - ) -} -``` - -- Using the user's context in features - -```tsx title=features/wallet/ui.tsx -import { viewerModel } from "entities/viewer"; - -export const Wallet = () => { - const viewer = viewerModel.useViewer(); - const { moneyCount } = viewer; - - ... -} -``` - -- Using the viewer components - -```tsx title=features/header/ui.tsx -import { ViewerAvatar } from "entities/viewer"; -... -export const Header = () => { - ... - return ( - - ... - - - ) -} -``` - -## Pages - -**Pages related to the current user in one way or another** - -- They can both directly affect the functionality of the viewer -- And use it indirectly (including its context / features) - -### Examples - -```sh -# Segments can be both files and directories -| -├── pages/viewer # Layer: Application pages -| | # Slice Group: The "Current User" structural group -| ├── profile/ # Slice: The viewer profile page -| | ├── ui.tsx # Segment: UI-logic (components) -| | ├── lib.ts # Segment: Infrastructure-logic (helpers/utils) -| | ├── model.ts # Segment: Business Logic -| | └── index.ts # [Public API Declaration] -| | -| ├── settings/ # Slice: The viewer account settings page -| ... -``` - -- `pages/viewer/profile` - the user's LC page -- `pages/viewer/settings` - user account settings page -- `pages/user` - the user's page (not necessarily the current one) -- `pages/auth/{sign-in, sign-up, reset}` **structural** group of authorization pages *(login / registration / password recovery)* - -### `ui` - -- Using the viewer components and *viewer-based* features on the pages - -```tsx title=pages/user/ui.tsx -import { Wallet } from "features/wallet"; -import { ViewerCard } from "entities/viewer"; -... -export const UserPage = () => { - ... - return ( - -
} - /> - ... - - - ) -} -``` - -- Using the viewer model - -```tsx title=pages/some/ui.tsx -import { viewerModel } from "entities/viewer"; -... -export const SomePage = () => { - ... - return ( - - ... - viewerModel.saveChanges(payload)} /> - - ) -} -``` - -## Processes - -**Business processes affecting the current user** - -- Affects user cases that permeate the pages of the system -- **The process layer is optional**, and is usually used *only when the logic grows in pages* and you need *separate context management* on several pages at once - -### Examples - -```sh -# Segments can be both files and directories -| -├── processes # Layer: Business processes -| ├── auth/ # Slice: User authorization process -| | ├── lib.ts # Segment: Infrastructure-logic (helpers/utils) -| | ├── model.ts # Segment: Business Logic -| | └── index.ts # [Public API Declaration] -| | -| ├── quick-tour/ # Slice: The process of onboarding a new user -| ... -``` - -- `processes/auth` - the business process of user authorization -- `processes/quick-tour` - a business process for familiarizing the user with the system *(~ UserOnboard)* - -## App - -**Initialization of user account data** - -- There is usually a check on whether the user **was already logged in** before he entered the service - - **If yes** - the provider replenishes the user's context for further use in the system - - **If not** - the authorization process is started or the context of the viewer is changed so that the authorization page takes the necessary actions - -### Examples - -```sh -# The `app` structure is unique for each project and is not regulated by the methodology -| -├── app/providers # Layer: Initializing the application (HOCs providers) -| ├── withAuth.tsx # HOC: Initializing the authorization context -| | ... # -| ... -``` - -- `app/providers/withAuth` - HOC for user authorization - - Used **only at the top level, as a provider** with logic initialization, to which only _**app**-layer_ -- **Not to be confused with the `useViewer` hook**, which is accessed by all other layers *(processes / pages / features)* - -## Conclusions - -As we can see in the examples above - **all business logic begins to be built from a single entity, and can spread to the very top layer.** - -Therefore, you need to be able to **correctly allocate the scope of the module's influence**, and depending on this, choose the appropriate layer for the location of the logic. - -*Thus, we will get the most supported, readable and reused code.* - -## FAQ - - - -### How to pass a token - -> https://t.me/feature_sliced/4618 - -### Login - -> https://t.me/feature_sliced/3227 - -### Logout - -> https://t.me/feature_sliced/3227 - -## See also - -- [Discussion "The applicability of Feature-Sliced Design in combat"](https://github.com/feature-sliced/documentation/discussions/65) (*there are also examples with viewer* inside) -- [Question about the user's profile and identity (community-chat)](https://t.me/feature_sliced/342) -- [Explanation about UIKIT#Card and User#Card (community-chat)](https://t.me/feature_sliced/552) - -[refs-public-api]: /docs/reference/public-api diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/autocompleted.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/autocompleted.mdx index b5eeb72d0a..8639d1b09e 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/autocompleted.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/autocompleted.mdx @@ -1,6 +1,7 @@ --- sidebar_position: 5 sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' @@ -14,4 +15,4 @@ import WIP from '@site/src/shared/ui/wip/tmpl.mdx' > About decomposition by layers ## See also -- [(Discussion) About the application of the methodology for the selection with loaded dictionaries](https://github.com/feature-sliced/documentation/discussions/65#discussioncomment-480807) \ No newline at end of file +- [(Discussion) About the application of the methodology for the selection with loaded dictionaries](https://github.com/feature-sliced/documentation/discussions/65#discussioncomment-480807) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/browser-api.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/browser-api.mdx index 52128b75bb..957c651620 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/browser-api.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/browser-api.mdx @@ -1,5 +1,6 @@ --- sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/cms.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/cms.mdx index 8db23a3594..82b7f031ad 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/cms.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/cms.mdx @@ -1,5 +1,6 @@ --- sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/feedback.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/feedback.mdx index ad2431fd94..8bab4fc7f6 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/feedback.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/feedback.mdx @@ -1,5 +1,6 @@ --- sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' @@ -8,4 +9,4 @@ import WIP from '@site/src/shared/ui/wip/tmpl.mdx' -> Errors, Alerts, Notifications, ... \ No newline at end of file +> Errors, Alerts, Notifications, ... diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/i18n.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/i18n.mdx index 9b0f7126af..d2b543cee6 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/i18n.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/i18n.mdx @@ -1,6 +1,7 @@ --- sidebar_position: 6 sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/index.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/index.mdx index 3b07bcfc87..d41ddf713f 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/index.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/index.mdx @@ -14,22 +14,23 @@ import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" import { UserSwitchOutlined, LayoutOutlined, FontSizeOutlined } from "@ant-design/icons"; - + diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/metric.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/metric.mdx index 4cca5c5412..5587db6d1b 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/metric.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/metric.mdx @@ -1,5 +1,6 @@ --- sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/monorepo.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/monorepo.mdx index 9c5b93d911..a9c7bb13fc 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/monorepo.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/monorepo.mdx @@ -1,6 +1,7 @@ --- sidebar_position: 9 sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md new file mode 100644 index 0000000000..e155d430d4 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md @@ -0,0 +1,102 @@ +--- +sidebar_position: 3 +--- + +# Page layouts + +This guide examines the abstraction of a _page layout_ — when several pages share the same overall structure, and differ only in the main content. + +:::info + +Is your question not covered by this guide? Post your question by leaving feedback on this article (blue button on the right) and we will consider expanding this guide! + +::: + +## Simple layout + +The simplest layout can be seen on this page. It has a header with site navigation, two sidebars, and a footer with external links. There is no complicated business logic, and the only dynamic parts are sidebars and the switchers on the right side of the header. Such a layout can be placed entirely in `shared/ui` or in `app/layouts`, with props filling in the content for the sidebars: + +```tsx title="shared/ui/layout/Layout.tsx" +import { Link, Outlet } from "react-router-dom"; +import { useThemeSwitcher } from "./useThemeSwitcher"; + +export function Layout({ siblingPages, headings }) { + const [theme, toggleTheme] = useThemeSwitcher(); + + return ( +
+
+ + +
+
+ + {/* This is where the main content goes */} + +
+
+
    +
  • GitHub
  • +
  • Twitter
  • +
+
+
+ ); +} +``` + +```ts title="shared/ui/layout/useThemeSwitcher.ts" +export function useThemeSwitcher() { + const [theme, setTheme] = useState("light"); + + function toggleTheme() { + setTheme(theme === "light" ? "dark" : "light"); + } + + useEffect(() => { + document.body.classList.remove("light", "dark"); + document.body.classList.add(theme); + }, [theme]); + + return [theme, toggleTheme] as const; +} +``` + +The code of sidebars is left as an exercise for the reader 😉. + +## Using widgets in the layout + +Sometimes you want to include certain business logic in the layout, especially if you're using deeply nested routes with a router like [React Router][ext-react-router]. Then you can't store the layout in Shared or in Widgets due to [the import rule on layers][import-rule-on-layers]: + +> A module in a slice can only import other slices when they are located on layers strictly below. + +Before we discuss solutions, we need to discuss if it's even a problem in the first place. Do you _really need_ that layout, and if so, does it _really need_ to be a Widget? If the block of business logic in question is reused on 2-3 pages, and the layout is simply a small wrapper for that widget, consider one of these two options: + +1. **Write the layout inline on the App layer, where you configure the routing** + This is great for routers that support nesting, because you can group certain routes and apply the layout only to them. + +2. **Just copy-paste it** + The urge to abstract code is often very overrated. It is especially the case for layouts, which rarely change. At some point, if one of these pages will need to change, you can simply do the change without needlessly affecting other pages. If you're worried that someone might forget to update the other pages, you can always leave a comment that describes the relationship between the pages. + +If none of the above are applicable, there are two solutions to include a widget in the layout: + +1. **Use render props or slots** + Most frameworks allow you to pass a piece of UI externally. In React, it's called [render props][ext-render-props], in Vue it's called [slots][ext-vue-slots]. +2. **Move the layout to the App layer** + You can also store your layout on the App layer, for example, in `app/layouts`, and compose any widgets you want. + +## Further reading + +- There's an example of how to build a layout with authentication with React and Remix (equivalent to React Router) in the [tutorial][tutorial]. + +[tutorial]: /docs/get-started/tutorial +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-react-router]: https://reactrouter.com/ +[ext-render-props]: https://www.patterns.dev/react/render-props-pattern/ +[ext-vue-slots]: https://vuejs.org/guide/components/slots diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/page-layout.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/page-layout.mdx deleted file mode 100644 index 994141ea52..0000000000 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/page-layout.mdx +++ /dev/null @@ -1,28 +0,0 @@ ---- -sidebar_position: 2 -sidebar_class_name: sidebar-item--wip ---- - -import WIP from '@site/src/shared/ui/wip/tmpl.mdx' - -# PageLayout - - - -## Header - -> https://t.me/feature_sliced/2978 - -## Sidebar - -> - https://t.me/feature_sliced/4046 -> - https://t.me/feature_sliced/4090 - -> https://t.me/feature_sliced/3475 - -> https://t.me/feature_sliced/3425 - -## See also - -- [(DiscussionTalk) About page layout and FSD](https://youtu.be/b_nBvHWqxP8?t=4452) - diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/platforms.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/platforms.mdx index 532493817e..2a5212f2a1 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/platforms.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/platforms.mdx @@ -1,5 +1,6 @@ --- sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/ssr.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/ssr.mdx index 45453bc328..43eb82eabd 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/ssr.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/ssr.mdx @@ -1,5 +1,6 @@ --- sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/theme.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/theme.mdx index 044cdbb873..cf590be8e5 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/theme.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/theme.mdx @@ -1,6 +1,7 @@ --- sidebar_position: 4 sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/types.md b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/types.md new file mode 100644 index 0000000000..78e3748c6b --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/types.md @@ -0,0 +1,440 @@ +--- +sidebar_position: 2 +--- + +# Types + +This guide concerns data types from typed languages like TypeScript and describes where they fit within FSD. + +:::info + +Is your question not covered by this guide? Post your question by leaving feedback on this article (blue button on the right) and we will consider expanding this guide! + +::: + +## Utility types + +Utility types are types that don't have much meaning on their own and are usually used with other types. For example: + +
+ +```ts +type ArrayValues = T[number]; +``` + +
+ Source: https://github.com/sindresorhus/type-fest/blob/main/source/array-values.d.ts +
+ +
+ +To make utility types available across your project, either install a library like [`type-fest`][ext-type-fest], or create your own library in `shared/lib`. Make sure to clearly indicate what new types _should_ be added to this library, and what types _don't belong_ there. For example, call it `shared/lib/utility-types` and add a README inside that describes what is a utility type in your team. + +Don't overestimate the potential reusability of a utility type. Just because it can be reused, doesn't mean it will be, and as such, not every utility type needs to be in Shared. Some utility types are fine right next to where they are needed: + +- 📂 pages + - 📂 home + - 📂 api + - 📄 ArrayValues.ts (utility type) + - 📄 getMemoryUsageMetrics.ts (the code that uses the utility type) + +:::warning + +Resist the temptation to create a `shared/types` folder, or to add a `types` segment to your slices. The category "types" is similar to the category "components" or "hooks" in that it describes what the contents are, not what they are for. Segments should describe the purpose of the code, not the essence. + +::: + +## Business entities and their cross-references + +Among the most important types in an app are the types of business entities, i.e. the real-world things that your app works with. For example, in a music streaming app, you might have business entities _Song_, _Album_, etc. + +Business entities often come from the backend, so the first step is to type the backend responses. It's convenient to have a function to make a request to every endpoint, and to type the response of this function. For extra type safety, you may want to run the response through a schema validation library like [Zod][ext-zod]. + +For example, if you keep all your requests in Shared, you could do it like this: + +```ts title="shared/api/songs.ts" +import type { Artist } from "./artists"; + +interface Song { + id: number; + title: string; + artists: Array; +} + +export function listSongs() { + return fetch('/api/songs').then((res) => res.json() as Promise>); +} +``` + +You might notice that the `Song` type references a different entity, `Artist`. This is a benefit of storing your requests in Shared — real-world types are often intertwined. If we kept this function in `entities/song/api`, we wouldn't be able to simply import `Artist` from `entities/artist`, because FSD restricts cross-imports between slices with [the import rule on layers][import-rule-on-layers]: + +> A module in a slice can only import other slices when they are located on layers strictly below. + +There are two ways to deal with this issue: + +1. **Parametrize your types** + You can make your types accept type arguments as slots for connections with other entities, and even impose constraints on those slots. For example: + + ```ts title="entities/song/model/song.ts" + interface Song { + id: number; + title: string; + artists: Array; + } + ``` + + This works better for some types than others. A simple type like `Cart = { items: Array }` can easily be made to work with any type of product. More connected types, like `Country` and `City`, may not be as easy to separate. + +2. **Cross-import (but do it right)** + To make cross-imports between entities in FSD, you can use a special public API specifically for each slice that will be cross-importing. For example, if we have entities `song`, `artist`, and `playlist`, and the latter two need to reference `song`, we can make two special public APIs for both of them in the `song` entity with the `@x` notation: + + - 📂 entities + - 📂 song + - 📂 @x + - 📄 artist.ts (a public API for the `artist` entity to import from) + - 📄 playlist.ts (a public API for the `playlist` entity to import from) + - 📄 index.ts (regular public API) + + The contents of a file `📄 entities/song/@x/artist.ts` are similar to `📄 entities/song/index.ts`: + + ```ts title="entities/song/@x/artist.ts" + export type { Song } from "../model/song.ts"; + ``` + + Then the `📄 entities/artist/model/artist.ts` can import `Song` like this: + + ```ts title="entities/artist/model/artist.ts" + import type { Song } from "entities/song/@x/artist"; + + export interface Artist { + name: string; + songs: Array; + } + ``` + + By making explicit connections between entities, we stay on top of inter-dependencies and maintain a decent level of domain separation. + +## Data transfer objects and mappers {#data-transfer-objects-and-mappers} + +Data transfer objects, or DTOs, is a term that describes the shape of data that comes from the backend. Sometimes, the DTO is fine to use as is, but sometimes it's inconvenient for the frontend. That's where mappers come in — they transform a DTO into a more convenient shape. + +### Where to put DTOs + +If you have backend types in a separate package (for example, if you share code between the frontend and the backend), then just import your DTOs from there and you're done! If you don't share code between the backend and frontend, then you need to keep DTOs somewhere in your frontend codebase, and we will explore this case below. + +If you have your request functions in `shared/api`, that's where the DTOs should be, right next to the function that uses them: + +```ts title="shared/api/songs.ts" +import type { ArtistDTO } from "./artists"; + +interface SongDTO { + id: number; + title: string; + artist_ids: Array; +} + +export function listSongs() { + return fetch('/api/songs').then((res) => res.json() as Promise>); +} +``` + +As mentioned in the previous section, storing your requests and DTOs in Shared comes with the benefit of being able to reference other DTOs. + +### Where to put mappers + +Mappers are functions that accept a DTO for transformation, and as such, they should be located near the definition of the DTO. In practice this means that if your requests and DTOs are defined in `shared/api`, then the mappers should go there as well: + +```ts title="shared/api/songs.ts" +import type { ArtistDTO } from "./artists"; + +interface SongDTO { + id: number; + title: string; + disc_no: number; + artist_ids: Array; +} + +interface Song { + id: string; + title: string; + /** The full title of the song, including the disc number. */ + fullTitle: string; + artistIds: Array; +} + +function adaptSongDTO(dto: SongDTO): Song { + return { + id: String(dto.id), + title: dto.title, + fullTitle: `${dto.disc_no} / ${dto.title}`, + artistIds: dto.artist_ids.map(String), + }; +} + +export function listSongs() { + return fetch('/api/songs').then(async (res) => (await res.json()).map(adaptSongDTO)); +} +``` + +If your requests and stores are defined in entity slices, then all this code would go there, keeping in mind the limitations of cross-imports between slices: + +```ts title="entities/song/api/dto.ts" +import type { ArtistDTO } from "entities/artist/@x/song"; + +export interface SongDTO { + id: number; + title: string; + disc_no: number; + artist_ids: Array; +} +``` + +```ts title="entities/song/api/mapper.ts" +import type { SongDTO } from "./dto"; + +export interface Song { + id: string; + title: string; + /** The full title of the song, including the disc number. */ + fullTitle: string; + artistIds: Array; +} + +export function adaptSongDTO(dto: SongDTO): Song { + return { + id: String(dto.id), + title: dto.title, + fullTitle: `${dto.disc_no} / ${dto.title}`, + artistIds: dto.artist_ids.map(String), + }; +} +``` + +```ts title="entities/song/api/listSongs.ts" +import { adaptSongDTO } from "./mapper"; + +export function listSongs() { + return fetch('/api/songs').then(async (res) => (await res.json()).map(adaptSongDTO)); +} +``` + +```ts title="entities/song/model/songs.ts" +import { createSlice, createEntityAdapter } from "@reduxjs/toolkit"; + +import { listSongs } from "../api/listSongs"; + +export const fetchSongs = createAsyncThunk('songs/fetchSongs', listSongs); + +const songAdapter = createEntityAdapter(); +const songsSlice = createSlice({ + name: "songs", + initialState: songAdapter.getInitialState(), + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchSongs.fulfilled, (state, action) => { + songAdapter.upsertMany(state, action.payload); + }) + }, +}); +``` + +### How to deal with nested DTOs + +The most problematic part is when a response from the backend contains several entities. For example, if the song included not just the authors' IDs, but the entire author objects. In this case, it is impossible for entities not to know about each other (unless we want to discard the data or have a firm conversation with the backend team). Instead of coming up with solutions for indirect connections between slices (such as a common middleware that would dispatch actions to other slices), prefer explicit cross-imports with the `@x` notation. Here is how we can implement it with Redux Toolkit: + +```ts title="entities/song/model/songs.ts" +import { + createSlice, + createEntityAdapter, + createAsyncThunk, + createSelector, +} from '@reduxjs/toolkit' +import { normalize, schema } from 'normalizr' + +import { getSong } from "../api/getSong"; + +// Define normalizr entity schemas +export const artistEntity = new schema.Entity('artists') +export const songEntity = new schema.Entity('songs', { + artists: [artistEntity], +}) + +const songAdapter = createEntityAdapter() + +export const fetchSong = createAsyncThunk( + 'songs/fetchSong', + async (id: string) => { + const data = await getSong(id) + // Normalize the data so reducers can load a predictable payload, like: + // `action.payload = { songs: {}, artists: {} }` + const normalized = normalize(data, songEntity) + return normalized.entities + } +) + +export const slice = createSlice({ + name: 'songs', + initialState: songAdapter.getInitialState(), + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchSong.fulfilled, (state, action) => { + songAdapter.upsertMany(state, action.payload.songs) + }) + }, +}) + +const reducer = slice.reducer +export default reducer +``` + +```ts title="entities/song/@x/artist.ts" +export { fetchSong } from "../model/songs"; +``` + +```ts title="entities/artist/model/artists.ts" +import { createSlice, createEntityAdapter } from '@reduxjs/toolkit' + +import { fetchSong } from 'entities/song/@x/artist' + +const artistAdapter = createEntityAdapter() + +export const slice = createSlice({ + name: 'users', + initialState: artistAdapter.getInitialState(), + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchSong.fulfilled, (state, action) => { + // And handle the same fetch result by inserting the artists here + artistAdapter.upsertMany(state, action.payload.artists) + }) + }, +}) + +const reducer = slice.reducer +export default reducer +``` + +This slightly limits the benefits of slice isolation, but it accurately represents a connection between these two entities that we have no control over. If these entities are to ever be refactored, they have to be refactored together. + +## Global types and Redux + +Global types are types that will be used across the whole application. There are two kinds of global types, based on what they need to know about: +1. Generic types that don't have any application specifics +2. Types that need to know about the whole application + +The first case is simple to resolve — place your types in Shared, in an appropriate segment. For example, if you have an interface for a global variable for analytics, you can put it in `shared/analytics`. + +:::warning + +Avoid creating the `shared/types` folder. It groups unrelated things based only on the property of "being a type", and that property is usually not useful when searching for code in a project. + +::: + +The second case is commonly encountered in projects with Redux without RTK. Your final store type is only available once you add all the reducers together, but this store type needs to be available to selectors that you use across the app. For example, here's your typical store definition: + +```ts title="app/store/index.ts" +import { combineReducers, rootReducer } from "redux"; + +import { songReducer } from "entities/song"; +import { artistReducer } from "entities/artist"; + +const rootReducer = combineReducers(songReducer, artistReducer); + +const store = createStore(rootReducer); + +type RootState = ReturnType; +type AppDispatch = typeof store.dispatch; +``` + +It would be nice to have typed Redux hooks `useAppDispatch` and `useAppSelector` in `shared/store`, but they cannot import `RootState` and `AppDispatch` from the App layer due to the [import rule on layers][import-rule-on-layers]: + +> A module in a slice can only import other slices when they are located on layers strictly below. + +The recommended solution in this case is to create an implicit dependency between layers Shared and App. These two types, `RootState` and `AppDispatch` are unlikely to change, and they will be familiar to Redux developers, so we don't have to worry about them as much. + +In TypeScript, you can do it by declaring the types as global like this: + +```ts title="app/store/index.ts" +/* same content as in the code block before… */ + +declare type RootState = ReturnType; +declare type AppDispatch = typeof store.dispatch; +``` + +```ts title="shared/store/index.ts" +import { useDispatch, useSelector, type TypedUseSelectorHook } from "react-redux"; + +export const useAppDispatch = useDispatch.withTypes() +export const useAppSelector: TypedUseSelectorHook = useSelector; +``` + +## Enums + +The general rule with enums is that they should be defined **as close to the usage locations as possible**. When an enum represents values specific to a single feature, it should be defined in that same feature. + +The choice of segment should be dictated by usage locations as well. If your enum contains, for example, positions of a toast on the screen, it should be placed in the `ui` segment. If it represents the loading state of a backend operation, it should be placed in the `api` segment. + +Some enums are truly common across the whole project, like general backend response statuses or design system tokens. In this case, you can place them in Shared, and choose the segment based on what the enum represents (`api` for response statuses, `ui` for design tokens, etc.). + +## Type validation schemas and Zod + +If you want to validate that your data conforms to a certain shape or constraints, you can define a validation schema. In TypeScript, a popular library for this job is [Zod][ext-zod]. Validation schemas should also be colocated with the code that uses them, as much as possible. + +Validation schemas are similar to mappers (as discussed in the [Data transfer objects and mappers](#data-transfer-objects-and-mappers) section) in the sense that they take a data transfer object and parse it, producing an error if the parsing fails. + +One of the most common cases of validation is for the data that comes from the backend. Typically, you want to fail the request when the data doesn't match the schema, so it makes sense to put the schema in the same place as the request function, which is usually the `api` segment. + +If your data comes through user input, like a form, the validation should happen as the data is being entered. You can place your schema in the `ui` segment, next to the form component, or in the `model` segment, if the `ui` segment is too crowded. + +## Typings of component props and context + +In general, it's best to keep the props or context interface in the same file as the component or context that uses them. If you have a framework with single-file components, like Vue or Svelte, and you can't define the props interface in the same file, or you want to share that interface between several components, create a separate file in the same folder, typically, the `ui` segment. + +Here's an example with JSX (React or Solid): + +```ts title="pages/home/ui/RecentActions.tsx" +interface RecentActionsProps { + actions: Array<{ id: string; text: string }>; +} + +export function RecentActions({ actions }: RecentActionsProps) { + /* … */ +} +``` + +And here's an example with the interface stored in a separate file for Vue: + +```ts title="pages/home/ui/RecentActionsProps.ts" +export interface RecentActionsProps { + actions: Array<{ id: string; text: string }>; +} +``` + +```html title="pages/home/ui/RecentActions.vue" + +``` + +## Ambient declaration files (`*.d.ts`) + +Some packages, for example, [Vite][ext-vite] or [ts-reset][ext-ts-reset], require ambient declaration files to work across your app. Usually, they aren't large or complicated, so they often don't require any architecting, it's fine to just throw them in the `src/` folder. To keep the `src` more organized, you can keep them on the App layer, in `app/ambient/`. + +Other packages simply don't have typings, and you might want to declare them as untyped or even write your own typings for them. A good place for those typings would be `shared/lib`, in a folder like `shared/lib/untyped-packages`. Create a `%LIBRARY_NAME%.d.ts` file there and declare the types you need: + +```ts title="shared/lib/untyped-packages/use-react-screenshot.d.ts" +// This library doesn't have typings, and we didn't want to bother writing our own. +declare module "use-react-screenshot"; +``` + +## Auto-generation of types + +It's common to generate types from external sources, for example, generating backend types from an OpenAPI schema. In this case, create a dedicated place in your codebase for these types, like `shared/api/openapi`. Ideally, you should also include a README in that folder that describes what these files are, how to regenerate them, etc. + +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-type-fest]: https://github.com/sindresorhus/type-fest +[ext-zod]: https://zod.dev +[ext-vite]: https://vitejs.dev +[ext-ts-reset]: https://www.totaltypescript.com/ts-reset diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/types.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/types.mdx deleted file mode 100644 index a4eecdbe23..0000000000 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/types.mdx +++ /dev/null @@ -1,30 +0,0 @@ ---- -sidebar_position: 3 -sidebar_class_name: sidebar-item--wip ---- - -import WIP from '@site/src/shared/ui/wip/tmpl.mdx' - -# Types - - - -> **The types are different** -> -> This may be as a utility type (`Maybe`) and subject area (Tls Contract, Camera, ...) -> -> 1) what refers to The subject area is usually better to put in the shared/api (especially when there is a code generation in one place) -> (for entities such spraying PTS released) -> -> 2) Common service types that you can segelerite - same-react-app-env.d.ts -> -> 3) And if you have service types and you need to import right everywhere, then it's not critical especially, you can also put it in shared, you can also put it next to react-app.env.dts -> -> https://t.me/feature_sliced/3879 -> -> 4) It makes sense to store normalized types for entities in entities -> -> https://t.me/feature_sliced/4513 - - -> https://t.me/feature_sliced/3877 diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx index aa3a2b88dd..855f00eb72 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx @@ -1,6 +1,7 @@ --- sidebar_position: 8 sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/index.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/index.mdx index 1be46e72c3..319ebbf93f 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/index.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/index.mdx @@ -21,14 +21,14 @@ import { ToolOutlined, ImportOutlined, BugOutlined, FunctionOutlined } from "@an description="Practical examples on the application of the methodology" to="/docs/guides/examples" Icon={ToolOutlined} - tags={['Auth', 'Types', 'Page layout']} + tags={['Authentication', 'Types', 'Page layout']} /> + 📁 src +
    +
  • +
    + 📁 actions +
      +
    • 📁 product
    • +
    • 📁 order
    • +
    +
    +
  • +
  • 📁 api
  • +
  • 📁 components
  • +
  • 📁 containers
  • +
  • 📁 constants
  • +
  • 📁 i18n
  • +
  • 📁 modules
  • +
  • 📁 helpers
  • +
  • +
    + 📁 routes +
      +
    • 📁 products.jsx
    • +
    • 📄 products.[id].jsx
    • +
    +
    +
  • +
  • 📁 utils
  • +
  • 📁 reducers
  • +
  • 📁 selectors
  • +
  • 📁 styles
  • +
  • 📄 App.jsx
  • +
  • 📄 index.js
  • +
+ + +## Before you start {#before-you-start} + +The most important question to ask your team when considering to switch to Feature-Sliced Design is — _do you really need it?_ We love Feature-Sliced Design, but even we recognize that some projects are perfectly fine without it. + +Here are some reasons to consider making the switch: + +1. New team members are complaining that it's hard to get to a productive level +2. Making modifications to one part of the code **often** causes another unrelated part to break +3. Adding new functionality is difficult due to the sheer amount of things you need to think about + +**Avoid switching to FSD against the will of your teammates**, even if you are the lead. +First, convince your teammates that the benefits outweigh the cost of migration and the cost of learning a new architecture instead of the established one. + +Also keep in mind that any kind of architectural changes are not immediately observable to the management. Make sure they are on board with the switch before starting and explain to them why it might benefit the project. + +:::tip + +If you need help convincing the project manager that FSD is beneficial, consider some of these points: +1. Migration to FSD can happen incrementally, so it will not halt the development of new features +2. A good architecture can significantly decrease the time that a new developer needs to get productive +3. FSD is a documented architecture, so the team doesn't have to continuously spend time on maintaining their own documentation + +::: + +--- + +If you made the decision to start migrating, then the first thing you want to do is to set up an alias for `📁 src`. It will be helpful later to refer to top-level folders. We will consider `@` as an alias for `./src` for the rest of this guide. + +## Step 1. Divide the code by pages {#divide-code-by-pages} + +Most custom architectures already have a division by pages, however small or large in logic. If you already have `📁 pages`, you may skip this step. + +If you only have `📁 routes`, create `📁 pages` and try to move as much component code from `📁 routes` as possible. Ideally, you would have a tiny route and a larger page. As you're moving code, create a folder for each page and add an index file: + +:::note + +For now, it's okay if your pages reference each other. You can tackle that later, but for now, focus on establishing a prominent division by pages. + +::: + +Route file: + +```js title="src/routes/products.[id].js" +export { ProductPage as default } from "@/pages/product" +``` + +Page index file: + +```js title="src/pages/product/index.js" +export { ProductPage } from "./ProductPage.jsx" +``` + +Page component file: + +```jsx title="src/pages/product/ProductPage.jsx" +export function ProductPage(props) { + return
; +} +``` + +## Step 2. Separate everything else from the pages {#separate-everything-else-from-pages} + +Create a folder `📁 src/shared` and move everything that doesn't import from `📁 pages` or `📁 routes` there. Create a folder `📁 src/app` and move everything that does import the pages or routes there, including the routes themselves. + +Remember that the Shared layer doesn't have slices, so it's fine if segments import from each other. + +You should end up with a file structure like this: + +
+ 📁 src +
    +
  • +
    + 📁 app +
      +
    • +
      + 📁 routes +
        +
      • 📄 products.jsx
      • +
      • 📄 products.[id].jsx
      • +
      +
      +
    • +
    • 📄 App.jsx
    • +
    • 📄 index.js
    • +
    +
    +
  • +
  • +
    + 📁 pages +
      +
    • +
      + 📁 product +
        +
      • +
        + 📁 ui +
          +
        • 📄 ProductPage.jsx
        • +
        +
        +
      • +
      • 📄 index.js
      • +
      +
      +
    • +
    • 📁 catalog
    • +
    +
    +
  • +
  • +
    + 📁 shared +
      +
    • 📁 actions
    • +
    • 📁 api
    • +
    • 📁 components
    • +
    • 📁 containers
    • +
    • 📁 constants
    • +
    • 📁 i18n
    • +
    • 📁 modules
    • +
    • 📁 helpers
    • +
    • 📁 utils
    • +
    • 📁 reducers
    • +
    • 📁 selectors
    • +
    • 📁 styles
    • +
    +
    +
  • +
+
+ +## Step 3. Tackle cross-imports between pages {#tackle-cross-imports-between-pages} + + + + +Find all instances where one page is importing from the other and do one of the two things: + +1. Copy-paste the imported code into the depending page to remove the dependency +2. Move the code to a proper segment in Shared: + - if it's a part of the UI kit, move it to `📁 shared/ui`; + - if it's a configuration constant, move it to `📁 shared/config`; + - if it's a backend interaction, move it to `📁 shared/api`. + +:::note + +**Copy-pasting isn't architecturally wrong**, in fact, sometimes it may be more correct to duplicate than to abstract into a new reusable module. The reason is that sometimes the shared parts of pages start drifting apart, and you don't want dependencies getting in your way in these cases. + +However, there is still sense in the DRY ("don't repeat yourself") principle, so make sure you're not copy-pasting business logic. Otherwise you will need to remember to fix bugs in several places at once. + +::: + +## Step 4. Unpack the Shared layer {#unpack-shared-layer} + +You might have a lot of stuff in the Shared layer on this step, and you generally want to avoid that. The reason is that the Shared layer may be a dependency for any other layer in your codebase, so making changes to that code is automatically more prone to unintended consequences. + +Find all the objects that are only used on one page and move it to the slice of that page. And yes, _that applies to actions, reducers, and selectors, too_. There is no benefit in grouping all actions together, but there is benefit in colocating relevant actions close to their usage. + +You should end up with a file structure like this: + +
+ 📁 src +
    +
  • 📁 app (unchanged)
  • +
  • +
    + 📁 pages +
      +
    • +
      + 📁 product +
        +
      • 📁 actions
      • +
      • 📁 reducers
      • +
      • 📁 selectors
      • +
      • +
        + 📁 ui +
          +
        • 📄 Component.jsx
        • +
        • 📄 Container.jsx
        • +
        • 📄 ProductPage.jsx
        • +
        +
        +
      • +
      • 📄 index.js
      • +
      +
      +
    • +
    • 📁 catalog
    • +
    +
    +
  • +
  • +
    + 📁 shared (only objects that are reused) +
      +
    • 📁 actions
    • +
    • 📁 api
    • +
    • 📁 components
    • +
    • 📁 containers
    • +
    • 📁 constants
    • +
    • 📁 i18n
    • +
    • 📁 modules
    • +
    • 📁 helpers
    • +
    • 📁 utils
    • +
    • 📁 reducers
    • +
    • 📁 selectors
    • +
    • 📁 styles
    • +
    +
    +
  • +
+
+ +## Step 5. Organize code by technical purpose {#organize-by-technical-purpose} + +In FSD, division by technical purpose is done with _segments_. There are a few common ones: + +- `ui` — everything related to UI display: UI components, date formatters, styles, etc. +- `api` — backend interactions: request functions, data types, mappers, etc. +- `model` — the data model: schemas, interfaces, stores, and business logic. +- `lib` — library code that other modules on this slice need. +- `config` — configuration files and feature flags. + +You can create your own segments, too, if you need. Make sure not to create segments that group code by what it is, like `components`, `actions`, `types`, `utils`. Instead, group the code by what it's for. + +Reorganize your pages to separate code by segments. You should already have a `ui` segment, now it's time to create other segments, like `model` for your actions, reducers, and selectors, or `api` for your thunks and mutations. + +Also reorganize the Shared layer to remove these folders: +- `📁 components`, `📁 containers` — most of it should become `📁 shared/ui`; +- `📁 helpers`, `📁 utils` — if there are some reused helpers left, group them together by function, like dates or type conversions, and move theses groups to `📁 shared/lib`; +- `📁 constants` — again, group by function and move to `📁 shared/config`. + +## Optional steps {#optional-steps} + +### Step 6. Form entities/features from Redux slices that are used on several pages {#form-entities-features-from-redux} + +Usually, these reused Redux slices will describe something relevant to the business, for example, products or users, so these can be moved to the Entities layer, one entity per one folder. If the Redux slice is related to an action that your users want to do in your app, like comments, then you can move it to the Features layer. + +Entities and features are meant to be independent from each other. If your business domain contains inherent connections between entities, refer to the [guide on business entities][business-entities-cross-relations] for advice on how to organize these connections. + +The API functions related to these slices can stay in `📁 shared/api`. + +### Step 7. Refactor your modules {#refactor-your-modules} + +The `📁 modules` folder is commonly used for business logic, so it's already pretty similar in nature to the Features layer from FSD. Some modules might also be describe large chunks of the UI, like an app header. In that case, you should migrate them to the Widgets layer. + +### Step 8. Form a clean UI foundation in `shared/ui` {#form-clean-ui-foundation} + +`📁 shared/ui` should ideally contain a set of UI elements that don't have any business logic encoded in them. They should also be highly reusable. + +Refactor the UI components that used to be in `📁 components` and `📁 containers` to separate out the business logic. Move that business logic to the higher layers. If it's not used in too many places, you could even consider copy-pasting. + +## See also {#see-also} + +- [(Talk in Russian) Ilya Klimov — Крысиные бега бесконечного рефакторинга: как не дать техническому долгу убить мотивацию и продукт](https://youtu.be/aOiJ3k2UvO4) + +[ext-steiger]: https://github.com/feature-sliced/steiger +[business-entities-cross-relations]: /docs/guides/examples/types#business-entities-and-their-cross-references diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-legacy.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-legacy.mdx deleted file mode 100644 index 67ee065c12..0000000000 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-legacy.mdx +++ /dev/null @@ -1,120 +0,0 @@ ---- -sidebar_position: 3 -sidebar_class_name: sidebar-item--wip ---- - -import WIP from '@site/src/shared/ui/wip/tmpl.mdx' - -# Migration from legacy - - - -> The article aggregates the experience of several companies and projects on moving to Feature-Sliced Design with different initial conditions - -## Why? - -> How much does the move need? "Death by a thousand cuts" and those debt. What is missing? How can the methodology help? - -> See the talk of [Ilya Klimov about the need and procedure for refactoring](http://youtu.be/aOiJ3k2UvO4) - -![approaches-themed-bordered](/img/approaches.png) - -## What's the plan? - -### 1. Unification of the code base - -```diff -- ├── products/ -- | ├── components/ -- | ├── containers/ -- | ├── store/ -- | ├── styles/ -- ├── checkout/ -- | ├── components/ -- | ├── containers/ -- | ├── helpers/ -- | ├── styles/ -+ └── src/ - ├── actions/ - ├── api/ -+ ├── components/ -+ ├── containers/ - ├── constants/ - ├── epics/ -+ ├── i18n/ - ├── modules/ -+ ├── helpers/ -+ ├── pages/ -- ├── routes/ -- ├── utils/ - ├── reducers/ -- ├── redux/ - ├── selectors/ -+ ├── store -+ ├── styles/ - ├── App.jsx - └── index.jsx -``` - -### 2. Putting together the destructive decoupled - -```diff - └── src/ -- ├── actions/ - ├── api/ -- ├── components/ -- ├── containers/ -- ├── constants/ -- ├── epics/ -+ ├── entities/{...} -+ | ├── ui -+ | ├── model/{actions, selectors, ...} -+ | ├── lib - ├── i18n/ - | # We can temporarily put the remaining segments here -+ ├── modules/{helpers, constants} -- ├── helpers/ - ├── pages/ -- ├── reducers/ -- ├── selectors/ -- ├── store/ - ├── styles/ - ├── App.jsx - └── index.jsx -``` - -### 3. Allocate scopes of responsibility - -```diff - └── src/ -- ├── api/ -+ ├── app/ -+ | ├── index.jsx -+ | ├── style.css - ├── pages/ -+ ├── features/ -+ | ├── add-to-cart/{ui, model, lib} -+ | ├── choose-delivery/{ui, model, lib} -+ ├── entities/{...} -+ | ├── delivery/{ui, model, lib} -+ | ├── cart/{ui, model, lib} -+ | ├── product/{ui, model, lib} -+ ├── shared/ -+ | ├── api/ -+ | ├── lib/ # helpers -+ | | ├── i18n/ -+ | ├── config/ # constants -- ├── i18n/ -- ├── modules/{helpers, constants} - └── index.jsx -``` - -### 4. Final ? - -> About the remaining problems and how much it is worth eliminating them - -## See also - -- [(Talk) Ilya Klimov-The Rat Race of endless refactoring: how not to let technical debt kill motivation and product](https://youtu.be/aOiJ3k2UvO4) -- [(Talk) Ilya Azin - Architecture of Frontend projects](https://youtu.be/SnzPAr_FJ7w) - - There is also discussed approaches for architecture and costs of refactoring \ No newline at end of file diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md index 3bd4324d5b..4c0a98a649 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md @@ -1,8 +1,8 @@ --- -sidebar_position: 4 +sidebar_position: 2 --- -# Migration from v1 +# Migration from v1 to v2 ## Why v2? @@ -158,10 +158,10 @@ Now it is much easier to [observe the principle of low coupling][refs-low-coupli - [New ideas v2 with explanations (atomicdesign-chat)][ext-tg-v2-draft] - [Discussion of abstractions and naming for the new version of the methodology (v2)](https://github.com/feature-sliced/documentation/discussions/31) -[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion +[refs-low-coupling]: /docs/reference/slices-segments#zero-coupling-high-cohesion [refs-adaptability]: /docs/about/understanding/naming -[ext-v1]: https://featureslices.dev/v1.0.html +[ext-v1]: https://feature-sliced.github.io/featureslices.dev/v1.0.html [ext-tg-spb]: https://t.me/feature_slices [ext-fdd]: https://github.com/feature-sliced/documentation/tree/rc/feature-driven [ext-fdd-issues]: https://github.com/kof/feature-driven-architecture/issues diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md new file mode 100644 index 0000000000..af057a421e --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md @@ -0,0 +1,45 @@ +--- +sidebar_position: 3 +--- + +# Migration from v2.0 to v2.1 + +The main change in v2.1 is the new mental model for decomposing an interface — pages first. + +In v2.0, FSD would recommend identifying entities and features in your interface, considering even the smallest bits of entity representation and interactivity for decomposition. Then you would build widgets and pages from entities and features. In this model of decomposition, most of the logic was in entities and features, and pages were just compositional layers that didn't have much significance on their own. + +In v2.1, we recommend starting with pages, and possibly even stopping there. Most people already know how to separate the app into individual pages, and pages are also a common starting point when trying to locate a component in the codebase. In this new model of decomposition, you keep most of the UI and logic in each individual page, maintaining a reusable foundation in Shared. If a need arises to reuse business logic across several pages, you can move it to a layer below. + +Another addition to Feature-Sliced Design is the standardization of cross-imports between entities with the `@x`-notation. + +## How to migrate {#how-to-migrate} + +There are no breaking changes in v2.1, which means that a project written with FSD v2.0 is also a valid project in FSD v2.1. However, we believe that the new mental model is more beneficial for teams and especially onboarding new developers, so we recommend making minor adjustments to your decomposition. + +### Merge slices + +A simple way to start is by running our linter, [Steiger][steiger], on the project. Steiger is built with the new mental model, and the most helpful rules will be: + +- [`insignificant-slice`][insignificant-slice] — if an entity or feature is only used in one page, this rule will suggest merging that entity or feature into the page entirely. +- [`excessive-slicing`][excessive-slicing] — if a layer has too many slices, it's usually a sign that the decomposition is too fine-grained. This rule will suggest merging or grouping some slices to help project navigation. + +```bash +npx steiger src +``` + +This will help you identify which slices are only used once, so that you could reconsider if they are really necessary. In such considerations, keep in mind that a layer forms some kind of global namespace for all the slices inside of it. Just as you wouldn't pollute the global namespace with variables that are only used once, you should treat a place in the namespace of a layer as valuable, to be used sparingly. + +### Standardize cross-imports + +If you had cross-imports between in your project before (we don't judge!), you may now take advantage of a new notation for cross-importing in Feature-Sliced Design — the `@x`-notation. It looks like this: + +```ts title="entities/B/some/file.ts" +import type { EntityA } from "entities/A/@x/B"; +``` + +For more details, check out the [Public API for cross-imports][public-api-for-cross-imports] section in the reference. + +[insignificant-slice]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice +[steiger]: https://github.com/feature-sliced/steiger +[excessive-slicing]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/excessive-slicing +[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx index 5b12632df5..a30b16912b 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx @@ -1,16 +1,121 @@ --- sidebar_position: 10 -sidebar_class_name: sidebar-item--wip --- +# Usage with NextJS -import WIP from '@site/src/shared/ui/wip/tmpl.mdx' +It is possible to implement FSD in a NextJS project, but conflicts arise due to differences between the requirements for the NextJS project structure and the principles of FSD in two points:  -# Usage with NextJS +- Routing files in the `pages` layer +- Conflict or absence of the `app` layer in NextJS + +## Conflict between FSD and NextJS on `pages` layer {#pages-conflict} + + +NextJS suggests using the `pages` folder to define application routes. NextJS expects files in the `pages` folder to match URLs. +This routing mechanism **does not correspond** to the FSD concept, since it is not possible to maintain a flat slice structure in such a routing mechanism. + +### Moving the `pages` folder of NextJS to the root folder of the project (recommended) + +The approach is to move the `pages` NextJS folder to the root folder of the project and import the FSD pages into the `pages` NextJS folder. This saves +the FSD project structure inside the `src` folder. + +```sh +├── pages # NextJS pages folder +├── src +│ ├── app +│ ├── entities +│ ├── features +│ ├── pages # FSD pages folder +│ ├── shared +│ ├── widgets +``` + +### Renaming the `pages` layer within the FSD structure + +Another way to solve the problem is to rename the `pages` layer in the FSD structure to avoid conflicts with the NextJS `pages` folder. +You can rename the `pages` layer in FSD to `views`. +In that way, the structure of the project in the `src` folder is preserved without contradiction with the requirements of NextJS. + +```sh +├── app +├── entities +├── features +├── pages # NextJS pages folder +├── views # Renamed FSD pages folder +├── shared +├── widgets +``` + +Keep in mind that it's highly recommended to document this rename prominently in your project's README or internal documentation. This rename is a part of your ["project knowledge"][project-knowledge]. + +## The absence of the `app` folder in NextJS {#app-absence} + +In NextJS below version 13, there is no explicit `app` folder, instead NextJS provides the `_app.tsx` file, +which plays the role of a wrapping component for all project pages. - +### Importing app functionality to `pages/_app.tsx` file -> About the specifics of the framework and compatibility with the methodology +To solve the problem of missing the `app` folder in the NextJS structure, you can create an `App` component inside the `app` layer and import the `App` component into `pages/_app.tsx` so that NextJS can work with it. +For example: -## See also +```tsx +// app/providers/index.tsx + +const App = ({ Component, pageProps }: AppProps) => { + return ( + + + + + + + + ); +}; + +export default App; +``` +Then you can import the `App` component and global project styles into `pages/_app.tsx` as follows: + +```tsx +// pages/_app.tsx + +import 'app/styles/index.scss' + +export { default } from 'app/providers'; +``` + +## Dealing with App Router {#app-router} + +App Router has become stable in NextJS version 13.4. App Router allows you to use the `app` folder for routing instead of the `pages` folder. +To comply with the principles of FSD, you should treat the NextJS `app` folder in the same way as recommended +to resolve a conflict with the NextJS `pages` folder. + +The approach is to move the NextJS `app` folder to the root folder of the project and import the FSD pages into the NextJS `app` folder. This saves +the FSD project structure inside the `src` folder. You should still also add the `pages` folder to the root, because the App router is compatible with the Pages router. + +``` +├── app # NextJS app folder +├── pages # Stub NextJS pages folder +│ ├── README.md # Description of why this folder exists +├── src +│ ├── app # FSD app folder +│ ├── entities +│ ├── features +│ ├── pages # FSD pages folder +│ ├── shared +│ ├── widgets +``` + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)][ext-app-router-stackblitz] + +## Middleware + +If you are using middleware in a project, it also needs to be moved from `src` to the root of the project. Otherwise, NextJS simply won't see it — middleware must be located strictly at the root next to `app` or `pages`. + +## See also {#see-also} - [(Thread) About the pages directory in NextJS](https://t.me/feature_sliced/3623) + +[project-knowledge]: /docs/about/understanding/knowledge-types +[ext-app-router-stackblitz]: https://stackblitz.com/edit/stackblitz-starters-aiez55?file=README.md diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx new file mode 100644 index 0000000000..929bdcaf51 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx @@ -0,0 +1,179 @@ +--- +sidebar_position: 10 +--- +# Usage with NuxtJS + +It is possible to implement FSD in a NuxtJS project, but conflicts arise due to the differences between NuxtJS project structure requirements and FSD principles: + +- Initially, NuxtJS offers a project file structure without a `src` folder, i.e. in the root of the project. +- The file routing is in the `pages` folder, while in FSD this folder is reserved for the flat slice structure. + + +## Adding an alias for the `src` directory + +Add an `alias` object to your config: +```ts title="nuxt.config.ts" +export default defineNuxtConfig({ + devtools: { enabled: true }, // Not FSD related, enabled at project startup + alias: { + "@": '../src' + }, +}) +``` +## Choose how to configure the router + +In NuxtJS, there are two ways to customize the routing - using a config and using a file structure. +In the case of file-based routing, you will create index.vue files in folders inside the app/routes directory, and in the case of configure, you will configure the routers in the `router.options.ts` file. + + +### Routing using config + +In the `app` layer, create a `router.options.ts` file, and export a config object from it: +```ts title="app/router.options.ts" +import type { RouterConfig } from '@nuxt/schema'; + +export default { + routes: (_routes) => [], +}; + +``` + +To add a `Home` page to your project, you need to do the following steps: +- Add a page slice inside the `pages` layer +- Add the appropriate route to the `app/router.config.ts` config + + +To create a page slice, let's use the [CLI](https://github.com/feature-sliced/cli): + +```shell +fsd pages home +``` + +Create a ``home-page.vue`` file inside the ui segment, access it using the Public API + +```ts title="src/pages/home/index.ts" +export { default as HomePage } from './ui/home-page'; +``` + +Thus, the file structure will look like this: +```sh +|── src +│ ├── app +│ │ ├── router.config.ts +│ ├── pages +│ │ ├── home +│ │ │ ├── ui +│ │ │ │ ├── home-page.vue +│ │ │ ├── index.ts +``` +Finally, let's add a route to the config: + +```ts title="app/router.config.ts" +import type { RouterConfig } from '@nuxt/schema' + +export default { + routes: (_routes) => [ + { + name: 'home', + path: '/', + component: () => import('@/pages/home.vue').then(r => r.default || r) + } + ], +} +``` + +### File Routing + +First of all, create a `src` directory in the root of your project, and create app and pages layers inside this directory and a routes folder inside the app layer. +Thus, your file structure should look like this: + +```sh +├── src +│ ├── app +│ │ ├── routes +│ ├── pages # Pages folder, related to FSD +``` + +In order for NuxtJS to use the routes folder inside the `app` layer for file routing, you need to modify `nuxt.config.ts` as follows: +```ts title="nuxt.config.ts" +export default defineNuxtConfig({ + devtools: { enabled: true }, // Not FSD related, enabled at project startup + alias: { + "@": '../src' + }, + dir: { + pages: './src/app/routes' + } +}) +``` + +Now, you can create routes for pages within `app` and connect pages from `pages` to them. + +For example, to add a `Home` page to your project, you need to do the following steps: +- Add a page slice inside the `pages` layer +- Add the corresponding route inside the `app` layer +- Connect the page from the slice with the route + +To create a page slice, let's use the [CLI](https://github.com/feature-sliced/cli): + +```shell +fsd pages home +``` + +Create a ``home-page.vue`` file inside the ui segment, access it using the Public API + +```ts title="src/pages/home/index.ts" +export { default as HomePage } from './ui/home-page'; +``` + +Create a route for this page inside the `app` layer: + +```sh + +├── src +│ ├── app +│ │ ├── routes +│ │ │ ├── index.vue +│ ├── pages +│ │ ├── home +│ │ │ ├── ui +│ │ │ │ ├── home-page.vue +│ │ │ ├── index.ts +``` + +Add your page component inside the `index.vue` file: + +```html title="src/app/routes/index.vue" + + + +``` + +## What to do with `layouts`? + +You can place layouts inside the `app` layer, to do this you need to modify the config as follows: + +```ts title="nuxt.config.ts" +export default defineNuxtConfig({ + devtools: { enabled: true }, // Not related to FSD, enabled at project startup + alias: { + "@": '../src' + }, + dir: { + pages: './src/app/routes', + layouts: './src/app/layouts' + } +}) +``` + + +## See also + +- [Documentation on changing directory config in NuxtJS](https://nuxt.com/docs/api/nuxt-config#dir) +- [Documentation on changing router config in NuxtJS](https://nuxt.com/docs/guide/recipes/custom-routing#router-config) +- [Documentation on changing aliases in NuxtJS](https://nuxt.com/docs/api/nuxt-config#alias) + diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx new file mode 100644 index 0000000000..88931cfa3f --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx @@ -0,0 +1,436 @@ +--- +sidebar_position: 10 +--- +# Usage with React Query + +## The problem of “where to put the keys” + +### Solution — break down by entities + +If the project already has a division into entities, and each request corresponds to a single entity, +the purest division will be by entity. In this case, we suggest using the following structure: +```sh +└── src/ # + ├── app/ # + | ... # + ├── pages/ # + | ... # + ├── entities/ # + | ├── {entity}/ # + | ... └── api/ # + | ├── `{entity}.query` # Query-factory where are the keys and functions + | ├── `get-{entity}` # Entity getter function + | ├── `create-{entity}` # Entity creation function + | ├── `update-{entity}` # Entity update function + | ├── `delete-{entity}` # Entity delete function + | ... # + | # + ├── features/ # + | ... # + ├── widgets/ # + | ... # + └── shared/ # + ... # +``` + +If there are connections between the entities (for example, the Country entity has a field-list of City entities), +then you can use the [public API for cross-imports][public-api-for-cross-imports] or consider the alternative solution below. + +### Alternative solution — keep it in shared + +In cases where entity separation is not appropriate, the following structure can be considered: + +```sh +└── src/ # + ... # + └── shared/ # + ├── api/ # + ... ├── `queries` # Query-factories + | ├── `document.ts` # + | ├── `background-jobs.ts` # + | ... # + └── index.ts # +``` + +Then in `@/shared/api/index.ts`: + +```ts title="@/shared/api/index.ts" +export { documentQueries } from "./queries/document"; +``` + +## The problem of “Where to insert mutations?” + +It is not recommended to mix mutations with queries. There are two options: + +### 1. Define a custom hook in the `api` segment near the place of use + +```tsx title="@/features/update-post/api/use-update-title.ts" +export const useUpdateTitle = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ id, newTitle }) => + apiClient + .patch(`/posts/${id}`, { title: newTitle }) + .then((data) => console.log(data)), + + onSuccess: (newPost) => { + queryClient.setQueryData(postsQueries.ids(id), newPost); + }, + }); +}; +``` + +### 2. Define a mutation function somewhere else (Shared or Entities) and use `useMutation` directly in the component + +```tsx +const { mutateAsync, isPending } = useMutation({ + mutationFn: postApi.createPost, +}); +``` + +```tsx title="@/pages/post-create/ui/post-create-page.tsx" +export const CreatePost = () => { + const { classes } = useStyles(); + const [title, setTitle] = useState(""); + + const { mutate, isPending } = useMutation({ + mutationFn: postApi.createPost, + }); + + const handleChange = (e: ChangeEvent) => + setTitle(e.target.value); + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + mutate({ title, userId: DEFAULT_USER_ID }); + }; + + return ( +
+ + + Create + + + ); +}; +``` + +## Organization of requests + +### Query factory + +A query factory is an object where the key values are functions that return a list of query keys. Here's how to use it: + +```ts +const keyFactory = { + all: () => ["entity"], + lists: () => [...postQueries.all(), "list"], +}; +``` + +:::info +`queryOptions` is a built-in utility in react-query@v5 (optional) + +```ts +queryOptions({ + queryKey, + ...options, +}); +``` + +For greater type safety, further compatibility with future versions of react-query, and easy access to functions and query keys, +you can use the built-in queryOptions function from “@tanstack/react-query” +[(More details here)](https://tkdodo.eu/blog/the-query-options-api#queryoptions). +::: + +### 1. Creating a Query Factory + +```tsx title="@/entities/post/api/post.queries.ts" +import { keepPreviousData, queryOptions } from "@tanstack/react-query"; +import { getPosts } from "./get-posts"; +import { getDetailPost } from "./get-detail-post"; +import { PostDetailQuery } from "./query/post.query"; + +export const postQueries = { + all: () => ["posts"], + + lists: () => [...postQueries.all(), "list"], + list: (page: number, limit: number) => + queryOptions({ + queryKey: [...postQueries.lists(), page, limit], + queryFn: () => getPosts(page, limit), + placeholderData: keepPreviousData, + }), + + details: () => [...postQueries.all(), "detail"], + detail: (query?: PostDetailQuery) => + queryOptions({ + queryKey: [...postQueries.details(), query?.id], + queryFn: () => getDetailPost({ id: query?.id }), + staleTime: 5000, + }), +}; +``` + +### 2. Using Query Factory in application code +```tsx +import { useParams } from "react-router-dom"; +import { postApi } from "@/entities/post"; +import { useQuery } from "@tanstack/react-query"; + +type Params = { + postId: string; +}; + +export const PostPage = () => { + const { postId } = useParams(); + const id = parseInt(postId || ""); + const { + data: post, + error, + isLoading, + isError, + } = useQuery(postApi.postQueries.detail({ id })); + + if (isLoading) { + return
Loading...
; + } + + if (isError || !post) { + return <>{error?.message}; + } + + return ( +
+

Post id: {post.id}

+
+

{post.title}

+
+

{post.body}

+
+
+
Owner: {post.userId}
+
+ ); +}; +``` + +### Benefits of using a Query Factory +- **Request structuring:** A factory allows you to organize all API requests in one place, making your code more readable and maintainable. +- **Convenient access to queries and keys:** The factory provides convenient methods for accessing different types of queries and their keys. +- **Query Refetching Ability:** The factory allows easy refetching without the need to change query keys in different parts of the application. + +## Pagination + +In this section, we'll look at an example of the `getPosts` function, which makes an API request to retrieve post entities using pagination. + +### 1. Creating a function `getPosts` +The getPosts function is located in the `get-posts.ts` file, which is located in the `api` segment + +```tsx title="@/pages/post-feed/api/get-posts.ts" +import { apiClient } from "@/shared/api/base"; + +import { PostWithPaginationDto } from "./dto/post-with-pagination.dto"; +import { PostQuery } from "./query/post.query"; +import { mapPost } from "./mapper/map-post"; +import { PostWithPagination } from "../model/post-with-pagination"; + +const calculatePostPage = (totalCount: number, limit: number) => + Math.floor(totalCount / limit); + +export const getPosts = async ( + page: number, + limit: number, +): Promise => { + const skip = page * limit; + const query: PostQuery = { skip, limit }; + const result = await apiClient.get("/posts", query); + + return { + posts: result.posts.map((post) => mapPost(post)), + limit: result.limit, + skip: result.skip, + total: result.total, + totalPages: calculatePostPage(result.total, limit), + }; +}; +``` + +### 2. Query factory for pagination +The `postQueries` query factory defines various query options for working with posts, +including requesting a list of posts with a specific page and limit. + +```tsx +import { keepPreviousData, queryOptions } from "@tanstack/react-query"; +import { getPosts } from "./get-posts"; + +export const postQueries = { + all: () => ["posts"], + lists: () => [...postQueries.all(), "list"], + list: (page: number, limit: number) => + queryOptions({ + queryKey: [...postQueries.lists(), page, limit], + queryFn: () => getPosts(page, limit), + placeholderData: keepPreviousData, + }), +}; +``` + + +### 3. Use in application code + +```tsx title="@/pages/home/ui/index.tsx" +export const HomePage = () => { + const itemsOnScreen = DEFAULT_ITEMS_ON_SCREEN; + const [page, setPage] = usePageParam(DEFAULT_PAGE); + const { data, isFetching, isLoading } = useQuery( + postApi.postQueries.list(page, itemsOnScreen), + ); + return ( + <> + setPage(page)} + page={page} + count={data?.totalPages} + variant="outlined" + color="primary" + /> + + + ); +}; +``` +:::note +The example is simplified, the full version is available on [GitHub](https://github.com/ruslan4432013/fsd-react-query-example) +::: + +## `QueryProvider` for managing queries +In this guide, we will look at how to organize a `QueryProvider`. + +### 1. Creating a `QueryProvider` +The file `query-provider.tsx` is located at the path `@/app/providers/query-provider.tsx`. + +```tsx title="@/app/providers/query-provider.tsx" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { ReactNode } from "react"; + +type Props = { + children: ReactNode; + client: QueryClient; +}; + +export const QueryProvider = ({ client, children }: Props) => { + return ( + + {children} + + + ); +}; +``` + +### 2. Creating a `QueryClient` +`QueryClient` is an instance used to manage API requests. +The `query-client.ts` file is located at `@/shared/api/query-client.ts`. +`QueryClient` is created with certain settings for query caching. + +```tsx title="@/shared/api/query-client.ts" +import { QueryClient } from "@tanstack/react-query"; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, + gcTime: 5 * 60 * 1000, + }, + }, +}); +``` + +## Code generation + +There are tools that can generate API code for you, but they are less flexible than the manual approach described above. +If your Swagger file is well-structured, +and you're using one of these tools, it might make sense to generate all the code in the `@/shared/api` directory. + + +## Additional advice for organizing RQ +### API Client + +Using a custom API client class in the shared layer, +you can standardize the configuration and work with the API in the project. +This allows you to manage logging, +headers and data exchange format (such as JSON or XML) from one place. +This approach makes it easier to maintain and develop the project because it simplifies changes and updates to interactions with the API. + +```tsx title="@/shared/api/api-client.ts" +import { API_URL } from "@/shared/config"; + +export class ApiClient { + private baseUrl: string; + + constructor(url: string) { + this.baseUrl = url; + } + + async handleResponse(response: Response): Promise { + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + try { + return await response.json(); + } catch (error) { + throw new Error("Error parsing JSON response"); + } + } + + public async get( + endpoint: string, + queryParams?: Record, + ): Promise { + const url = new URL(endpoint, this.baseUrl); + + if (queryParams) { + Object.entries(queryParams).forEach(([key, value]) => { + url.searchParams.append(key, value.toString()); + }); + } + const response = await fetch(url.toString(), { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + return this.handleResponse(response); + } + + public async post>( + endpoint: string, + body: TData, + ): Promise { + const response = await fetch(`${this.baseUrl}${endpoint}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + return this.handleResponse(response); + } +} + +export const apiClient = new ApiClient(API_URL); +``` + +## See also {#see-also} + +- [(GitHub) Sample Project](https://github.com/ruslan4432013/fsd-react-query-example) +- [(CodeSandbox) Sample Project](https://codesandbox.io/p/github/ruslan4432013/fsd-react-query-example/main) +- [About the query factory](https://tkdodo.eu/blog/the-query-options-api) + +[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx new file mode 100644 index 0000000000..886b2381ee --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx @@ -0,0 +1,100 @@ +--- +sidebar_position: 10 +--- +# Usage with SvelteKit + +It is possible to implement FSD in a SvelteKit project, but conflicts arise due to the differences between the structure requirements of a SvelteKit project and the principles of FSD: + +- Initially, SvelteKit offers a file structure inside the `src/routes` folder, while in FSD the routing must be part of the `app` layer. +- SvelteKit suggests putting everything not related to routing in the `src/lib` folder. + + +## Let's set up the config + +```ts title="svelte.config.ts" +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config}*/ +const config = { + preprocess: [vitePreprocess()], + kit: { + adapter: adapter(), + files: { + routes: 'src/app/routes', // move routing inside the app layer + lib: 'src', + appTemplate: 'src/app/index.html', // Move the application entry point inside the app layer + assets: 'public' + }, + alias: { + '@/*': 'src/*' // Create an alias for the src directory + } + } +}; +export default config; +``` + +## Move file routing to `src/app`. + +Let's create an app layer, move the app's entry point `index.html` into it, and create a routes folder. +Thus, your file structure should look like this: + +```sh +├── src +│ ├── app +│ │ ├── index.html +│ │ ├── routes +│ ├── pages # FSD Pages folder +``` + +Now, you can create routes for pages within `app` and connect pages from `pages` to them. + +For example, to add a home page to your project, you need to do the following steps: +- Add a page slice inside the `pages` layer +- Add the corresponding rooute to the `routes` folder from the `app` layer +- Align the page from the slice with the rooute + +To create a page slice, let's use the [CLI](https://github.com/feature-sliced/cli): + +```shell +fsd pages home +``` + +Create a ``home-page.svelte`` file inside the ui segment, access it using the Public API + +```ts title="src/pages/home/index.ts" +export { default as HomePage } from './ui/home-page.svelte'; +``` + +Create a route for this page inside the `app` layer: + +```sh + +├── src +│ ├── app +│ │ ├── routes +│ │ │ ├── +page.svelte +│ │ ├── index.html +│ ├── pages +│ │ ├── home +│ │ │ ├── ui +│ │ │ │ ├── home-page.svelte +│ │ │ ├── index.ts +``` + +Add your page component inside the `+page.svelte` file: + +```html title="src/app/routes/+page.svelte" + + + + +``` + +## See also. + +- [Documentation on changing directory config in SvelteKit](https://kit.svelte.dev/docs/configuration#files) + + diff --git a/i18n/en/docusaurus-plugin-content-docs/current/privacy.md b/i18n/en/docusaurus-plugin-content-docs/current/privacy.md deleted file mode 100644 index 270de2b194..0000000000 --- a/i18n/en/docusaurus-plugin-content-docs/current/privacy.md +++ /dev/null @@ -1,17 +0,0 @@ -# Privacy - -**We use cookies to collect data about documentation usage (only for further analytics and improvement of the site, and for no other purpose)** - -The only plugins this site relies on are [@docusaurus/plugin-google-analytics](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-google-analytics) and [docusaurus-plugin-hotjar](https://github.com/symblai/docusaurus-plugin-hotjar). We only use the traffic data provided by [Google Analytics](https://analytics.google.com/) and [Hotjar](https://www.hotjar.com/) to collect feedback from you and refine your user experience. - -:::warning NOTE -This page is not a legal notice, please refer to [Google Analytics](https://analytics.google.com/) and [Hotjar](https://www.hotjar.com/) for more details -::: - -**Who will have access to this metrics data?** - -- Core-team of methodology -- Google Analytics, which we rely on for analyze service usage [(more...)](https://www.google.com/analytics/terms/us.html) -- Hotjar, which we rely on for collect usage feedback [(more...)](https://help.hotjar.com/hc/en-us/categories/360003405813) - -![feature-sliced-banner](/img/banner.jpg) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/index.mdx b/i18n/en/docusaurus-plugin-content-docs/current/reference/index.mdx index f1f31cd9d9..d944747bd3 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/index.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/reference/index.mdx @@ -25,15 +25,9 @@ A detailed description of the key concepts of Feature-Sliced Design. to="/docs/reference/slices-segments" Icon={AppstoreOutlined} /> - diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/_category_.yaml b/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/_category_.yaml deleted file mode 100644 index df39e2f2b1..0000000000 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/_category_.yaml +++ /dev/null @@ -1 +0,0 @@ -label: Isolation diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md b/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md deleted file mode 100644 index 503a24bf59..0000000000 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md +++ /dev/null @@ -1,143 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Low Coupling & High Cohesion - -Application modules should be designed according to **high cohesion** (should solve one specific task) and **low coupling** (independent of other modules) principles. - -![coupling-cohesion-themed](/img/coupling.png) - -Within the methodology, this is achieved through: - -* Splitting the application into layers and slices that implement specific functionality -* Providing a [public access interface][refs-public-api] for each module -* Setting up restrictions for [modules interactions][refs-isolation] - each module can depend only on the modules below it, but not on modules from the same or higher layer - -## Components composition (UI level) - -The majority of modern UI frameworks and libraries provide a component model in which each component can have its own properties, state, child components, and even slots. - -This model allows you to design an interface as a **composition of various components that are not directly related to each other** and, thereby, achieve **low coupling** of the interface components. - -### Example - -Let's consider such a composition using the example of a **list with a header:** - -#### Laying the extensibility - -List component will not itself define the look and structure of the header components and list elements, instead it will accept them as parameters - -```tsx -interface ListProps { - Header: React.ReactNode; - Items: React.ReactNode; -} - -const List: Component = ({ Header, Items }) => ( -
- {Header} -
    - {Items} -
-
-) - -``` - -#### Using the composition - -This allows you to **reuse and independently change** components with different Header and list Items. Header and Items components can have both their own local state and their binding to the general state of the application - the List component will not know anything about it, and therefore will not depend on it - -```tsx -} Items={} /> - -} /> - -} Items={} /> - -``` - -## Layer composition (APP level) - -The methodology suggests putting the functionality that is valuable for the user into **features slice**, and the logic related to business entities - into **entities**. Both features and entities **should be designed as modules with high cohesion**, i.e. aimed at solving **one specific task** or related to **one specific entity.** - -All interactions between such modules, similar to the UI components from the example above, should be coordinated via a **modules composition**. - -### Example - -Let's use an example of a chat application with the following features: - -* user can open a contact list and select a friend -* user can open a conversation with a selected friend - -According to methodology principles, it can be represented as: - -Entities - -* User (contains user's state) -* Contact (state of the contact list, utilities for working with an individual contact) -* Chat (the state of the current chat and utilies for it) - -Features - -* Form for sending a message -* Chat selection menu - -#### Let's tie it all together - -The application, to begin with, will have one page, and the interface will be slightly modified from the first example - -```tsx title=page/main/ui.tsx -} - Items={} - Footer={} -/> -``` - -#### Data model - -The page data model will be organized as a **composition of features and entities**. In this example, the features will be implemented as factories and they will access the interface of entities through the parameters of these factories. - -> However, the implementation using factory is optional - the feature may directly depend on the lower layers. - -```ts title=pages/main/model.ts -import { userModel } from "entitites/user" -import { conversationModel } from "entities/conversation" -import { contactModel } from "entities/contact" - -import { createMessageInput } from "features/message-input" -import { createConversationSwitch } from "features/conversation-switch" - -import { beautifiy } from "shared/lib/beautify-text" - -export const { allConversations, setConversation } = createConversationSwitch({ - contacts: contactModel.allContacts, - setConversation: conversationModel.setConversation, - currentConversation: conversationModel.conversation, - currentUser: userModel.currentUser -}) - -export const { sendMessage, attachFile } = createMessageInput({ - author: userModel.currentUser - send: conversationModel.sendMessage, - formatMessage: beautify -}) -``` - -## Summary - -1. Modules must have **high cohesion** (have one responsibility, solve one specific task) and provide a [**public interface**][refs-public-api] access -2. **Low coupling** is achieved through the composition of elements - UI components, features and entities -3. To reduce entanglement, modules **should interact with each other only through a public interfaces** - this makes modules independent of each other's internal implementation - -## See also - -* [(Article) Low Coupling and High Cohesion in details](https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/) - * *The diagram at the beginning is inspired by this article* -* [(Article) Low Coupling and High Cohesion. The Law of Demeter](https://medium.com/german-gorelkin/low-coupling-high-cohesion-d36369fb1be9) -* [(Presentation) On design principles (including Low Coupling & High Cohesion)](https://www.slideshare.net/cristalngo/software-design-principles-57388843) - -[refs-public-api]: /docs/reference/public-api -[refs-isolation]: /docs/reference/isolation diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/decouple-entities.mdx b/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/decouple-entities.mdx deleted file mode 100644 index 6b8d757d7a..0000000000 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/decouple-entities.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -sidebar_position: 2 -sidebar_class_name: sidebar-item--wip ---- - -import WIP from '@site/src/shared/ui/wip/tmpl.mdx' - -# Decouple entities - - - -> About cross-imports of types, adapters and about how to explicitly build connections between entities - -> Also about mythical absolutely-decoupled entities - -## See also - -- [(Thread) Memo about decomposition by entities and building explicit links between them](https://t.me/feature_sliced/3633) -- [(Thread) Example of decomposition for "connected entities" (users/pets/friends)](https://t.me/feature_sliced/3316) -- [(Thread) About cross-imports of types/adapters in entities](https://t.me/feature_sliced/4276) -- [(Thread) About the boundaries of entities and features](https://t.me/feature_sliced/4521) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/index.md b/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/index.md deleted file mode 100644 index 42756834fd..0000000000 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/index.md +++ /dev/null @@ -1,71 +0,0 @@ -# Isolation of modules - -Within the framework of the methodology, all modules are distributed by scopes of responsibility (layer, slice, segment) - -The layers, in turn, are organized vertically: - -- "At the bottom" are the reused modules (ui-kit, internal libraries of the project), as the most abstract -- And as you move "up", more specific modules are located. - -Regardless of whether it belongs to any slice, each module [**is required to provide a public access interface**][refs-public-api]. - -## Requirements - -The interaction of each module with the rest of the application is designed taking into account a number of requirements: - -1. **Low coupling** with other modules - - *A change in one module should have a weak and predictable effect on others* - -1. **High cohesion** - the responsibilities of each module are "focused" on one task - - - *If the module has too many responsibilities (starts "doing too much") - this should be noticed as soon as possible* -1. **Absence of cyclic dependencies** on the scale of the entire application - - - *Often lead to unexpected, undesirable behavior, it is better to avoid them altogether* - -## Rule - -To meet these requirements, within the framework of the methodology, it is necessary to observe the basic rule: - -:::info Important - -A module can depend only on "underlying" modules, but not on modules from the same or higher layer - -::: - -- `features/auth` **cannot** depend on `features/filters` **and vice versa** -- `features/auth` **may** depend on `shared/ui/button`, **but not vice versa** - -Following this rule allows you to keep dependencies **"unidirectional"** - which automatically **eliminates cyclic imports** and significantly **simplifies tracking dependencies** between modules in the application. - -## Identifying problems - - -Violation of this rule is a signal of problems: - -1. The module has **import from another module** from its own layer - - - Perhaps the module was **unnecessarily fragmented** or has **unnecessary responsibility.** - - You should **combine** it with the imported module or **move it (partially or completely) to the layer below** or transfer the logic of relationships to modules on higher layers. - -1. The module **is imported by many modules** from its own layer - - - Perhaps the module has **extra responsibility.** - - You should **move it (partially or entirely) to the layer below**, or transfer the logic of connections to modules on higher layers. - -1. The module **has imports from many modules** from its own layer - - - Perhaps the module belongs to **another scope of responsibility.** - - You should **move it (partially or completely) to the layer above**. - -## See also - -- [(Guide) About achieving low coupling][refs-low-coupling] -- [(Discussion) Coupled entities](https://github.com/feature-sliced/documentation/discussions/49) -- [(Discussion) About cross-imports and analysis зависимостей](https://github.com/feature-sliced/documentation/discussions/65#discussioncomment-480822) -- [**GRASP** Patterns](https://en.wikipedia.org/wiki/GRASP_(object-oriented_design)) - -[refs-public-api]: /docs/reference/public-api -[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/layers.mdx b/i18n/en/docusaurus-plugin-content-docs/current/reference/layers.mdx index 8782c9d45f..6833fca012 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/layers.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/reference/layers.mdx @@ -3,22 +3,16 @@ sidebar_position: 1 pagination_next: reference/slices-segments --- -# Layers - -Layers are the first level of organisational hierarchy in Feature-Sliced Design. Their purpose is to separate code based on how much responsibility it needs and how many other modules in the app it depends on. - -:::note +import useBaseUrl from '@docusaurus/useBaseUrl'; -On this page, a _module_ refers to an internal module in the application — a file or directory with an index file. Not to be confused with npm packages. - -::: +# Layers -Every layer carries special semantic meaning to help you determine how much responsibility you should allocate to a module in your code. The names and meanings of layers are standardized across all projects built with Feature-Sliced Design. +Layers are the first level of organisational hierarchy in Feature-Sliced Design. Their purpose is to separate code based on how much responsibility it needs and how many other modules in the app it depends on. Every layer carries special semantic meaning to help you determine how much responsibility you should allocate to your code. There are **7 layers** in total, arranged from most responsibility and dependency to least: -A file system tree, with a single root folder called src and then seven subfolders: app, processes, pages, widgets, features, entities, shared. The processes folder is slightly faded out. -A file system tree, with a single root folder called src and then seven subfolders: app, processes, pages, widgets, features, entities, shared. The processes folder is slightly faded out. +A file system tree, with a single root folder called src and then seven subfolders: app, processes, pages, widgets, features, entities, shared. The processes folder is slightly faded out. +A file system tree, with a single root folder called src and then seven subfolders: app, processes, pages, widgets, features, entities, shared. The processes folder is slightly faded out. 1. App 2. Processes (deprecated) @@ -28,133 +22,107 @@ There are **7 layers** in total, arranged from most responsibility and depe 6. Entities 7. Shared -You don't have to use every layer in your project — only add them if you think it brings value to your project. +You don't have to use every layer in your project — only add them if you think it brings value to your project. Typically, most frontend projects will have at least the Shared, Pages, and App layers. + +In practice, layers are folders with lowercase names (for example, `📁 shared`, `📁 pages`, `📁 app`). Adding new layers is _not recommended_ because their semantics are standardized. ## Import rule on layers -Layers are made up of _slices_ — highly cohesive groups of modules. Feature-Sliced Design promotes low coupling, which is why dependencies between slices are regulated by **the import rule on layers**: +Layers are made up of _slices_ — highly cohesive groups of modules. Dependencies between slices are regulated by **the import rule on layers**: + +> _A module (file) in a slice can only import other slices when they are located on layers strictly below._ -> _A module in a slice can only import other slices when they are located on layers strictly below._ +For example, the folder `📁 ~/features/aaa` is a slice with the name "aaa". A file inside of it, `~/features/aaa/api/request.ts`, cannot import code from any file in `📁 ~/features/bbb`, but can import code from `📁 ~/entities` and `📁 ~/shared`, as well as any sibling code from `📁 ~/features/aaa`, for example, `~/features/aaa/lib/cache.ts`. -For example, in `~/features/aaa`, `aaa` is the slice, so a file `~/features/aaa/api/request.ts` cannot import code from any module in `~/features/bbb`, but can import code from `~/entities` and `~/shared`, as well as any sibling code from `~/features/aaa`. +Layers App and Shared are **exceptions** to this rule — they are both a layer and a slice at the same time. Slices partition code by business domain, and these two layers are exceptions because Shared does not have business domains, and App combines all business domains. + +In practice, this means that layers App and Shared are made up of segments, and segments can import each other freely. ## Layer definitions +This section describes the semantic meaning of each layer to create an intuition for what kind of code belongs there. + ### Shared -Isolated modules, components and abstractions that are detached from the specifics of the project or business. -Warning: not to be treated like [a utility dump][ext--sova-utility-dump]! +This layer forms a foundation for the rest of the app. It's a place to create connections with the external world, for example, backends, third-party libraries, the environment. It is also a place to define your own highly contained libraries. -This layer, unlike others, does not consist of slices, and instead consists of segments directly. +This layer, like the App layer, _does not contain slices_. Slices are intended to divide the layer into business domains, but business domains do not exist in Shared. This means that all files in Shared can reference and import from each other. -**Content examples**: +Here are the segments that you can typically find in this layer: -* UI kit -* API client -* Code working with browser APIs +- `📁 api` — the API client and potentially also functions to make requests to specific backend endpoints. +- `📁 ui` — the application's UI kit. + Components on this layer should not contain business logic, but it's okay for them to be business-themed. For example, you can put the company logo and page layout here. Components with UI logic are also allowed (for example, autocomplete or a search bar). +- `📁 lib` — a collection of internal libraries. + This folder should not be treated as helpers or utilities ([read here why these folders often turn into a dump][ext-sova-utility-dump]). Instead, every library in this folder should have one area of focus, for example, dates, colors, text manipulation, etc. That area of focus should be documented in a README file. The developers in your team should know what can and cannot be added to these libraries. +- `📁 config` — environment variables, global feature flags and other global configuration for your app. +- `📁 routes` — route constants or patterns for matching routes. +- `📁 i18n` — setup code for translations, global translation strings. + +You are free to add more segments, but make sure that the name of these segments describes the purpose of the content, not its essence. For example, `components`, `hooks`, and `types` are bad segment names because they aren't that helpful when you're looking for code. ### Entities -Concepts from the real world that form together the essence of the project. Commonly, these are the terms that the business uses to describe the product. +Slices on this layer represent concepts from the real world that the project is working with. Commonly, they are the terms that the business uses to describe the product. For example, a social network might work with business entities like User, Post, and Group. -Each slice in this layer contains static UI elements, data stores and CRUD operations. +An entity slice might contain the data storage (`📁 model`), data validation schemas (`📁 model`), entity-related API request functions (`📁 api`), as well as the visual representation of this entity in the interface (`📁 ui`). The visual representation doesn't have to produce a complete UI block — it is primarily meant to reuse the same appearance across several pages in the app, and different business logic may be attached to it through props or slots. -**Slice examples**: +#### Entity relationships - - -
For a social network For a Git frontend (e.g., GitHub)
    -
  • User
  • -
  • Post
  • -
  • Group
  • -
    -
  • Repository
  • -
  • File
  • -
  • Commit
  • -
+Entities in FSD are slices, and by default, slices cannot know about each other. In real life, however, entities often interact with each other, and sometimes one entity owns or contains other entities. Because of that, the business logic of these interactions is preferably kept in higher layers, like Features or Pages. +When one entity's data object contains other data objects, usually it's a good idea to make the connection between the entities explicit and side-step the slice isolation by making a cross-reference API with the `@x` notation. The reason is that connected entities need to be refactored together, so it's best to make the connection impossible to miss. -:::tip +For example: -You may notice in the example of a Git frontend that a _repository_ contains _files_. This makes the repository a higher-level entity which has other entities nested inside. That is a common situation with entities, and sometimes it's hard to manage such higher-level entities without breaking the import rule on layers. +```ts title="entities/artist/model/artist.ts" +import type { Song } from "entities/song/@x/artist"; -Here are a few suggestions to overcome this issue: -* The UI of entities should contain slots for places where the lower-level entities are to be inserted -* The business logic related to entity interaction should be placed in features (most of the time) -* The typings of database entities can be extracted to the Shared layer below, next to the API client +export interface Artist { + name: string; + songs: Array; +} +``` -::: - -### Features +```ts title="entities/song/@x/artist.ts" +export type { Song } from "../model/song.ts"; +``` -Actions that a user can make in the application to interact with the business entities to achieve a valuable outcome. This also includes actions that the app makes on behalf of the user to produce value for them. +Learn more about the `@x` notation in the [Public API for cross-imports][public-api-for-cross-imports] section. -Each slice in this layer can contain _interactive_ UI elements, internal state and API calls that enable value-producing actions. +### Features -**Slice examples**: +This layer is for the main interactions in your app, things that your users care to do. These interactions often involve business entities, because that's what the app is about. - - -
For a social network For a Git frontend (e.g., GitHub) Actions on behalf of users
    -
  • Authenticate
  • -
  • Create a post
  • -
  • Join a group
  • -
    -
  • Edit a file
  • -
  • Leave a comment
  • -
  • Merge branches
  • -
    -
  • Detect dark mode
  • -
  • Perform background computation
  • -
  • User-Agent-based actions
  • -
+A crucial principle for using the Features layer effectively is: **not everything needs to be a feature**. A good indicator that something needs to be a feature is the fact that it is reused on several pages. -### Widgets +For example, if the app has several editors, and all of them have comments, then comments are a reused feature. Remember that slices are a mechanism for finding code quickly, and if there are too many features, the important ones are drowned out. -Self-sufficient UI blocks that emerged from the composition of lower-level units like entities and features. +Ideally, when you arrive in a new project, you would discover its functionality by looking through the pages and features. When deciding on what should be a feature, optimize for the experience of a newcomer to the project to quickly discover large important areas of code. -This layer provides a way to fill in the slots left in the UI of Entities with other Entities and interactive elements from Features. Therefore, it is common not to have business logic on this layer, instead keeping it in Features. Each slice in this layer contains ready-to-use UI components and sometimes non-business logic such as gestures, keyboard interaction, etc. +A feature slice might contain the UI to perform the interaction like a form (`📁 ui`), the API calls needed to make the action (`📁 api`), validation and internal state (`📁 model`), feature flags (`📁 config`). -Sometimes, however, it is more convenient to have business logic on this layer. Usually it happens when the widget is quite rich in interactivity (e.g., interactive data tables) and the business logic inside them is not used in other places. +### Widgets -**Slice examples**: +The Widgets layer is intended for large self-sufficient blocks of UI. Widgets are most useful when they are reused across multiple pages, or when the page that they belong to has multiple large independent blocks, and this is one of them. - - -
For a social network For a Git frontend (e.g., GitHub)
    -
  • Post card
  • -
  • User profile header (with actions)
  • -
    -
  • List of files in a repository (with actions)
  • -
  • Comment in a thread
  • -
  • Repository card
  • -
+If a block of UI makes up most of the interesting content on a page, and is never reused, it **should not be a widget**, and instead it should be placed directly inside that page. :::tip -If you're using a nested routing system (e.g. the router of [Remix][ext--remix]), it may be helpful to use the Widgets layer in the same way as a flat routing system would use the Pages layer — to create complete interface blocks, complete with related data fetching, loading states, and error boundaries. In the same way, you can store page layouts on this layer. +If you're using a nested routing system (like the router of [Remix][ext-remix]), it may be helpful to use the Widgets layer in the same way as a flat routing system would use the Pages layer — to create full router blocks, complete with related data fetching, loading states, and error boundaries. + +In the same way, you can store page layouts on this layer. ::: ### Pages -Complete pages for a page-based application (like a website) or screens/activities for screen-based applications (like mobile apps). - -This layer is similar to Widgets in its compositional nature, albeit on a larger scale. Each slice in this layer contains UI components that are ready to be plugged into a router and sometimes data-fetching logic and error handling. +Pages are what makes up websites and applications (also known as screens or activities). One page usually corresponds to one slice, however, if there are several very similar pages, they can be grouped into one slice, for example, registration and login forms. -**Slice examples**: +There's no limit to how much code you can place in a page slice as long as your team still finds it easy to navigate. If a UI block on a page is not reused, it's perfectly fine to keep it inside the page slice. - - -
For a social network For a Git frontend (e.g., GitHub)
    -
  • News feed
  • -
  • Community page
  • -
  • User's public profile
  • -
    -
  • Repository page
  • -
  • User's repositories
  • -
  • Branches in a repository
  • -
+In a page slice you can typically find the page's UI as well as loading states and error boundaries (`📁 ui`) and the data fetching and mutating requests (`📁 api`). It's not common for a page to have a dedicated data model, and tiny bits of state can be kept in the components themselves. ### Processes @@ -164,7 +132,7 @@ This layer has been deprecated. The current version of the spec recommends avoid ::: -Escape hatches for multi-page interactions. +Processes are escape hatches for multi-page interactions. This layer is deliberately left undefined. Most applications should not use this layer, and keep router-level and server-level logic on the App layer. Consider using this layer only when the App layer grows large enough to become unmaintainable and needs unloading. @@ -172,14 +140,15 @@ This layer is deliberately left undefined. Most applications should not use this All kinds of app-wide matters, both in the technical sense (e.g., context providers) and in the business sense (e.g., analytics). -This layer usually doesn't contain slices, like Shared, instead having segments directly. +This layer usually doesn't contain slices, as well as Shared, instead having segments directly. -**Content examples**: +Here are the segments that you can typically find in this layer: -* Styles -* Routing -* Store and other context providers -* Analytics initialization +- `📁 routes` — the router configuration +- `📁 store` — global store configuration +- `📁 styles` — global styles +- `📁 entrypoint` — the entrypoint to the application code, framework-specific -[ext--remix]: https://remix.run -[ext--sova-utility-dump]: https://dev.to/sergeysova/why-utils-helpers-is-a-dump-45fo +[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports +[ext-remix]: https://remix.run +[ext-sova-utility-dump]: https://dev.to/sergeysova/why-utils-helpers-is-a-dump-45fo diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/public-api.md b/i18n/en/docusaurus-plugin-content-docs/current/reference/public-api.md index 78543f859b..e04ddf7993 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/public-api.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/reference/public-api.md @@ -1,217 +1,155 @@ --- sidebar_position: 3 -pagination_next: about/index --- -# Public API - -Each entity of the methodology is designed as a **user-friendly and integrable module.** - -## Goals - -The convenience of using and integrating the module is achieved through the fulfillment of *a number of goals*: +import useBaseUrl from '@docusaurus/useBaseUrl'; -1. The application must be **protected from changes** to the internal structure of individual modules -1. The processing of the internal structure of the module **should not affect** other modules -1. Significant changes in the behavior of the module should be **easily detectable** - > **Significant changes in the behavior of the module** - changes that break the expectations of the user entities of the module. - -These goals can be achieved by introducing a public interface (Public API), which is a single access point to the module's capabilities and defines the "contract" of the module's interaction with the outside world. - -:::info Important +# Public API -The entity structure must have a single entry point that provides a public interface +A public API is a _contract_ between a group of modules, like a slice, and the code that uses it. It also acts as a gate, only allowing access to certain objects, and only through that public API. -::: +In practice, it's usually implemented as an index file with re-exports: -```sh -└── features/               #  - ├── auth-form / # Internal structure of the feature - | ├── ui/        # - | ├── model/     # - | ├── {...}/     # - ├── index.ts # Entrypoint features with its public API +```js title="pages/auth/index.js" +export { LoginPage } from "./ui/LoginPage"; +export { RegisterPage } from "./ui/RegisterPage"; ``` -```ts title=**/**/index.ts -export { Form as AuthForm } from "./ui" -export * as authFormModel from "./model" -``` - -## Requirements for the public API - -Meeting these requirements allows you to reduce interaction with the module to **the implementation of a public interface-contract** and, thereby, achieve reliability and ease of use of the module. - -### 1. Access Control - -The public API must **control access** to the contents of the module - -- Other parts of the application can use **only those module entities that are presented in the public interface** -- The internal part of the module outside the public interface **is accessible only to the module itself**. - -#### Examples +## What makes a good public API? -##### Suspension from private imports +A good public API makes using and integrating into other code a slice convenient and reliable. It can be achieved by setting these three goals: -- **Bad**: There is a direct access to the internal parts of the module, bypassing the public access interface - it is dangerous, especially when refactoring the module +1. The rest of the application must be protected from structural changes to the slice, like a refactoring +1. Significant changes in the behavior of the slice that break the previous expectations should cause changes in the public API +1. Only the necessary parts of the slice should be exposed - ```diff - - import { Form } from "features/auth-form/components/view/form" - ``` +The last goal has some important practical implications. It may be tempting to create wildcard re-exports of everything, especially in early development of the slice, because any new objects you export from your files are also automatically exported from the slice: -- **Good:** The API exports only what is necessary and allowed in advance, the module developer now needs to think only about not breaking the Public API when refactoring - - ```diff - + import { AuthForm } from "features/auth-form" - ``` - -### 2. Sustainability for changes - -The public API should be sustainable for changes inside the module - -- Breaking changes in the behavior of the module are reflected in the change of the Public API - -#### Examples - -##### Abstracting from the implementation - -Changing the internal structure should not lead to a change in the Public API - -- **Bad:** moving or renaming this component inside the feature will lead to the need to refactor imports in all places where the component is used. +```js title="Bad practice, features/comments/index.js" +// ❌ BAD CODE BELOW, DON'T DO THIS +export * from "./ui/Comment"; // 👎 don't try this at home +export * from "./model/comments"; // 💩 this is bad practice +``` - ```diff - - import { Form } from "features/auth-form/ui/form" - ``` +This hurts the discoverability of a slice because you can't easily tell what the interface of this slice is. Not knowing the interface means that you have to dig deep into the code of a slice to understand how to integrate it. Another problem is that you might accidentally expose the module internals, which will make refactoring difficult if someone starts depending on them. -- **Good:** the interface of the feature does not display its internal structure, external "users" of the feature will not suffer from moving or renaming the component inside the feature +## Public API for cross-imports {#public-api-for-cross-imports} - ```diff - + import { AuthForm } from "features/auth-form" - ``` +Cross-imports are a situation when one slice imports from another slice on the same layer. Usually that is prohibited by the [import rule on layers][import-rule-on-layers], but often there are legitimate reasons to cross-import. For example, business entities often reference each other in the real world, and it's best to reflect these relationships in the code instead of working around them. -### 3. Integrability +For this purpose, there's a special kind of public API, also known as the `@x`-notation. If you have entities A and B, and entity B needs to import from entity A, then entity A can declare a separate public API just for entity B. -The public API should facilitate **easy and flexible integration** +- `📂 entities` + - `📂 A` + - `📂 @x` + - `📄 B.ts` — a special public API just for code inside `entities/B/` + - `📄 index.ts` — the regular public API -- Should be convenient for use by the rest of the application, in particular, to solve the problem of name collisions +Then the code inside `entities/B/` can import from `entities/A/@x/B`: -#### Examples +```ts +import type { EntityA } from "entities/A/@x/B"; +``` -##### Name collision +The notation `A/@x/B` is meant to be read as "A crossed with B". -- **Bad:** there will be a name collision +:::note - ```ts title=features/auth-form/index.ts - export { Form } from "./ui" - export * as model from "./model" - ``` +Try to keep cross-imports to a minimum, and **only use this notation on the Entities layer**, where eliminating cross-imports is often unreasonable. - ```ts title=features/post-form/index.ts - export { Form } from "./ui" - export * as model from "./model" - ``` +::: - ```diff - - import { Form, model } from "features/auth-form" - - import { Form, model } from "features/post-form" - ``` +## Issues with index files -- **Good:** the collision is solved at the interface level +Index files like `index.js`, also known as barrel files, are the most common way to define a public API. They are easy to make, but they are known to cause problems with certain bundlers and frameworks. - ```ts title=features/auth-form/index.ts - export { Form as AuthForm } from "./ui" - export * as authFormModel from "./model" - ``` +### Circular imports - ```ts title=features/post-form/index.ts - export { Form as PostForm } from "./ui" - export * as postFormModel from "./model" - ``` +Circular import is when two or more files import each other in a circle. - ```diff - + import { AuthForm, authFormModel } from "features/auth-form" - + import { PostForm, postFormModel } from "features/post-form" - ``` + -##### Flexible use +
+ Three files importing each other in a circle + Three files importing each other in a circle +
+ Pictured above: three files, `fileA.js`, `fileB.js`, and `fileC.js`, importing each other in a circle. +
+
-- **Bad:** it is inconvenient to write, it is inconvenient to read, the" user " of the feature suffers +These situations are often difficult for bundlers to deal with, and in some cases they might even lead to runtime errors that might be difficult to debug. - ```diff - - import { storeActionUpdateUserDetails } from "features/auth-form" - - dispatch(storeActionUpdateUserDetails(...)) - ``` +Circular imports can occur without index files, but having an index file presents a clear opporutnity to accidentally create a circular import. It often happens when you have two objects exposed in the public API of a slice, for example, `HomePage` and `loadUserStatistics`, and the `HomePage` needs to access `loadUserStatistics`, but it does it like this: -- **Good:** the "user" of the feature gets access to the necessary things iteratively and flexibly +```jsx title="pages/home/ui/HomePage.jsx" +import { loadUserStatistics } from "../"; // importing from pages/home/index.js - ```diff - + import { authFormModel } from "features/auth-form" - + dispatch(authFormModel.effects.updateUserDetails(...)) // redux - + authFormModel.updateUserDetailsFx(...) // effector - ``` +export function HomePage() { /* … */ } +``` -##### Resolution of collisions +```js title="pages/home/index.js" +export { HomePage } from "./ui/HomePage"; +export { loadUserStatistics } from "./api/loadUserStatistics"; +``` -Name collisions should be resolved at the level of the public interface, not the implementation +This situation creates a circular import, because `index.js` imports `ui/HomePage.jsx`, but `ui/HomePage.jsx` imports `index.js`. -- **Bad:** name collisions are resolved at the implementation level +To prevent this issue, consider these two principles. If you have two files, and one imports from the other: +- When they are in the same slice, always use _relative_ imports and write the full import path +- When they are in different slices, always use _absolute_ imports, for example, with an alias - ```ts title=features/auth-form/index.ts - export { AuthForm } from "./ui" - export { authFormActions, authFormReducer } from "model" - ``` +### Large bundles and broken tree-shaking in Shared {#large-bundles} - ```ts title=features/post-form/index.ts - export { PostForm } from "./ui" - export { postFormActions, postFormReducer } from "model" - ``` +Some bundlers might have a hard time tree-shaking (removing code that isn't imported) when you have an index file that re-exports everything. -- **Good:** name collisions are resolved at the interface level +Usually this isn't a problem for public APIs, because the contents of a module are usually quite closely related, so you would rarely need to import one thing and tree-shake away the other. However, there are two very common cases when the normal rules of public API in FSD may lead to issues — `shared/ui` and `shared/lib`. - ```ts title=features/auth-form/model.ts - export { actions, reducer } - ``` +These two folders are both collections of unrelated things that often aren't all needed in one place. For example, `shared/ui` might have modules for every component in the UI library: - ```ts title=features/auth-form/index.ts - export { Form as AuthForm } from "./ui" - export * as authFormModel from "./model" - ``` +- `📂 shared/ui/` + - `📁 button` + - `📁 text-field` + - `📁 carousel` + - `📁 accordion` - ```ts title=features/post-form/model.ts - export { actions, reducer } - ``` +This problem is made worse when one of these modules has a heavy dependency, like a syntax highlighter or a drag'n'drop library. You don't want to pull those into every page that uses something from `shared/ui`, for example, a button. - ```ts title=features/post-form/index.ts - export { Form as PostForm } from "./ui" - export * as postFormModel from "./model" - ``` +If your bundles grow undesirably due to a single public API in `shared/ui` or `shared/lib`, it's recommended to instead have a separate index file for each component or library: -## About re-exports +- `📂 shared/ui/` + - `📂 button` + - `📄 index.js` + - `📂 text-field` + - `📄 index.js` -In JavaScript, the public interface of a module is created by re-exporting entities from inside the module in an `index` file: +Then the consumers of these components can import them directly like this: -```ts title=**/**/index.ts -export { Form as AuthForm } from "./ui" -export * as authModel from "./model" +```js title="pages/sign-in/ui/SignInPage.jsx" +import { Button } from '@/shared/ui/button'; +import { TextField } from '@/shared/ui/text-field'; ``` -### Disadvantages +### No real protection against side-stepping the public API + +When you create an index file for a slice, you don't actually forbid anyone from not using it and importing directly. This is especially a problem for auto-imports, because there are several places from which an object can be imported, so the IDE has to decide that for you. Sometimes it might choose to import directly, breaking the public API rule on slices. -- In most popular bundlers, due to re-exports, **the code-splitting works worse**, because [tree-shaking](https://webpack.js.org/guides/tree-shaking/) with this approach, it is safe to discard only the entire module, but not part of it. - > For example, importing `authModel` into the page model will cause the `AuthForm` component to get into the chunk of this page, even if this component is not used there. +To catch these issues automatically, we recommend using [Steiger][ext-steiger], an architectural linter with a ruleset for Feature-Sliced Design. -- As a result, initialization of the chunk becomes more expensive, because the browser must process all the modules in it, including those that got into the bundle "for the company" +### Worse performance of bundlers on large projects -### Possible solutions +Having a large amount of index files in a project can slow down the development server, as noted by TkDodo in [his article "Please Stop Using Barrel Files"][ext-please-stop-using-barrel-files]. -- `webpack` allows you to mark re-export files as [**side effects free**](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free) - this allows `webpack` to use more aggressive optimizations when working with such a file +There are several things you can do to tackle this issue: +1. The same advice as in ["Large bundles and broken tree-shaking in Shared" issue](#large-bundles) — have separate index files for each component/library in `shared/ui` and `shared/lib` instead of one big one +2. Avoid having index files in segments on layers that have slices. + For example, if you have an index for the feature "comments", `📄 features/comments/index.js`, there's no reason to have another index for the `ui` segment of that feature, `📄 features/comments/ui/index.js`. +3. If you have a very big project, there's a good chance that your application can be split into several big chunks. + For example, Google Docs has very different responsibilities for the document editor and for the file browser. You can create a monorepo setup where each package is a separate FSD root, with its own set of layers. Some packages can only have the Shared and Entities layers, others might only have Pages and App, others still might include their own small Shared, but still use the big one from another package too. -## See also + -- [(Discussion) Public Abstraction API][disc-src] -- [Principles **SOLID**][ext-solid] -- [Patterns **GRASP**][ext-grasp] + -[disc-src]: https://github.com/feature-sliced/documentation/discussions/41 -[ext-solid]: https://ru.wikipedia.org/wiki/SOLID -[ext-grasp]: https://ru.wikipedia.org/wiki/GRASP +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-please-stop-using-barrel-files]: https://tkdodo.eu/blog/please-stop-using-barrel-files diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx b/i18n/en/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx index 01aeb65e24..846451d473 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx @@ -4,54 +4,70 @@ sidebar_position: 2 pagination_next: reference/public-api --- +import useBaseUrl from '@docusaurus/useBaseUrl'; + # Slices and segments ## Slices -Slices are the second level in the organizational hierarchy of Feature-Sliced Design. Their main purpose is to group code by its meaning for the product, business or just the application. +Slices are the second level in the organizational hierarchy of Feature-Sliced Design. Their main purpose is to group code by its meaning for the product, business, or just the application. -The names of slices are not standardized because they are directly determined by the business domain of your application. For example, a photo gallery might have slices `photo`, `create-album`, `gallery-page`. A social network would require different slices, for example, `post`, `add-user-to-friends`, `news-feed`. +The names of slices are not standardized because they are directly determined by the business domain of your application. For example, a photo gallery might have slices `photo`, `effects`, `gallery-page`. A social network would require different slices, for example, `post`, `comments`, `news-feed`. -Closely related slices can be structurally grouped in a directory, but they should exercise the same isolation rules as other slices — there should be **no code sharing** in that directory. +The layers Shared and App don't contain slices. That is because Shared should contain no business logic at all, hence has no meaning for the product, and App should contain only code that concerns the entire application, so no splitting is necessary. -![Features "compose", "like" and "delete" grouped in a directory "post". In that directory there is also a file "some-shared-code.ts" that is crossed out to imply that it's not allowed.](/img/graphic-nested-slices.svg) +### Zero coupling and high cohesion {#zero-coupling-high-cohesion} -The layers Shared and App don't contain slices. That is because Shared should contain no business logic at all, hence has no meaning for the product, and App should contain only code that concerns the entire application, so no splitting is necessary. +Slices are meant to be independent and highly cohesive groups of code files. The graphic below might help to visualize the tricky concepts of _cohesion_ and _coupling_: + +
+ + +
+ Image inspired by https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/ +
+
+ +An ideal slice is independent from other slices on its layer (zero coupling) and contains most of the code related to its primary goal (high cohesion). + +The independence of slices is enforced by the [import rule on layers][layers--import-rule]: + +> _A module (file) in a slice can only import other slices when they are located on layers strictly below._ ### Public API rule on slices -Inside a slice, the code could be organized very liberally, and that doesn't pose any issues as long as the slice provides a good public API. This is enforced with the **public API rule on slices**: +Inside a slice, the code could be organized in any way that you want. That doesn't pose any issues as long as the slice provides a good public API for other slices to use it. This is enforced with the **public API rule on slices**: > _Every slice (and segment on layers that don't have slices) must contain a public API definition._ > > _Modules outside of this slice/segment can only reference the public API, not the internal file structure of the slice/segment._ -Read more about the rationale of public APIs and the best practices on creating one in the [Public API reference][ref--public-api]. +Read more about the rationale of public APIs and the best practices on creating one in the [Public API reference][ref-public-api]. + +### Slice groups + +Closely related slices can be structurally grouped in a folder, but they should exercise the same isolation rules as other slices — there should be **no code sharing** in that folder. + +![Features "compose", "like" and "delete" grouped in a folder "post". In that folder there is also a file "some-shared-code.ts" that is crossed out to imply that it's not allowed.](/img/graphic-nested-slices.svg) ## Segments Segments are the third and final level in the organizational hierarchy, and their purpose is to group code by its technical nature. There a few standardized segment names: -* `ui` — UI components, data formatting functions -* `model` — business logic and data storage, as well as functions to manipulate this data -* `lib` — auxiliary and infrastructural code -* `api` — communication with external APIs, backend API methods -Custom segments are permitted, but should be created sparingly. The most common places for custom segments are the App layer and the Shared layer, where slices don't make sense. +- `ui` — everything related to UI display: UI components, date formatters, styles, etc. +- `api` — backend interactions: request functions, data types, mappers, etc. +- `model` — the data model: schemas, interfaces, stores, and business logic. +- `lib` — library code that other modules on this slice need. +- `config` — configuration files and feature flags. -### Examples +See the [Layers page][layers--layer-definitions] for examples of what each of these segments might be used for on different layers. -| Layer | `ui` | `model` | `lib` | `api` | -| :------- | :----------- | :----------- | :----------- | :----------- | -| Shared | UI kit | Usually not used | Utility modules of several related files.
If you need to use individual helpers, consider using utility libraries such as [`lodash-es`][ext--lodash]. | Rudimentary API client with additional features like authentication or caching. | -| Entities | Skeleton of a business entity with slots for interactive elements | Data storage of instances of this entity as well as functions for manipulating that data.
This segment is most fit for storing server-side data. If you use [TanStack Query][ext--tanstack-query] or other methods of implicit storage, you may choose to omit this segment. | Functions for manipulating instances of this entity that aren't related to storage | API methods using the API client from Shared for easy communication with the backend | -| Features | Interactive elements that enable users to use this feature | Business logic and infrastructure data storage, if needed (e.g., current app theme). This is the code that actually produces value for the user. | Infrastructural code that helps to concisely describe the business logic in the `model` segment | API methods that represent this feature on the backend.
May compose API methods from Entities. | -| Widgets | Composition of Entities and Features into self-contained UI blocks.
Can also contain error boundaries and loading states. | Infrastructure data storage, if needed | Non-business interactions (e.g., gestures) and other necessary code for the block to function on a page | Usually not used, but can contain data loaders in nested routing contexts (e.g., [Remix][ext--remix]) | -| Pages | Composition of Entities, Features and Widgets into complete pages.
Can also contain error boundaries and loading states. | Usually not used | Non-business interactions (e.g., gestures) and other necessary code for the page to deliver a complete user experience | Data loaders for SSR-oriented frameworks | +You can also create custom segments. The most common places for custom segments are the App layer and the Shared layer, where slices don't make sense. -[ref--public-api]: /docs/reference/public-api +Make sure that the name of these segments describes the purpose of the content, not its essence. For example, `components`, `hooks`, and `types` are bad segment names because they aren't that helpful when you're looking for code. -[ext--lodash]: https://www.npmjs.com/package/lodash-es -[ext--tanstack-query]: https://tanstack.com/query/latest -[ext--remix]: https://remix.run +[layers--layer-definitions]: /docs/reference/layers#layer-definitions +[layers--import-rule]: /docs/reference/layers#import-rule-on-layers +[ref-public-api]: /docs/reference/public-api diff --git a/i18n/en/docusaurus-theme-classic/footer.json b/i18n/en/docusaurus-theme-classic/footer.json index 4b930976dc..b9e10f3101 100644 --- a/i18n/en/docusaurus-theme-classic/footer.json +++ b/i18n/en/docusaurus-theme-classic/footer.json @@ -20,21 +20,17 @@ "description": "The label of footer link with label=Discussions linking to https://github.com/feature-sliced/documentation/discussions" }, "link.item.label.Community": { - "message": "Community", - "description": "The label of the footer link with label=Community linking to /community" + "message": "Community", + "description": "The label of the footer link with label=Community linking to /community" }, "link.item.label.Help": { - "message": "Help", - "description": "The label of the footer link with label=Help linking to /nav" + "message": "Help", + "description": "The label of the footer link with label=Help linking to /nav" }, "link.item.label.License": { "message": "License", "description": "The label of footer link with label=License linking to LICENSE" }, - "link.item.label.Privacy": { - "message": "Privacy", - "description": "The label of footer link with label=Privacy linking to /docs/privacy" - }, "link.item.label.Contribution Guide (RU)": { "message": "Contribution Guide (RU)", "description": "The label of footer link with label=Contribution Guide (RU) linking to CONTRIBUTING.md" @@ -64,7 +60,7 @@ "description": "The label of footer link with label=GitHub linking to https://github.com/feature-sliced" }, "copyright": { - "message": "Copyright © 2018-2023 Feature-Sliced Design", + "message": "Copyright © 2018-2025 Feature-Sliced Design", "description": "The footer copyright" } } diff --git a/i18n/ja/code.json b/i18n/ja/code.json new file mode 100644 index 0000000000..738036b4d7 --- /dev/null +++ b/i18n/ja/code.json @@ -0,0 +1,410 @@ +{ + "pages.home.features.title": { + "message": "利点", + "description": "Features" + }, + "pages.home.features.logic.title": { + "message": "明確なビジネスロジック", + "description": "Feature title" + }, + "pages.home.features.logic.description": { + "message": "アーキテクチャはドメインモジュールで構成されているため、習得が容易である", + "description": "Feature description" + }, + "pages.home.features.adaptability.title": { + "message": "適応性", + "description": "Feature title" + }, + "pages.home.features.adaptability.description": { + "message": "アーキテクチャのコンポーネントは柔軟に交換したり、新しい条件に応じて追加したりすることができる", + "description": "Feature description" + }, + "pages.home.features.debt.title": { + "message": "技術的負債", + "description": "Feature title" + }, + "pages.home.features.debt.description": { + "message": "各モジュールは副作用なしに独立して変更/再作成できる", + "description": "Feature description" + }, + "pages.home.features.shared.title": { + "message": "明確な再利用性", + "description": "Feature title" + }, + "pages.home.features.shared.description": { + "message": "DRYとローカルカスタマイズのバランスが保たれている", + "description": "Feature description" + }, + "pages.home.concepts.title": { + "message": "コンセプト", + "description": "Concepts" + }, + "pages.home.concepts.public.title": { + "message": "公開API", + "description": "Concept title" + }, + "pages.home.concepts.public.description": { + "message": "各モジュールはその公開APIをトップレベルで宣言する必要がある", + "description": "Concept description" + }, + "pages.home.concepts.isolation.title": { + "message": "分離", + "description": "Concept title" + }, + "pages.home.concepts.isolation.description": { + "message": "モジュールは同じレイヤーや上層レイヤーの他のモジュールに直接依存してはいけない", + "description": "Concept description" + }, + "pages.home.concepts.needs.title": { + "message": "ニーズの理解", + "description": "Concept title" + }, + "pages.home.concepts.needs.description": { + "message": "ビジネスとユーザーのニーズに焦点を当てる", + "description": "Concept description" + }, + "pages.home.scheme.title": { + "message": "構造", + "description": "Scheme" + }, + "pages.home.companies.using": { + "message": "FSDを使用している企業", + "description": "Companies using methodology" + }, + "pages.home.companies.add_me": { + "message": "あなたの会社でFSDが使用されていますか?", + "description": "Methodology is used in your company?" + }, + "pages.home.companies.tell_us": { + "message": "教えてください", + "description": "Tell us" + }, + "pages.examples.title": { + "message": "実装例", + "description": "Page title" + }, + "pages.examples.subtitle": { + "message": "FSDを使って作られたプロジェクト一覧", + "description": "Page subtitle" + }, + "pages.examples.add_me.title": { + "message": "実装例を追加", + "description": "Request to add example" + }, + "pages.examples.repo.title": { + "message": "リポジトリ", + "description": "Examples repository label" + }, + "pages.examples.versions": { + "message": "バージョン一覧も参照してください", + "description": "Versions reminder" + }, + "pages.versions.title": { + "message": "Feature-Sliced Designのバージョン", + "description": "Feature-Sliced Design versions" + }, + "pages.versions.current": { + "message": "ここで現在公開されているバージョンのドキュメントを見つけることができます", + "description": "Description for current version" + }, + "pages.versions.legacy": { + "message": "ここで古いバージョンのドキュメントを見つけることができます", + "description": "Description for legacy version" + }, + "pages.nav.title": { + "message": "🧭 ナビゲーション", + "description": "NavPage title" + }, + "pages.nav.legacy.title": { + "message": "古いリンク", + "description": "NavPage section=legacy title" + }, + "pages.nav.legacy.details": { + "message": "ドキュメントの再構成後、一部の記事リンクが変更されました。以下に探しているページが見つかるかもしれません。", + "description": "NavPage section=legacy details" + }, + "pages.nav.legacy.subdetails": { + "message": "互換性のために古いリンクからのリダイレクトがあります", + "description": "NavPage section=legacy subdetails" + }, + "features.feedback-badge.label": { + "message": "ドキュメントのフィードバックを共有する 🤙", + "description": "Feedback share button label" + }, + "features.feedback-badge.url": { + "message": "https://forms.gle/7p4anU2shHAzmfqc8", + "description": "Feedback share form url" + }, + "features.feedback-doc.thanks": { + "message": "フィードバックありがとうございます!", + "description": "DocFeedback block=Thanks" + }, + "features.feedback-doc.title": { + "message": "このページは役に立ちましたか?", + "description": "DocFeedback block=Title" + }, + "features.feedback-doc.subtitle": { + "message": "あなたのフィードバックはドキュメントの改善に役立ちます", + "description": "DocFeedback block=Subtitle" + }, + "features.feedback-doc.button-text": { + "message": "フィードバックを送る", + "description": "The text on a floating button to leave feedback about the docs" + }, + "features.feedback-doc.email-placeholder": { + "message": "メールアドレスを入力してください(任意)", + "description": "The placeholder for email input" + }, + "features.feedback-doc.error-message": { + "message": "後でもう一度お試しください。", + "description": "The error message displayed when feedback form submission fails" + }, + "features.feedback-doc.modal-title-error-403": { + "message": "リクエストURLがこのプロジェクトのPushFeedbackに指定されたURLと一致しません。", + "description": "The title of the modal displayed when the feedback form submission fails with 403 error" + }, + "features.feedback-doc.modal-title-error-404": { + "message": "提供されたプロジェクトIDをPushFeedbackで見つけることができませんでした。", + "description": "The title of the modal displayed when the feedback form submission fails with 404 error" + }, + "features.feedback-doc.message-placeholder": { + "message": "ここにフィードバックを書いてください…", + "description": "The placeholder for message input" + }, + "features.feedback-doc.modal-title": { + "message": "あなたの意見を共有してください", + "description": "The title of the modal displayed when the feedback form is opened" + }, + "features.feedback-doc.modal-title-error": { + "message": "おっと!", + "description": "The title of the modal displayed when the feedback form submission fails" + }, + "features.feedback-doc.modal-title-success": { + "message": "フィードバックありがとうございます!", + "description": "The title of the modal displayed when the feedback form submission is successful" + }, + "features.feedback-doc.rating-placeholder": { + "message": "このページは役に立ちましたか?", + "description": "The placeholder for rating input" + }, + "features.feedback-doc.rating-stars-placeholder": { + "message": "このページをどう評価しますか", + "description": "The placeholder for rating stars input" + }, + "features.feedback-doc.screenshot-button-text": { + "message": "スクリーンショットを撮る", + "description": "The text on a button to take a screenshot" + }, + "features.feedback-doc.screenshot-topbar-text": { + "message": "ページ上の要素を選択してください", + "description": "The text displayed in the top bar of the screenshot tool" + }, + "features.feedback-doc.send-button-text": { + "message": "送信", + "description": "The text on a button to send feedback" + }, + "features.hero.tagline": { + "message": "フロントエンドアーキテクチャの設計方法論", + "description": "Architectural methodology for frontend projects" + }, + "features.hero.get_started": { + "message": "始める", + "description": "Get Started" + }, + "features.hero.examples": { + "message": "実装例", + "description": "Examples" + }, + "features.hero.previous": { + "message": "前のバージョン", + "description": "Previous version" + }, + "shared.wip.title": { + "message": "この記事は執筆中です", + "description": "Admonition title" + }, + "shared.wip.subtitle": { + "message": "その公開を早めるために、以下の方法があります。", + "description": "Admonition subtitle" + }, + "shared.wip.var.feedback.base": { + "message": "📢 フィードバックを共有する ", + "description": "Variant for contribute (base)" + }, + "shared.wip.var.feedback.link": { + "message": "(チケットでのコメント/絵文字リアクション)", + "description": "Variant for contribute (link)" + }, + "shared.wip.var.material.base": { + "message": "💬 チャットでの議論結果をチケットにまとめる ", + "description": "Variant for contribute (base)" + }, + "shared.wip.var.material.link": { + "message": "(チャットURL)", + "description": "Variant for contribute (link)" + }, + "shared.wip.var.contribute.base": { + "message": "⚒️ 他の方法で", + "description": "Variant for contribute (base)" + }, + "shared.wip.var.contribute.link": { + "message": "貢献する", + "description": "Variant for contribute (link)" + }, + "theme.NotFound.title": { + "message": "ページが見つかりません", + "description": "The title of the 404 page" + }, + "theme.NotFound.p1": { + "message": "申し訳ありませんが、リクエストされたページが見つかりませんでした。", + "description": "The first paragraph of the 404 page" + }, + "theme.NotFound.p2": { + "message": "リンク元のサイトの所有者に連絡して、リンクが機能しないことを知らせてください。", + "description": "The 2nd paragraph of the 404 page" + }, + "theme.AnnouncementBar.closeButtonAriaLabel": { + "message": "閉じる", + "description": "The ARIA label for close button of announcement bar" + }, + "theme.blog.paginator.navAriaLabel": { + "message": "ブログリストページのナビゲーション", + "description": "The ARIA label for the blog pagination" + }, + "theme.blog.paginator.newerEntries": { + "message": "次のエントリ", + "description": "The label used to navigate to the newer blog posts page (previous page)" + }, + "theme.blog.paginator.olderEntries": { + "message": "前のエントリ", + "description": "The label used to navigate to the older blog posts page (next page)" + }, + "theme.blog.post.readingTime.plurals": { + "message": "{readingTime} 分の読書", + "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.tags.tagsListLabel": { + "message": "タグ:", + "description": "The label alongside a tag list" + }, + "theme.blog.post.readMore": { + "message": "続きを読む", + "description": "The label used in blog post item excerpts to link to full blog posts" + }, + "theme.blog.post.paginator.navAriaLabel": { + "message": "ブログ投稿ページのナビゲーション", + "description": "The ARIA label for the blog posts pagination" + }, + "theme.blog.post.paginator.newerPost": { + "message": "次の投稿", + "description": "The blog post button label to navigate to the newer/previous post" + }, + "theme.blog.post.paginator.olderPost": { + "message": "前の投稿", + "description": "The blog post button label to navigate to the older/next post" + }, + "theme.tags.tagsPageTitle": { + "message": "タグ", + "description": "The title of the tag list page" + }, + "theme.blog.post.plurals": { + "message": "{count} 投稿|{count} 投稿|{count} 投稿", + "description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.blog.tagTitle": { + "message": "{nPosts} のタグ \"{tagName}\"", + "description": "The title of the page for a blog tag" + }, + "theme.tags.tagsPageLink": { + "message": "すべてのタグを見る", + "description": "The label of the link targeting the tag list page" + }, + "theme.CodeBlock.copyButtonAriaLabel": { + "message": "クリップボードにコピー", + "description": "The ARIA label for copy code blocks button" + }, + "theme.CodeBlock.copied": { + "message": "コピーしました", + "description": "The copied button label on code blocks" + }, + "theme.CodeBlock.copy": { + "message": "コピー", + "description": "The copy button label on code blocks" + }, + "theme.docs.sidebar.expandButtonTitle": { + "message": "サイドバーを展開", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.docs.sidebar.expandButtonAriaLabel": { + "message": "サイドバーを展開", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.docs.paginator.navAriaLabel": { + "message": "ドキュメントページのナビゲーション", + "description": "The ARIA label for the docs pagination" + }, + "theme.docs.paginator.previous": { + "message": "前のページ", + "description": "The label used to navigate to the previous doc" + }, + "theme.docs.paginator.next": { + "message": "次のページ", + "description": "The label used to navigate to the next doc" + }, + "theme.docs.sidebar.collapseButtonTitle": { + "message": "サイドバーを折りたたむ", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.collapseButtonAriaLabel": { + "message": "サイドバーを折りたたむ", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.responsiveCloseButtonLabel": { + "message": "メニューを閉じる", + "description": "The ARIA label for close button of mobile doc sidebar" + }, + "theme.docs.sidebar.responsiveOpenButtonLabel": { + "message": "メニューを開く", + "description": "The ARIA label for open button of mobile doc sidebar" + }, + "theme.docs.versions.unreleasedVersionLabel": { + "message": "これは{siteTitle} {versionLabel}の将来のバージョンのドキュメントです。", + "description": "The label used to tell the user that he's browsing an unreleased doc version" + }, + "theme.docs.versions.unmaintainedVersionLabel": { + "message": "これは{siteTitle}の{versionLabel}バージョンのドキュメントで、現在はサポートされていません。", + "description": "The label used to tell the user that he's browsing an unmaintained doc version" + }, + "theme.docs.versions.latestVersionSuggestionLabel": { + "message": "最新のドキュメントは{latestVersionLink}({versionLabel})にあります。", + "description": "The label userd to tell the user that he's browsing an unmaintained doc version" + }, + "theme.docs.versions.latestVersionLinkLabel": { + "message": "最新バージョン", + "description": "The label used for the latest version suggestion link label" + }, + "theme.common.editThisPage": { + "message": "このページを編集", + "description": "The link label to edit the current page" + }, + "theme.common.headingLinkTitle": { + "message": "この見出しへの直接リンク", + "description": "Title for link to heading" + }, + "theme.lastUpdated.atDate": { + "message": " {date}", + "description": "The words used to describe on which date a page has been last updated" + }, + "theme.lastUpdated.byUser": { + "message": " {user}", + "description": "The words used to describe by who the page has been last updated" + }, + "theme.lastUpdated.lastUpdatedAtBy": { + "message": "最終更新{atDate}{byUser}", + "description": "The sentence used to display when a page has been last updated, and by who" + }, + "theme.common.skipToMainContent": { + "message": "メインコンテンツにスキップ", + "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation" + } +} diff --git a/i18n/ja/docusaurus-plugin-content-docs/community/index.mdx b/i18n/ja/docusaurus-plugin-content-docs/community/index.mdx new file mode 100644 index 0000000000..713ae1b412 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/community/index.mdx @@ -0,0 +1,40 @@ +--- +hide_table_of_contents: true +--- + +# 💫 コミュニティ + +

+コミュニティリソースと追加資料 +

+ +## メイン {#main} + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { StarOutlined, SearchOutlined, TeamOutlined, VerifiedOutlined } from "@ant-design/icons"; + + + + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/community/team.mdx b/i18n/ja/docusaurus-plugin-content-docs/community/team.mdx new file mode 100644 index 0000000000..718318bd8c --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/community/team.mdx @@ -0,0 +1,18 @@ +--- +sidebar_class_name: sidebar-item--wip +sidebar_position: 2 +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# チーム + + + +## コアチーム + +### チャンピオンズ {#champions} + +## コントリビューター {#contributors} + +## 会社 {#companies} \ No newline at end of file diff --git a/i18n/ja/docusaurus-plugin-content-docs/current.json b/i18n/ja/docusaurus-plugin-content-docs/current.json new file mode 100644 index 0000000000..7585aa991d --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current.json @@ -0,0 +1,54 @@ +{ + "version.label": { + "message": "v2.1", + "description": "現在のバージョンのラベル" + }, + "sidebar.getstartedSidebar.category.🚀 Get Started": { + "message": "🚀 はじめに", + "description": "サイドバーの「はじめに」カテゴリのラベル" + }, + "sidebar.guidesSidebar.category.🎯 Guides": { + "message": "🎯 ガイド", + "description": "サイドバーの「ガイド」カテゴリのラベル" + }, + "sidebar.referenceSidebar.category.📚 Reference": { + "message": "📚 参考書", + "description": "サイドバーの「参考書」カテゴリのラベル" + }, + "sidebar.aboutSidebar.category.🍰 About": { + "message": "🍰 メソッドについて", + "description": "サイドバーの「メソッドについて」カテゴリのラベル" + }, + "sidebar.aboutSidebar.category.Understanding": { + "message": "理解", + "description": "サイドバーの「理解」カテゴリのラベル" + }, + "sidebar.aboutSidebar.category.Promote": { + "message": "推進", + "description": "サイドバーの「推進」カテゴリのラベル" + }, + "sidebar.guidesSidebar.category.Examples": { + "message": "実装例", + "description": "サイドバーの「例」カテゴリのラベル" + }, + "sidebar.guidesSidebar.category.Migration": { + "message": "移行", + "description": "サイドバーの「移行」カテゴリのラベル" + }, + "sidebar.guidesSidebar.category.Tech": { + "message": "技術", + "description": "サイドバーの「技術」カテゴリのラベル" + }, + "sidebar.referenceSidebar.category.Units": { + "message": "ユニット", + "description": "サイドバーの「ユニット」カテゴリのラベル" + }, + "sidebar.referenceSidebar.category.Isolation": { + "message": "分離", + "description": "サイドバーの「分離」カテゴリのラベル" + }, + "sidebar.referenceSidebar.category.Layer": { + "message": "レイヤー", + "description": "サイドバーの「レイヤー」カテゴリのラベル" + } +} diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/alternatives.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/about/alternatives.mdx new file mode 100644 index 0000000000..4074abbdf7 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/alternatives.mdx @@ -0,0 +1,103 @@ +--- +sidebar_class_name: sidebar-item--wip +sidebar_position: 3 +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# 代替案 + + + +## ビッグボールオブマッド + + + +- [(記事) DDD - Big Ball of mud](https://thedomaindrivendesign.io/big-ball-of-mud/) + + +## スマート&ダムコンポーネント + + + +- [(記事) Dan Abramov - Presentational and Container Components (TLDR: 非推奨)](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) + + +## デザイン原則 + + + +## DDD + + + +## 参照 {#see-also} + +- [(記事) DDD, Hexagonal, Onion, Clean, CQRS, … How I put it all together](https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/) + +## クリーンアーキテクチャ + + + +- [(記事) DDD, Hexagonal, Onion, Clean, CQRS, … How I put it all together](https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/) + +## フレームワーク + + + +- [(記事) FSDの作成理由 (フレームワークに関する断片)](/docs/about/motivation) + + +## Atomic Design + +### これは何か? + +アトミックデザインでは、責任の範囲が標準化された層に分かれています。 + +アトミックデザインは**5つの層**に分かれます(上から下へ)。 + +1. `pages` - FSDの`pages`層と同様の目的を持つ。 +2. `templates` - コンテンツに依存しないページの構造を定義するコンポーネント。 +3. `organisms` - ビジネスロジックを持つ分子から構成されるモジュール。 +4. `molecules` - 通常、ビジネスロジックを持たないより複雑なコンポーネント。 +5. `atoms` - ビジネスロジックを持たないUIコンポーネント。 + +同じ層のモジュールは、FSDのように下の層のモジュールとだけ相互作用しています。 +つまり、分子(molecule)は原子(atom)から構築され、生命体(organism)は分子から、テンプレート(template)は生命体から、ページ(page)はテンプレートから構築されます。 +また、アトミックデザインはモジュール内での**公開API**の使用を前提としています。 + +### フロントエンドでの適用性 +アトミックデザインはプロジェクトで比較的よく見られます。アトミックデザインは、開発者の間というより、ウェブデザイナーの間で人気です。ウェブデザイナーは、スケーラブルでメンテナンスしやすいデザインを作成するためにアトミックデザインをよく使用しています。 +開発では、アトミックデザインは他のアーキテクチャ設計方法論と混合されることがよくあります。 + +しかし、アトミックデザインはUIコンポーネントとその構成に焦点を当てているため、 +アーキテクチャ内でビジネスロジックを実装する問題が発生してしまいます。 + +問題は、アトミックデザインがビジネスロジックのための明確な責任レベルを提供していないため、 +ビジネスロジックがさまざまなコンポーネントやレベルに分散され、メンテナンスやテストが複雑になることです。 +ビジネスロジックは曖昧になり、責任の明確な分離が困難になり、コードがモジュール化されず再利用可能でなくなります。 + +### FSDとの統合 +FSDの文脈では、アトミックデザインのいくつかの要素を使用して柔軟でスケーラブルなUIコンポーネントを作成することができます。 `atoms`と`molecules`の層は、FSDの`shared/ui`に実装でき、基本的なUI要素の再利用とメンテナンスを簡素化しています。 + +```sh +├── shared +│   ├── ui  +│   │   ├── atoms +│   │   ├── molecules +│   ... +``` + +FSDとアトミックデザインの比較は、両方の設計方法論がモジュール性と再利用性を目指していることを示していますが、 +異なる側面に焦点を当てています。アトミックデザインは視覚的コンポーネントとその構成に焦点を当てています。 +FSDはアプリケーションの機能を独立したモジュールに分割し、それらの相互関係に焦点を当てています。 + +- [Atomic Design](https://atomicdesign.bradfrost.com/table-of-contents/) +- [(動画) Atomic Design: What is it and why is it important?](https://youtu.be/Yi-A20x2dcA) + +## Feature Driven + + + +- [(講演) Feature Driven Architecture - Oleg Isonen](https://youtu.be/BWAeYuWFHhs) +- [Feature Driven-Short specification (from the point of view of FSD)](https://github.com/feature-sliced/documentation/tree/rc/feature-driven) diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/index.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/about/index.mdx new file mode 100644 index 0000000000..3b573e934b --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/index.mdx @@ -0,0 +1,44 @@ +--- +hide_table_of_contents: true +pagination_prev: reference/index +--- + +# 🍰 FSDについて + +背景指向 + +

+FSD設計方法論、チーム、コミュニティ、そして発展の歴史に関する一般情報 +

+ +## 主な内容 {#main} + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { StarOutlined, TrophyOutlined, BulbOutlined, TeamOutlined } from "@ant-design/icons"; + + + + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/mission.md b/i18n/ja/docusaurus-plugin-content-docs/current/about/mission.md new file mode 100644 index 0000000000..b99c74375c --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/mission.md @@ -0,0 +1,50 @@ +--- +sidebar_position: 1 +--- + +# ミッション + +ここでは、私たちがFSD方法論を開発する際に従う方法論適用の制限と目標について説明します。 + +- 私たちは、目標をイデオロギーとシンプルさのバランスとして考えている +- 私たちは、すべての人に適した銀の弾丸を作ることはできない + +**それでも、FSD方法論が広範な開発者にとって近く、アクセス可能であることを望んでいます。** + +## 目標 {#goals} + +### 幅広い開発者に対する直感的な明確さ {#intuitive-clarity-for-a-wide-range-of-developers} + +FSD方法論は、プロジェクトチームの大部分にとってアクセス可能であるべきです。 + +*なぜなら、将来のすべてのツールがあっても、FSD方法論を理解できるのは経験豊富なシニアやリーダーだけでは不十分だからである* + +### 日常的な問題の解決 {#solving-everyday-problems} + +FSD方法論には、プロジェクト開発における日常的な問題の理由と解決策が示されるべきです。 + +また、開発者が*コミュニティーの経験に基づいた*アプローチを使用できるようにし、長年のアーキテクチャや開発の問題を回避できるようにするには、**FSD方法論はこれに関連するツール(CLI、リンター)を提供することも必要です。** + + +> *@sergeysova: 想像してみてください。開発者が方法論に基づいてコードを書いているとき、開発者の直面している問題は10倍少なく発生しています。それは他の人々が多くの問題の解決策を考え出したから、可能になったのです。* + +## 制限 {#limitations} + +私たちは*自分たちの見解を押し付けたくありません*が、同時に*多くの開発者の習慣が日々の開発の妨げになっていることを理解しています。* + +すべての開発者にはシステム設計と開発経験レベルが異なるため、**次のことを理解することが重要です。** + +- FSD方法論は、すべての開発者にとって、同時に非常にシンプルで、非常に明確にするのは不可能 + > *@sergeysova: 一部の概念は、問題に直面し、解決に数年を費やさない限り、直感的に理解することはできない。* + > + > - *数学の例 — グラフ理論。* + > - *物理の例 — 量子力学。* + > - *プログラミングの例 — アプリケーションのアーキテクチャ。* + > +- シンプルさ、拡張性は、実現可能であって望ましい + +## 参照 {#see-also} + +- [アーキテクチャの問題][refs-architecture--problems] + +[refs-architecture--problems]: /docs/about/understanding/architecture#problems diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/motivation.md b/i18n/ja/docusaurus-plugin-content-docs/current/about/motivation.md new file mode 100644 index 0000000000..7394004603 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/motivation.md @@ -0,0 +1,133 @@ +--- +sidebar_position: 2 +--- + +# モチベーション + +**Feature-Sliced Design**の主なアイデアは、さまざまな開発者の経験を議論し、研究結果を統合することに基づいて、複雑で発展するプロジェクトの開発を容易にし、開発コストを削減することです。 + +明らかに、これは銀の弾丸ではなく、当然ながら、FSDには独自の[適用範囲の限界][refs-mission]があります。 + +## 既存の解決策が不足している理由 {#intuitive-clarity-for-a-wide-range-of-developers} + +> 通常、次のような議論があります。 +> +> - *「SOLID」、「KISS」、「YAGNI」、「DDD」、「GRASP」、「DRY」など、すでに確立された設計原則があるのに、なぜ新しい方法論が必要なのか?」* +> - *「プロジェクトのすべての問題は、良いドキュメント、テスト、確立されたプロセスで解決できる」* +> - *「すべての問題は、すべての開発者が上記のすべてに従えば解決される」* +> - *「すでにすべてが考案されているから、あなたはそれを利用できないだけだ」* +> - *\{FRAMEWORK_NAME\}を使えば、すべてが解決される」* + +### 原則だけでは不十分 {#principles-alone-are-not-enough} + +**良いアーキテクチャを設計するためには、原則の存在だけでは不十分です。** + +すべての人が原則を完全に理解しているわけではありません。正しく原則を理解し、適用できる人はさらに少ないです。 + +*設計原則はあまりにも一般的であり、「スケーラブルで柔軟なアプリケーションの構造とアーキテクチャをどのように設計するか?」という具体的な質問に対する答えを提供していません。* + +### プロセスは常に機能するわけではない {#processes-dont-always-work} + +*ドキュメント/テスト/プロセス*を使用するのは、確かに良いことですが、残念ながら、それに多くのコストをかけても、**アーキテクチャの問題や新しい人をプロジェクトに導入する問題を解決することは常にできるわけではありません。** + +- ドキュメントは、しばしば膨大で古くなってしまうので、各開発者のプロジェクトへの参加時間はあまり短縮されない。 +- 誰もが同じようにアーキテクチャを理解しているかを常に監視することは、膨大なリソースを必要とする。 +- bus-factorも忘れないようにしましょう。 + +### 既存のフレームワークはどこでも適用できるわけではない {#existing-frameworks-cannot-be-applied-everywhere} + +- 既存の解決策は通常、高い参入障壁があるため、新しい開発者を見つけるのが難しい。 +- ほとんどの場合、技術の選択はプロジェクトの深刻な問題が発生する前に決定されているため、**技術に依存せずに**、すでにあるもので作業をすることができなければならない。 + +> Q: *「私のプロジェクトでは`React/Vue/Redux/Effector/Mobx/{YOUR_TECH}`を使っていますが、エンティティの構造とそれらの間の関係をどのように構築すればよいでしょうか?」* + +### 結果として {#as-a-result} + +「雪の結晶」のようにユニークなプロジェクトが得られ、それぞれが従業員の長期的な関与を必要とし、他のプロジェクトではほとんど適用できない知識を必要とします。 + +> @sergeysova: *これは、現在のフロントエンド開発の状況そのものであり、各リーダーがさまざまなアーキテクチャやプロジェクトの構造を考案しているが、これらの構造が時間の試練に耐えるかどうかは不明であり、最終的にはリーダー以外の人がプロジェクトを発展させることができるのは最大で2人であり、新しい開発者を再び入れる必要がある。* + +## 開発者にとっての方法論の必要性 {#why-do-developers-need-the-methodology} + +### アーキテクチャの問題ではなくビジネス機能に集中するため {#focus-on-business-features-not-on-architecture-problems} + +FSDは、スケーラブルで柔軟なアーキテクチャの設計にかかるリソースを節約し、開発者の注意を主要な機能開発に向けることを可能にしています。同時に、プロジェクトごとにアーキテクチャの解決策も標準化されます。 + +*別の問題は、FSDがコミュニティの信頼を得る必要があることです。そうすれば、開発者は自分のプロジェクトの問題を解決する際に、与えられた時間内にFSDを理解し、信頼することができます。* + +### 経験に基づく解決策 {#an-experience-proven-solution} + +FSDは、*複雑なビジネスロジックの設計における経験に基づく解決策を目指す開発者*を対象としています。 + +*ただし、FSDは、全体としてベストプラクティスのセット、または特定の問題やケースに関する記事一覧です。したがって、開発や設計の問題に直面する他の開発者にも役立てます。* + +### プロジェクトの健康 {#project-health} + +FSDは、*プロジェクトの問題を事前に解決し、追跡することを可能にし、膨大なリソースを必要としません。* + +**技術的負債は通常、時間とともに蓄積され、その解決の責任はリーダーとチームの両方にあります。** + +FSDは、*スケーリングやプロジェクトの発展における潜在的な問題を事前に警告することを可能にしています。* + +## ビジネスにとってのFSD方法論の必要性 {#why-does-a-business-need-a-methodology} + +### 迅速なオンボーディング {#fast-onboarding} + +FSDを使用すると、**すでにこのアプローチに慣れている人をプロジェクトに雇うことができ、再教育する必要がありません。** + +*人々はより早くプロジェクトに慣れ、貢献し始め、次のプロジェクトのイテレーションで人を見つけるための追加の保証が得られます。* + +### 経験に基づく解決策 {#an-experience-proven-solution-1} + +ビジネスは、プロジェクトの発展における大部分の問題を解決するフレームワーク/解決策を得たいと考えています。FSDにより、ビジネスは*システムの開発中に発生するほとんどの問題に対する解決策を得ることができます。* + +### プロジェクトのさまざまな段階への適用性 {#applicability-for-different-stages-of-the-project} + +FSDは、*プロジェクトのサポートと発展の段階でも、MVPの段階でもプロジェクトに利益をもたらすことができます。* + +はい、MVPでは通常、機能が重要であり、将来のアーキテクチャは重要ではありません。しかし、限られた時間の中で、方法論のベストプラクティスを知っていることで、少ないコストで済むことができ、MVPバージョンのシステムを設計する際に合理的な妥協を見つけることができます(無計画に機能を追加するよりも)。 + +*テストについても同じことが言えます。* + +## 私たちの方法論が必要ない場合 {#when-is-our-methodology-not-needed} + +- プロジェクトが短期間しか存続しない場合 +- プロジェクトがサポートされるアーキテクチャを必要としない場合 +- ビジネスがコードベースと機能の提供速度の関連性を認識しない場合 +- ビジネスができるだけ早く注文を完了することを重視し、さらなるサポートを求めない場合 + +### ビジネスの規模 {#business-size} + +- **小規模ビジネス** - 通常、迅速で即効性のある解決策を必要とします。ビジネスは、成長する(少なくとも中規模に達する)と、顧客が継続的にサービスなどを利用するためには、開発される解決策の品質と安定性に時間をかける必要があることを理解し始めます。 +- **中規模ビジネス** - 通常、開発のすべての問題を理解しており、たとえ機能をできるだけ早くリリースしたい場合でも、品質の改善、リファクタリング、テスト(そしてもちろん、拡張可能なアーキテクチャ)に時間をかけます。 +- **大規模ビジネス** - 通常、すでに広範なオーディエンスを持ち、従業員の数も多く、独自のプラクティスのセットを持っているため、他のアプローチを採用するアイデアはあまり浮かびません。 + +## 目標 {#plans} + +主要な目標の大部分は[ここに記載されています][refs-mission--goals]が、今後のFSD方法論に対する私たちの期待についても話しておく必要があります。 + +### 経験の統合 {#combining-experience} + +現在、私たちは`core-team`のさまざまな経験を統合し、実践に基づいた方法論を得ることを目指しています。 + +もちろん、最終的にはAngular 3.0のようなものを得るかもしれませんが、ここで最も重要なのは、**複雑なシステムのアーキテクチャ設計の問題を探求することです。** + +*そして、現在のFSD方法論のバージョンに対して不満があることは確かですが、私たちはコミュニティの経験も考慮しながら、共通の努力で統一的かつ最適な解決策に到達したいと考えています。* + +### 仕様外の生活 {#life-outside-the-specification} + +すべてがうまくいけば、FSDは仕様やツールキットに限定されることはありません。 + +- 講演や記事があるかもしれない。 +- FSD方法論に従って書かれたプロジェクトの他の技術への移行のための`CODE_MODEs`があるかもしれない。 +- 最終的には、大規模な技術的解決策のメンテイナーに到達できるかもしれない。 + - *特にReactに関しては、他のフレームワークと比較して、これは主な問題である。なぜなら、特定の問題を解決する方法を示さないからである。* + +## 参照 {#see-also} + +- [方法論の使命について:目標と制限][refs-mission] +- [プロジェクトにおける知識の種類][refs-knowledge] + +[refs-mission]: /docs/about/mission +[refs-mission--goals]: /docs/about/mission#goals +[refs-knowledge]: /docs/about/understanding/knowledge-types \ No newline at end of file diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/promote/_category_.yaml b/i18n/ja/docusaurus-plugin-content-docs/current/about/promote/_category_.yaml new file mode 100644 index 0000000000..abce1d4fe5 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/promote/_category_.yaml @@ -0,0 +1,3 @@ +label: プロモート +position: 11 +collapsed: true diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/promote/for-company.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/about/promote/for-company.mdx new file mode 100644 index 0000000000..b8efbc68ca --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/promote/for-company.mdx @@ -0,0 +1,10 @@ +--- +sidebar_position: 4 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# 会社での推進 + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/promote/for-team.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/about/promote/for-team.mdx new file mode 100644 index 0000000000..c484972621 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/promote/for-team.mdx @@ -0,0 +1,10 @@ +--- +sidebar_position: 3 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# チームでの推進 + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/promote/integration.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/about/promote/integration.mdx new file mode 100644 index 0000000000..b12c4006cf --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/promote/integration.mdx @@ -0,0 +1,16 @@ +--- +sidebar_position: 1 +--- + +# 統合の側面 + +**利点**: +- [概要](/docs/get-started/overview#advantages) +- コードレビュー +- オンボーディング + +**欠点**: +- メンタル的な複雑さ +- 高い参入障壁 +- 「レイヤー地獄」 +- 機能ベースのアプローチにおける典型的な問題 \ No newline at end of file diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/promote/partial-application.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/about/promote/partial-application.mdx new file mode 100644 index 0000000000..a4fc1d8221 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/promote/partial-application.mdx @@ -0,0 +1,10 @@ +--- +sidebar_position: 2 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# 部分的な適用 + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/_category_.yaml b/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/_category_.yaml new file mode 100644 index 0000000000..5690b49cfd --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/_category_.yaml @@ -0,0 +1,2 @@ +label: 理解 +position: 3 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/abstractions.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/abstractions.mdx new file mode 100644 index 0000000000..5e7de6f46b --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/abstractions.mdx @@ -0,0 +1,18 @@ +--- +sidebar_position: 6 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# 抽象化 + + + +## 漏れのある抽象化の法則 {#the-law-of-leaky-abstractions} + +## なぜこんなに多くの抽象化があるのか {#why-are-there-so-many-abstractions} + +> 抽象化はプロジェクトの複雑さに対処するのに役立ちます。問題は、これらの抽象化がこのプロジェクトに特有のものになるのか、それともフロントエンドの特性に基づいて一般的な抽象化を導き出そうとするのかということです。 + +> アーキテクチャとアプリケーション全体は元々複雑であり、問題はその複雑さをどのように分配し、記述するかだけです。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/architecture.md b/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/architecture.md new file mode 100644 index 0000000000..2153d05788 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/architecture.md @@ -0,0 +1,94 @@ +--- +sidebar_position: 1 +--- + +# アーキテクチャ + +## 問題 {#problems} + +通常、アーキテクチャについての議論は、プロジェクトの開発が何らかの問題で停滞しているときに持ち上がります。 + +### バスファクターとオンボーディング + +プロジェクトとそのアーキテクチャを理解しているのは限られた人々だけです。 + +**例:** + +- *「新しい人を開発に加えるのが難しい」* +- *「問題があるたびに、各自が異なる回避策を持っている」* +- *「この大きなモノリスの中で何が起こっているのか理解できない」* + +### 暗黙的かつ制御されていない結果 {#implicit-and-uncontrolled-consequences} + +開発やリファクタリングにおいて多くの暗黙的な副作用が発生してしまいます(「すべてがすべてに依存している」)。 + +**例:** + +- *「フィーチャーが他のフィーチャーをインポートしている」* +- *「あるページのストアを更新したら、別のページのフィーチャーが壊れた」* +- *「ロジックがアプリ全体に散らばっていて、どこが始まりでどこが終わりかわからない」* + +### 制御されていないロジックの再利用 {#uncontrolled-reuse-of-logic} + +既存のロジックを再利用したり修正したりするのが難しいです。 + +通常、2つの極端なケースがあります。 + +- 各モジュールごとにロジックを完全にゼロから書く(既存のコードベースに重複が生じる可能性がある) +- すべてのモジュールを`shared`フォルダーに移動し、大きなモジュールの「ごみ屋敷」を作る(ほとんどが一箇所でしか使用されない) + +**例:** + +- *「プロジェクトに同じビジネスロジックの複数の実装があって、毎日その影響を受けている」* +- *「プロジェクトには6つの異なるボタン/ポップアップコンポーネントがある」* +- *「ヘルパー関数の「ごみ屋敷」」* + +## 要件 {#requirements} + +したがって、理想的なアーキテクチャに対する要求を提示するのは、理にかなっています。 + +:::note + +「簡単」と言われるところでは、「広範な開発者にとって相対的に簡単である」という意味です。なぜなら、[すべての人にとって完璧な解決策を作ることはできないからです](/docs/about/mission#limitations)。 + +::: + +### 明示性 + +- チームがプロジェクトとそのアーキテクチャを**簡単に習得し、説明できる**ようにする必要がある +- 構造はプロジェクトの**ビジネス価値**を反映するべきである +- **副作用と抽象化間の関係**が明示されるべきである +- ユニークな実装を妨げず、**ロジックの重複を簡単に発見できる**ようにする必要がある +- プロジェクト全体に**ロジックが散らばってはいけない** +- 良好なアーキテクチャのために**あまりにも多くの異なる抽象化やルールが存在してはならない** + +### 制御 + +- 良好なアーキテクチャは**課題の解決や機能の導入を加速する**べきである +- プロジェクトの開発を**制御**できる必要がある +- コードを**拡張、修正、削除するのが簡単である**べきである +- 機能の**分解と孤立性**が守られるべきである +- システムの各コンポーネントは**簡単に交換可能で削除可能**であるべきである + - *未来を予測することはできないから、[変更に最適化する必要はない][ext-kof-not-modification]* + - *既存のコンテキストに基づいて、[削除に最適化する方が良い][ext-kof-but-removing]* + +### 適応性 + +- 良好なアーキテクチャは、ほとんどのプロジェクトに適用可能であるべきである + - *既存のインフラソリューションと共に* + - *どの発展段階でも* +- フレームワークやプラットフォームに依存してはいけない +- プロジェクトとチームを簡単にスケールアップでき、開発の並行処理が可能である必要がある +- 変化する要件や状況に適応するのが簡単であるべきである + +## 関連情報 {#see-also} + +- [(React Berlin Talk) Oleg Isonen - Feature Driven Architecture][ext-kof] +- [(記事) プロジェクトのモジュール化について][ext-medium] +- [(記事) 関心の分離と機能に基づく構造について][ext-ryanlanciaux] + +[ext-kof-not-modification]: https://youtu.be/BWAeYuWFHhs?t=1631 +[ext-kof-but-removing]: https://youtu.be/BWAeYuWFHhs?t=1666 +[ext-kof]: https://youtu.be/BWAeYuWFHhs +[ext-medium]: https://alexmngn.medium.com/why-react-developers-should-modularize-their-applications-d26d381854c1 +[ext-ryanlanciaux]: https://ryanlanciaux.com/blog/2017/08/20/a-feature-based-approach-to-react-development/ diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/knowledge-types.md b/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/knowledge-types.md new file mode 100644 index 0000000000..74e5b2dff9 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/knowledge-types.md @@ -0,0 +1,23 @@ +--- +sidebar_position: 3 +sidebar_label: 知識の種類 +--- + +# プロジェクトにおける知識の種類 + +どのプロジェクトにも以下の「知識の種類」が存在します。 + +* **基礎知識** + 時間とともにあまり変わらない知識。例えばアルゴリズム、コンピュータサイエンス、プログラミング言語やそのAPIの動作メカニズムなど。 + +* **技術スタック** + プロジェクトで使用される技術的解決策のセットに関する知識。プログラミング言語、フレームワーク、ライブラリを含む。 + +* **プロジェクト知識** + 現在のプロジェクトに特有であり、他のプロジェクトでは役に立たない知識。この知識は新しいチームメンバーが効果的にプロジェクトに貢献するために必要である。 + +:::note + +**Feature-Sliced Design**は「プロジェクト知識」への依存を減らし、より多くの責任を引き受け、新しいチームメンバーのオンボーディングを容易にすることを目指している。 + +::: diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/naming.md b/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/naming.md new file mode 100644 index 0000000000..dcb4e36686 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/naming.md @@ -0,0 +1,36 @@ +--- +sidebar_position: 4 +--- + +# ネーミング + +異なる開発者は異なる経験と背景を持っているため、同じエンティティが異なる名前で呼ばれることによって、チーム内で誤解が生じる可能性があります。例えば + +- 表示用のコンポーネントは「ui」、「components」、「ui-kit」、「views」などと呼ばれることがある。 +- アプリケーション全体で再利用されるコードは「core」、「shared」、「app」などと呼ばれることがある。 +- ビジネスロジックのコードは「store」、「model」、「state」などと呼ばれることがある。 + +## Feature-Sliced Designにおけるネーミング {#naming-in-fsd} + +FSD設計方法論では、以下のような特定の用語が使用されます。 + +- 「app」、「process」、「page」、「feature」、「entity」、「shared」といった層の名前、 +- 「ui」、「model」、「lib」、「api」、「config」といったセグメントの名前。 + +これらの用語を遵守することは、チームメンバーやプロジェクトに新しく参加する開発者の混乱を防ぐために非常に重要です。標準的な名称を使用することは、コミュニティに助けを求める際にも役立ちます。 + +## 名前衝突 {#when-can-naming-interfere} + +名前衝突は、FSD設計方法論で使用される用語がビジネスで使用される用語と重なっている場合に発生する可能性があります。例えば + +- `FSD#process`と、アプリケーション内でモデル化されたプロセス、 +- `FSD#page`と、マガジンのページ、 +- `FSD#model`と、自動車モデル。 + +開発者がコード内で「プロセス」という言葉を見た場合、どのプロセスが指されているのかを理解するのに余分な時間を費やすことになってしまいます。このような**衝突は開発プロセスを妨げる場合があります**。 + +プロジェクトの用語集にFSD特有の用語が含まれている場合、これらの用語をチームや技術的に関心のない関係者と議論する際には特に注意が必要です。 + +チームとの効果的なコミュニケーションのためには、用語の前に「FSD」という略語を付けることをお勧めします。例えば、プロセスについて話すときは、「このプロセスをFSDのフィーチャー層に置くことができる」と言うことができます。 + +逆に、技術的でない関係者とのコミュニケーションでは、FSDの用語の使用を制限し、コードベースの内部構造に言及しない方が良いでしょう。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/needs-driven.md b/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/needs-driven.md new file mode 100644 index 0000000000..c8f60760ed --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/needs-driven.md @@ -0,0 +1,153 @@ +--- +sidebar_position: 2 +--- + +# ニーズの理解と課題の定義について + +:::note TL;DR + +— _新しい機能が解決する目標を明確にできませんか?それとも、問題はタスク自体が明確にされていないことにありますか?**FSDは、問題の定義や目標を引き出す手助けをすることも目的にしています。**_ + +— _プロジェクトは静的に存在するわけではなく、要件や機能は常に変化しています。プロジェクトは最初の要望のスナップショットのみに基づいて設計されているため、時間が経つにつれて、コードは混沌としてしまいます。**良いアーキテクチャの課題の一つは、変化する開発条件に対応できるようにすることです。**_ + +::: + + + + +## なぜ? {#why} + +エンティティの明確な名前を選び、その構成要素を理解するためには、**コードが解決する課題を明確に理解する必要があります。** + +> _@sergeysova: 開発中、私たちは各エンティティや機能に、その意図や意味を明確に反映する名前を付けようとしている。_ + +_課題を理解しなければ、重要なケースをカバーする正しいテストを書くことも、ユーザーに適切な場所でエラーを表示することもできず、単純にユーザーのフローを中断することにもなってしまいます。_ + +## どのような課題についての話? {#what-tasks-are-we-talking-about} + +フロントエンドは、エンドユーザーのためのアプリケーションやインターフェースを開発しているため、私たち開発者はその消費者の課題を解決しています。 + +私たちのもとに誰かが来るとき、**その人は自分の悩みを解決したり、ニーズを満たしたりしてほしいのです。** + +_マネージャーとアナリストの仕事はこのニーズを定義することです。開発者の仕事はウェブ開発の特性(接続の喪失、バックエンドのエラー、タイプミス、カーソルや指の操作ミス)を考慮して、そのニーズを実現することです。_ + +**ユーザーが持ってきた目的こそが、開発者の課題です。** + +> _小さな解決された課題が、Feature-Sliced Designの設計方法論におけるfeatureではあります。プロジェクト課題のスコープを小さな目標に分割する必要があります。_ + +## これが開発にどのように影響するのか? {#how-does-this-affect-development} + +### 課題(タスク)の分解 {#task-decomposition} + +開発者がタスクを実装し始めるとき、理解の簡素化とコードメンテナンスのために、**タスクを段階に分けます**。 + +- まずは、上位レベルのエンティティに分けて、それを実装する +- 次に、これらのエンティティをより小さく分ける +- そしてさらに続ける + +_エンティティを分解する過程で、開発者はそれに明確に意図を反映した名前を付ける必要があり、エンティティの一覧表を読む際にそのコードが解決する課題を理解するのに役立ちます。_ + +この際、ユーザーの悩みを軽減したり、ニーズを実現したりするユーザーへの手助けをすることを忘れないように心がけましょう。 + +### 課題の本質を理解する {#understanding-the-essence-of-the-task} + +エンティティに明確な名前を付けるためには、**開発者はその目的について十分に理解する必要があります。** + +- エンティティをどのように使用するつもりなのか +- エンティティがユーザーの課題のどの部分を実現するのか、他にどこでこのエンティティを使用できるのか +- などなど + +結論を出すのは難しくありません。**開発者がFSD枠内でのエンティティの名前を考えているとき、コードを書く前に不十分に定義された課題を見つけることができます。** + +> どのようにエンティティに名前を付けるのか、もしそのエンティティが解決できる課題をよく理解していない場合、そもそもどうやって課題をエンティティに分解できるのか? + +## どのように定義するのか? {#how-to-formulate-it} + +機能によって解決される課題を定義するためには、その課題自体を理解する必要があります。これはプロジェクトマネージャーやアナリストの責任範囲です。 + +_FSD設計方法論は、開発者に対して、プロダクトマネージャーが注目すべき課題を示唆することしかできません。_ + +> _@sergeysova: フロントエンドは、まず情報を表示するものである。どのコンポーネントも、まず何かを表示する。したがって、「ユーザーに何かを見せる」というタスクには実用的な価値がない。_ + +基本的なニーズや悩みを見つけたら、**あなたのプロダクトやサービスがどのようにユーザーの目標をサポートすることができるのかを考えます。** + +タスクトラッカーの新しいタスクは、ビジネスの課題を解決することを目的としており、ビジネスは同時にユーザーの課題を解決し、利益を上げようとしています。したがって、説明文に明記されていなくても、各タスクには特定の目標が含まれています。 + +開発者は、特定のタスクが追求する目的をはっきりと把握しておくべきです。しかし、すべての会社がプロセスを完璧に構築できるわけではありません。 + +## その利益は何か? {#and-what-is-the-benefit} + +では、プロセス全体を最初から最後まで見てみましょう。 + +### 1. ユーザーの課題を理解する {#1-understanding-user-tasks} + +開発者は、ユーザーの悩みとビジネスがその悩みをどのように解決するかを理解すると、ウェブ開発の特性によりビジネスには提供できない解決策を提案することができます。 + +> しかしもちろん、これは開発者が自分の行動や目的に無関心でない限り機能します。さもなければ、そもそもなぜFSDやアプローチが必要なのか?という疑問になってしまいます。 + +### 2. 構造化と整理 {#2-structuring-and-ordering} + +課題を理解することで、**頭とコードの中で明確な構造が得られます。** + +### 3. 機能とその構成要素を理解する {#3-understanding-the-feature-and-its-components} + +**1つの機能は、ユーザーにとって1つの有用な機能性です。** + +- 1つの機能に複数の機能性が実装されている場合、それは**境界の侵害**である。 +- 機能は分割不可能で成長可能になる場合があるが、**それは悪くない。** +- **悪い**のは、機能が「ユーザーにとってのビジネス価値は何か?」という質問に答えられないことである。 + - 「オフィスの地図」という機能は存在できない。 + - しかし、「地図上の会議室の予約」、「従業員の検索」、「作業場所の変更」は**存在可能である。** + +> _@sergeysova: 機能には、直接的にその機能を実現するコードだけが含まれるべきであり、余計な詳細や内部の解決策は含まれないべきである(理想的には)。_ + +> *機能のコードを開くと、**そのタスクに関連するものだけが見える**。それ以上は必要ない。* + +### 4. Profit {#4-profit} + +ビジネスはその方針を極めて稀にしか根本的に変えないため、**ビジネスのタスクをフロントエンドアプリケーションのコードに反映することは非常に大きな利点になれます。** + +_そうすれば、チームの新しいメンバーにそのコードが何をするのか、なぜ追加されたのかを説明する必要がなくなります。**すべては、すでにコードに反映されているビジネスのタスクを通じて説明されているからです。**_ + +> [Domain Driven Developmentにおける「ビジネス言語」][ext-ubiq-lang] + +--- + +## 現実に戻りましょう {#back-to-reality} + +ビジネスプロセスが明確な意味を持ち、設計段階で良い名前が付けられている場合、_その理解と論理をコードに移すことはそれほど問題ではありません。_ + +しかし実際には、タスクや機能性は通常「過度に」反復的に進化し、(または)デザインを考える時間がありません。 + +**その結果、今日、機能は意味を持っていますが、1か月後にその機能を拡張する際には、プロジェクト全体を再構築する必要があるかもしれません。** + +> *開発者は未来の要望を考慮しながら2〜3ステップ先を考えようとしますが、自分の経験に行き詰まってしまいます。* + +> _経験豊富なエンジニアは通常、すぐに10ステップ先を見て、どの機能を分割するか、どの機能を他の機能と統合するかを理解しています。_ + +> _しかし、経験上遭遇したことのないタスクが来ることもあり、その場合、どのように機能を適切に分解し、将来的に悲惨な結果を最小限に抑えるかを理解する手段がありません。_ + +## FSDの役割 {#the-role-of-methodology} + +**FSDは、開発者の問題を解決する手助けをし、ユーザーの問題を解決するのを容易にしています。** + +開発者のためだけに課題を解決することはありません。 + +しかし、開発者が自分の課題を解決するためには、**ユーザーの課題を理解する必要があります**。逆は成り立ちません。 + +### FSDに対する要件 {#methodology-requirements} + +明らかになるのは、**Feature-Sliced Design**のために少なくとも2つの要件を定義する必要があるということです。 + +1. FSD方法論は**フィーチャー、プロセス、エンティティを作成する方法**を説明する必要がある。 + - つまり、それらの間でコードをどのように分割するかを明確に説明する必要がある。これによりこれらのエンティティの命名も仕様に組み込まれるべきである。 +2. FSD方法論は、アーキテクチャがプロジェクトの変わりゆく要件にスムーズに対応できるようにするべきである。 + +## 関連情報 {#see-also} + +- [(記事) "How to better organize your applications"][ext-medium] + +[refs-arch--adaptability]: architecture#adaptability + +[ext-medium]: https://alexmngn.medium.com/how-to-better-organize-your-react-applications-2fd3ea1920f1 +[ext-ubiq-lang]: https://thedomaindrivendesign.io/developing-the-ubiquitous-language \ No newline at end of file diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/signals.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/signals.mdx new file mode 100644 index 0000000000..c1757ce6be --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/about/understanding/signals.mdx @@ -0,0 +1,10 @@ +--- +sidebar_position: 5 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# アーキテクチャのシグナル + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/branding.md b/i18n/ja/docusaurus-plugin-content-docs/current/branding.md new file mode 100644 index 0000000000..d35589c907 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/branding.md @@ -0,0 +1,81 @@ +import useBaseUrl from "@docusaurus/useBaseUrl"; + +# ブランドガイドライン + +FSDのビジュアルアイデンティティは、そのコアコンセプトである `Layered`、`Sliced self-contained parts`、`Parts & Compose`、`Segmented` に基づいています。しかし、私たちはFSDの哲学を反映し、簡単に認識できる美しいアイデンティティを目指しています。 + +**FSDのアイデンティティを「そのまま」変更せずに、私たちのアセットを使って快適にご利用ください。** このブランドガイドは、FSDのアイデンティティを正しく使用する手助けをします。 + +:::caution 互換性 + +FSDは以前、[別のレガシーアイデンティティ](https://drive.google.com/drive/folders/11Y-3qZ_C9jOFoW2UbSp11YasOhw4yBdl?usp=sharing)を持っていました。古いデザインは、FSDの主要なコンセプトを反映していませんでした。また、これは粗いドラフトとして作成され、更新されるべきものでした。 + +ブランドの互換性と長期的な使用のために、私たちは2021年から2022年にかけて慎重にリブランディングに取り組みました。**FSDのアイデンティティを使用する際に自信を持てるように🍰** + +*古いアイデンティティではなく、最新のアイデンティティを使用してください!* + +::: + +## 名前 {#title} + +- ✅ **正しい:** `Feature-Sliced Design`、`FSD` +- ❌ **間違っている:** `Feature-Sliced`、`Feature Sliced`、`FeatureSliced`、`feature-sliced`、`feature sliced`、`FS` + +## 絵文字 {#emojii} + +ケーキのイメージ 🍰 はFSDの主要なコンセプトをよく反映しているため、私たちのブランド絵文字として選ばれました。 + +> 例: *"🍰 フロントエンド用ののアーキテクチャデザイン設計方法論"* + +## ロゴとカラーパレット {#logo--palettte} + +FSDには異なるコンテキスト用のいくつかのロゴバリエーションがありますが、**primary**の使用が推奨されます。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
テーマロゴ (Ctrl/Cmd + Clickでダウンロード)使用法
primary
(#29BEDC, #517AED)
logo-primaryほとんどの場合に推奨されます
flat
(#3193FF)
logo-flat単色コンテキスト用
monochrome
(#FFF)
logo-monochrome白黒コンテキスト用
square
(#3193FF)
logo-square正方形サイズ用
+ +## バナーとスキーム {#banners--schemes} + +banner-primary +banner-monochrome + +## ソーシャルプレビュー + +作業中... + +## プレゼンテーションテンプレート {#presentation-template} + +作業中... + +## 参照 {#see-also} + +- [ディスカッション (github)](https://github.com/feature-sliced/documentation/discussions/399) +- [リブランディングの歴史と参考資料 (figma)](https://www.figma.com/file/RPphccpoeasVB0lMpZwPVR/FSD-Brand?node-id=0%3A1) diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/get-started/cheatsheet.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/get-started/cheatsheet.mdx new file mode 100644 index 0000000000..d5d502f9b4 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/get-started/cheatsheet.mdx @@ -0,0 +1,28 @@ +--- +# sidebar_position: 3 +unlisted: true +--- + +# 分解のチートシート + +インターフェースをレイヤーに分割する際の参考書として使用してください。以下にPDFバージョンもあり、印刷して枕の下に置いておくことができます。 + +## レイヤーの選択 {#choosing-a-layer} + +[PDFをダウンロード](/files/choosing-a-layer-en.pdf) + +![レイヤーの定義と自己チェックの質問](/img/choosing-a-layer-en.jpg) + +## 例 {#examples} + +### ツイート + +![分解されたツイート](/img/decompose-twitter.png) + +### GitHub + +![分解されたGitHub](/img/decompose-github.jpg) + +## 参照 {#see-also} + +- [(記事) ロジックの分解におけるさまざまなアプローチ](https://www.pluralsight.com/resources/blog/guides/how-to-organize-your-react--redux-codebase) diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/get-started/faq.md b/i18n/ja/docusaurus-plugin-content-docs/current/get-started/faq.md new file mode 100644 index 0000000000..8d24ada9e0 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/get-started/faq.md @@ -0,0 +1,68 @@ +--- +sidebar_position: 20 +pagination_next: guides/index +--- + +# FAQ + +:::info + +質問は、[Discordコミュニティ][discord]、[GitHub Discussions][github-discussions]、および[Telegramチャット][telegram]で聞くことができます。 + +::: + +### ツールキットやリンターはありますか? {#is-there-a-toolkit-or-a-linter} + +はい!CLI または IDE を通じてプロジェクトのアーキテクチャと [フォルダー ジェネレーター][ext-tools] をチェックするための [Steiger][ext-steiger] というリンターがあります。 + +### ページのレイアウト/テンプレートはどこに保存すればよいですか? {#where-to-store-the-layouttemplate-of-pages} + +シンプルなレイアウトテンプレートが必要な場合は、`shared/ui`に保存できます。より上層のレイヤーを使用する必要がある場合、いくつかのオプションがあります。 + +- レイアウトが本当に必要ですか?レイアウトが数行で構成されている場合、各ページにコードを重複させる方が合理的です。 +- レイアウトが必要な場合は、個別のウィジェットやページとして保存し、App層のルーター設定にそれらを組み合わせることができます。ネストされたルーティングも一つのオプションです。 + +### フィーチャーとエンティティの違いは何ですか? {#what-is-the-difference-between-feature-and-entity} + +エンティティはアプリケーションが扱う現実世界の概念です。フィーチャーはユーザーに実際の価値を提供するインタラクションであり、ユーザーがエンティティで行いたいことです。 + +詳細および例については、参考書セクションの[スライスについてのページ][reference-entities]を参照してください。 + +### ページ/フィーチャー/エンティティを相互に埋め込むことはできますか? {#can-i-embed-pagesfeaturesentities-into-each-other} + +はい、しかし、この埋め込みはより上層のレイヤーで行う必要があります。例えば、ウィジェット内で両方のフィーチャーをインポートし、プロップス/子要素として一方のフィーチャーを他方に挿入することができます。 + +一方のフィーチャーを他方のフィーチャーからインポートすることはできません。これは[**レイヤーのインポートルール**][import-rule-layers]で禁止されています。 + +### Atomic Designはどうですか? {#what-about-atomic-design} + +現在、アトミックデザインをFeature-Sliced Designと一緒に使用することを義務付けていませんが、禁止もしていません。 + +アトミックデザインは、モジュールの`ui`セグメントにうまく適用できます。 + +### FSDに関する有用なリソース/記事などはありますか? {#are-there-any-useful-resourcesarticlesetc-about-fsd} + +はい! https://github.com/feature-sliced/awesome + +### なぜFeature-Sliced Designが必要なのですか? {#why-do-i-need-feature-sliced-design} + +FSDは、プロジェクトの主要な価値を提供するコンポーネントの観点から、あなたとあなたのチームが迅速にプロジェクトを把握するのに役立ちます。標準化されたアーキテクチャは、オンボーディングを迅速化し、コード構造に関する議論を解決するのに役立ちます。FSDが作成された理由については、[モチベーション][motivation]のページを参照してください。 + +### 初心者の開発者にFSDのアーキテクチャ/設計方法論は必要ですか? {#does-a-novice-developer-need-an-architecturemethodology} + +おそらく必要です。 + +*通常、一人でプロジェクトを設計・開発する場合、すべてが順調に進みます。しかし、開発に中断が生じたり、新しい開発者がチームに加わると問題が発生します。* + +### 認証コンテキストをどのように扱えばよいですか? {#how-do-i-work-with-the-authorization-context} + +[こちら](/docs/guides/examples/auth)で回答しています。 + +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools +[import-rule-layers]: /docs/reference/layers#import-rule-on-layers +[reference-entities]: /docs/reference/layers#entities +[motivation]: /docs/about/motivation +[telegram]: https://t.me/feature_sliced +[discord]: https://discord.gg/S8MzWTUsmp +[github-discussions]: https://github.com/feature-sliced/documentation/discussions diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/get-started/index.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/get-started/index.mdx new file mode 100644 index 0000000000..75b532c1b0 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/get-started/index.mdx @@ -0,0 +1,38 @@ +--- +hide_table_of_contents: true +pagination_prev: intro +--- + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { RocketOutlined, PlaySquareOutlined, QuestionCircleOutlined } from "@ant-design/icons"; + +# 🚀 クイックスタート + +

+ようこそ!このセクションでは、Feature-Sliced Designの適用方法とその基礎知識が簡単に紹介されます。また、FSDの主な利点とその作成理由についての内容も記載されています。 +

+ + + + +{/* */} diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/get-started/overview.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/get-started/overview.mdx new file mode 100644 index 0000000000..dbefa82034 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/get-started/overview.mdx @@ -0,0 +1,137 @@ +--- +sidebar_position: 1 +--- + +# 概要 + +**Feature-Sliced Design** (FSD) とは、フロントエンドアプリケーションの設計方法論です。簡単に言えば、コードを整理するためのルールと規約の集大成です。FSDの主な目的は、ビジネス要件が絶えず変化する中で、プロジェクトをより理解しやすく、構造化されたものにすることです。 + +ルールのセットに加えて、FSDはツールチェーンでもあります。プロジェクトのアーキテクチャをチェックするための[リンター][ext-steiger]、CLIやIDEを通じた[フォルダージェネレーター][ext-tools]、および豊富な[実装例のコレクション][examples]があります。 + +## FSDは私のプロジェクトに適しているのか? {#is-it-right-for-me} + +FSDは、あらゆる規模のプロジェクトやチームに導入できます。以下の場合、あなたのプロジェクトに適しています。 + +- **フロントエンド**開発での使用(ウェブサイト、モバイル/デスクトップアプリケーションのインターフェース作成など) +- **アプリケーション**開発での使用(ライブラリ開発ではない) + +これだけです!使用するプログラミング言語、フレームワーク、状態管理ライブラリには制限がありません。尚、FSDを段階的に導入したり、モノレポで使用したり、アプリケーションをパッケージに分割し、それぞれにFSDを個別に導入することもできます! + +既存のアーキテクチャからFSDに移行することを検討している場合は、現在のアーキテクチャがチームに**支障をきたしている**かどうかを確認してください。例えば、プロジェクトが大きくなりすぎて新機能の開発が効率的に行えない場合や、多くの新しいメンバーがチームに加わることが予想される場合です。現在のアーキテクチャが正常に機能している場合、変更する必要はないかもしれません。しかし、移行を決定した場合は、[移行セクション][migration]の推奨事項を確認してください。 + +## 基本的な例 {#basic-example} + +以下は、FSDを実装したシンプルなプロジェクトです。 + +- `📁 app` +- `📁 pages` +- `📁 shared` + +これらのトップレベルのフォルダーは*レイヤー*と呼ばれます。詳しく見てみましょう。 + +- `📂 app` + - `📁 routes` + - `📁 analytics` +- `📂 pages` + - `📁 home` + - `📂 article-reader` + - `📁 ui` + - `📁 api` + - `📁 settings` +- `📂 shared` + - `📁 ui` + - `📁 api` + +`📂 pages`内のフォルダーは*スライス*と呼ばれます。スライスはドメイン(この場合はページ)ごとにレイヤーを分割します。 + +`📂 app`、`📂 shared`、および`📂 pages/article-reader`内のフォルダーは*セグメント*と呼ばれ、スライス(またはレイヤー)を技術的な目的に応じて分割します。 + +## 概念 {#concepts} + +レイヤー、スライス、セグメントは、以下の図に示されるように階層を形成します。 + +
+ ![FSDの概念の階層、以下に説明](/img/visual_schema.jpg) + +
+

上の図には、左から右に「レイヤー」、「スライス」、「セグメント」とラベル付けされた3つの列があります。

+

「レイヤー」列には、上から下に「app」、「processes」、「pages」、「widgets」、「features」、「entities」、「shared」とラベル付けされた7つの区分があります。「processes」区分は取り消し線が引かれています。「entities」区分は2番目の列「スライス」と接続されていて、2番目の列が「entities」の内容であることを示しています。

+

「スライス」列には、上から下に「user」、「post」、「comment」とラベル付けされた3つの区分があります。「post」区分は「セグメント」列と同様に接続されていて、「post」の内容であることを示しています。

+

「セグメント」列には、上から下に「ui」、「model」、「api」とラベル付けされた3つの区分があります。

+
+
+ +### レイヤー {#layers} + +レイヤーはすべてのFSDプロジェクトで標準化されています。すべてのレイヤーを使用する必要はありませんが、ネーミングは重要です。現在、7つのレイヤーが存在しています(上から下へ)。 + +1. App*(アップ) — アプリケーションの起動に必要なすべてのもの(ルーティング、エントリーポイント、グローバルスタイル、プロバイダーなど) +2. Processes(プロセス、非推奨) — 複雑なページ間のシナリオ +3. Pages(ページ) — ページ全体、またはネストされたルーティングの場合、ページの大部分 +4. Widgets(ウィジェット) — 大きな自己完結型の機能部分、またはインターフェースの大部分。通常はユーザーシナリオ全体を実装する +5. Features(フィーチャー) — プロダクト機能の再利用可能な実装、つまりユーザーにビジネス価値をもたらすアクション +6. Entities(エンティティ) — プロジェクトが扱うビジネスエンティティ、例えば`user`や`product` +7. Shared*(シェアード) — 再利用可能なコード。特にプロジェクト/ビジネスの詳細から切り離されたもの + +_* — App層とShared層のレイヤーは他のレイヤーとは異なり、スライスを持たず、直接セグメントで構成されています。_ + +レイヤーの特徴は、レイヤーのモジュールは、下層のレイヤーモジュールのみを知ることができ、その結果、レイヤーが下層のレイヤーからのみモジュールをインポートできることです。 + +### スライス {#slices} + +次にスライスがあり、レイヤーをドメインごとに分割します。スライスの名前は自由に付けることができ、いくつでも作成できます。スライスは、意味的に関連するコードをグループ化することで、プロジェクト内のナビゲーションをしやすくします。 + +スライスは同じレイヤーの他のスライスを使用できないため、スライス内のコードの強い結合とスライス間の弱い結合が保証されます。 + +### セグメント {#segments} + +スライス、およびApp層とShared層のレイヤーはセグメントで構成され、セグメントはその目的に応じてコードをグループ化します。セグメントの名前は標準で固定されていませんが、最も一般的な目的のためにいくつかの共通の名前があります。 + +- `ui` — 表示に関連するすべて: UIコンポーネント、日付フォーマッター、スタイルなど +- `api` — バックエンドとのやり取り: リクエスト関数、データ型、マッパー +- `model` — データモデル: バリデーションスキーマ、インターフェース、ストレージ、ビジネスロジック +- `lib` — 他のモジュールが必要とするライブラリコード +- `config` — 設定ファイルとフィーチャーフラグ + +通常、これらのセグメントはほとんどのレイヤーに十分であるため、独自のセグメントはShared層やApp層でのみ作成されることが多いです。しかし、これは厳格なルールではありません。 + +## 利点 {#advantages} + +- **一貫性** + 構造が標準化されているため、プロジェクトがより一貫性を持ち、新しいメンバーのチームへの参加が容易になります。 + +- **変更とリファクタリングへの耐性** + レイヤーのモジュールは、同じレイヤーや上層レイヤーの他のモジュールを使用できないため、アプリケーションの他の部分に予期しない影響を与えることなく、分離された変更を加えることができます。 + +- **ロジックの再利用制御** + レベルに応じて、コードを非常に再利用可能にすることも、非常にローカルにすることもできます。 + これにより、**DRY**原則と実用性のバランスが保たれます。 + +- **ビジネスとユーザーのニーズに焦点を当てる** + アプリケーションはビジネスドメインに分割され、命名にはビジネス用語の使用が奨励されるため、プロジェクトの他の無関係な部分に完全に精通することなく、プロダクトで有用な作業を行うことができます。 + +## 段階的な導入 {#incremental-adoption} + +既存のコードベースをFSDに移行したい場合は、以下の戦略をお勧めします。私たち自身の移行経験から、この方法は非常に効果的であることが分かりました。 + +1. App層とShared層のレイヤーを徐々に形成し、基盤を作る。 + +2. 既存のすべてのインターフェースコードをウィジェットとページに分散させる。FSDのルールに違反する依存関係があっても良い。 + +3. インポートのルール違反を徐々に修正しながら、エンティティやフィーチャーを抽出する。 + +リファクタリング中に新しい大きなエンティティを追加することや、部分的なリファクタリングは避けることをお勧めします。 + +## 次のステップ {#next-steps} + +- **FSDの考え方を理解したい?** [チュートリアル][tutorial]を読んでください。 +- **例を見て学びたい?** [実装例セクション][examples]にたくさんあります。 +- **質問がある?** [Discordチャンネル][ext-discord]にアクセスして、コミュニティに質問してください。 + +[tutorial]: /docs/get-started/tutorial +[examples]: /examples +[migration]: /docs/guides/migration/from-custom +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools +[ext-discord]: https://discord.com/invite/S8MzWTUsmp + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/get-started/tutorial.md b/i18n/ja/docusaurus-plugin-content-docs/current/get-started/tutorial.md new file mode 100644 index 0000000000..8c18e275f1 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/get-started/tutorial.md @@ -0,0 +1,2264 @@ +--- +sidebar_position: 2 +--- + +# チュートリアル + +## 第1章 紙の上で + +このガイドでは、Real World Appとしても知られるアプリケーションを見ていきます。Conduitは、[Medium](https://medium.com/)の簡略版であり、ブログ記事を読み書きし、他の人の記事にコメントすることができます。 + +![Conduitのホームページ](/img/tutorial/realworld-feed-anonymous.jpg) + +これはかなり小さなアプリケーションなので、過度に分解することなく開発を進めます。おそらく、アプリケーション全体は3つの層に収まります: **App層**、**Pages層**、**Shared層**。もしそうでなければ、進行に応じて追加の層を導入しましょう。準備はいいですか? + +### ページの列挙から始める + +上のスクリーンショットを見てみると、少なくとも次のページがあると推測できます。 + +- ホーム(記事のフィード) +- ログインと登録 +- 記事の閲覧 +- 記事の編集 +- ユーザープロフィールの閲覧 +- プロフィールの編集(設定) + +これらの各ページは、*Pages*層の個別*スライス*になります。概要のセクションから思い出してください。スライスは単に層内のフォルダーであり、層は事前に定義された名前のフォルダーだけです。例えば、`pages`のようです。 + +したがって、私たちのPagesフォルダーは次のようになります。 + +``` +📂 pages/ + 📁 feed/ (フィード) + 📁 sign-in/ (ログイン/登録) + 📁 article-read/ (記事の閲覧) + 📁 article-edit/ (記事の編集) + 📁 profile/ (プロフィール) + 📁 settings/ (設定) +``` + +Feature-Sliced Designの特徴は、ページが互いに依存できないことです。つまり、1つのページが他のページのコードをインポートすることはできません。これは**層のインポートルール**によって禁じられています。 + +*スライス内のモジュール(ファイル)は、下層にあるスライスのみをインポートできる。* + +この場合、ページはスライスであるため、そのページ内のモジュール(ファイル)は、他のページではなく、下層からのみコードをインポートできます。 + +### フィードを詳しく見てみると + +
+ ![匿名訪問者の視点](/img/tutorial/realworld-feed-anonymous.jpg) +
+ _匿名訪問者の視点_ +
+
+ +
+ ![認証されたユーザーの視点](/img/tutorial/realworld-feed-authenticated.jpg) +
+ _認証されたユーザーの視点_ +
+
+ +フィードページには3つの動的領域があります。 + +1. 認証状態を示すログインリンク +2. フィードをフィルタリングするタグ一覧 +3. 1つ、または2つのフィード記事。各記事にはいいねボタンがある + +ログインリンクは、すべてのページで共通のヘッダーの一部であるため、一旦保留にしましょう。 + +#### タグ一覧 + +タグ一覧を作成するには、すべての利用可能なタグを取得し、各タグをチップ([chip](https://m3.material.io/components/chips/overview))として表示し、選択されたタグをクライアント側のストレージに保存する必要があります。これらの操作は、「APIとのインタラクション」、「ユーザーインターフェース」、「データストレージ」のカテゴリに関連しています。Feature-Sliced Designでは、コードは目的に応じて*セグメント*に分けられます。セグメントはスライス内のフォルダーであり、目的を説明する任意の名前を持つことができます。いくつかの目的は非常に一般的であるため、いくつかの一般的な名前があります。 + +- 📂 `api/` バックエンドとのインタラクション +- 📂 `ui/` 表示と外観を担当するコード +- 📂 `model/` データとビジネスロジックのストレージ +- 📂 `config/` フィーチャーフラグ、環境変数、その他の設定形式 + +タグを取得するコードは`api`に、タグコンポーネントは`ui`に、ストレージとのインタラクションは`model`に配置します。 + +#### 記事 + +同じ論理に従って、記事のフィードを同じ3つのセグメントに分けることができます。 + +- 📂 `api/`: ページごとの記事一覧を取得したり、いいねを残したりする +- 📂 `ui/`: + - タグを選択したときに追加のタブを表示できるタブ一覧 + - 個別の記事 + - ページネーション +- 📂 `model/`: クライアントのストレージに保存された読み込まれた投稿と現在のページ(必要に応じて) + +### 共通コードの再利用 + +アプリケーションのページは通常、目的によって非常に異なりますが、全体で共通するものもあります。例えば、デザイン言語に対応するUIキットや、すべてが特定の認証メソッドを介してREST APIを通じて行われるというバックエンドにおける取り決めです。スライスは隔離されている必要があるため、コードの再利用は下層の**Shared層**を介して行われます。 + +Shared層は他の層とは異なり、スライスではなくセグメントを含むため、Shared層はレイヤーとスライスのハイブリッドです。 + +通常、Shared層内のコードは事前に作成されず、開発の過程で抽出されます。なぜなら、どの部分のコードが実際に再利用されるかが開発中に明らかになるからです。それでも、Shared層にどんなコードを保持するかを念頭に置いておくことは重要です。 + +- 📂 `ui/` — ビジネスロジックなしのUIキット。例えば、ボタン、ダイアログ、フォームフィールド。 +- 📂 `api/` — バックエンドへのリクエスト用の便利なラッパー(例えば、ウェブの場合は、`fetch()`のラッパー) +- 📂 `config/` — 環境変数の処理 +- 📂 `i18n/` — 多言語対応の設定 +- 📂 `router/` — ルーティングのプリミティブと定数 + +これらはShared層内のセグメントの例に過ぎません。これらのいずれかを省略したり、自分自身のセグメントを作成したりできます。新しいセグメントを作成する際に覚えておくべき唯一のことは、セグメントの名前は内容の本質(何)ではなく、目的(なぜ)を説明するものでなければなりません。`components`、`hooks`、`modals`のような名前は使用しない方が良いです。なぜなら、これらはファイルが本質的に何を含んでいるかを説明するものであり、コードが書かれた目的を説明するものではないからです。このようなネーミングの結果、チームは必要なものを見つけるためにフォルダーを掘り下げなければならず、さらに無関係なコードが隣接しているため、リファクタリング時にアプリケーションの大部分に影響を与え、レビューやテストが難しくなってしまいます。 + +### 公開APIを定義する + +Feature-Sliced Designの文脈において、*公開API*という用語は、スライス、またはセグメントが、プロジェクト内の他のモジュールがインポートできるものを宣言することを意味します。例えば、JavaScriptでは、他のファイルからオブジェクトを再エクスポートする`index.js`ファイルがこれに該当します。これにより、外部との契約(つまり、公開API)が変更されない限り、スライス内でのリファクタリングを自由にできます。 + +Shared層にはスライスがないため、通常、セグメントレベルで公開API(インデックス)を定義する方が便利です。そうすることで、Shared層からのインポートは自然に目的に応じて整理されます。他のレイヤーにはスライスがあるため、通常は1つのインデックスをスライスに定義し、スライス自身が内部のセグメントのセットを制御する方が実用的です。なぜなら、他のレイヤーは通常、エクスポートがはるかに少なく、リファクタリングが頻繁に行われるからです。 + +私たちのスライス/セグメントは次のようになるでしょう。 + +``` +📂 pages/ + 📂 feed/ + 📄 index + 📂 sign-in/ + 📄 index + 📂 article-read/ + 📄 index + 📁 … +📂 shared/ + 📂 ui/ + 📄 index + 📂 api/ + 📄 index + 📁 … +``` + +`pages/feed`や`shared/ui`のようなフォルダー内にあるものは、これらのフォルダーにのみ知られており、これらのフォルダーの内容に関する保証はありません。 + +### 大きな再利用可能なUIブロック + +以前、再利用可能なアプリケーションのヘッダーのところに戻りますが、各ページでヘッダーを再構築するのは非効率的なので、再利用します。再利用するコードには、すでにShared層がありますが、Shared層内の大きなUIブロックには注意が必要です。Shared層は上層のレイヤーについて何も知らないべきです。 + +Shared層とPages層の間には、Entities層、Features層、Widgets層の3つの他のレイヤーがあります。他のプロジェクトでは、これらのレイヤーに大きな再利用可能なブロックで使用したいものがあるかもしれません。その場合、そのブロックをShared層に置くことはできません。なぜなら、上層からインポートしなければならず、それは禁止されているからです。ここでWidgets層が役立ちます。これはShared層、Entities層、Features層の上に位置しているため、すべてを使用できます。 + +私たちの場合、ヘッダーは非常にシンプルです。静的なロゴと上部ナビゲーションしかありません。ナビゲーションはAPIに現在のユーザーが認証されているかどうかを尋ねる必要がありますが、これは`api`セグメントからの単純なインポートで解決できます。したがって、ヘッダーはShared層に残します。 + +### フォームページに着目 + +記事を読むだけでなく、編集することもできるページも見てみましょう。例えば、記事編集者のページです。 + +![Conduitの記事編集者](/img/tutorial/realworld-editor-authenticated.jpg) + +見た目は単純ですが、私たちがまだ調べていないアプリケーション開発のいくつかの側面を含んでいます。フォームのバリデーション、エラー状態、データの永続的な保存のようなものです。 + +このページを作成するには、Shared層からいくつかのフィールドとボタンを取り、それらをこのページの`ui`セグメントにあるフォームにまとめます。次に、`api`セグメントで、バックエンドに記事を作成するための変更リクエストを定義します。 + +リクエストを送信する前にリクエストをバリデーションするために、バリデーションスキーマが必要です。バリデーションスキーマはデータモデルであるため、`model`セグメントに入れるのがちょうど良いです。そこでエラーメッセージを生成し、`ui`セグメントの別のコンポーネントを使用してエラーメッセージを表示します。 + +UXを向上させるために、ブラウザを閉じたときに偶発的なデータ損失を防ぐために、入力データを永続的に保存することもできます。これも`model`セグメントに適しています。 + +### まとめ + +いくつかのページに着目し、アプリケーションの基本的な構造を決めることができました。 + +1. Shared層 + 1. `ui` には再利用可能なUIキットが含まれる + 2. `api` にはバックエンドとのインタラクションのためのプリミティブが含まれる + 3. 残りはコードを書く過程で整理する +2. Pages層 — 各ページに対して個別のスライスを作成 + 1. `ui` にはページ自体とその構成要素が含まれる + 2. `api` には`shared/api`を使用するデータ取得のためのより専用的な関数が含まれる + 3. `model` には表示するデータのクライアントストレージなどが含まれる + +これでこのアプリケーションを作りましょう! + +## 第2章 コードの中で + +計画ができたので、実現していきましょう。Reactと[Remix](https://remix.run/)を使用します。 + +このプロジェクトにはすでにテンプレートが用意されているので、GitHubからクローンして作成を始めてください。 + +[https://github.com/feature-sliced/tutorial-conduit/tree/clean](https://github.com/feature-sliced/tutorial-conduit/tree/clean) + +依存関係を`npm install`でインストールし、`npm run dev`でサーバーを起動します。[http://localhost:3000](http://localhost:3000/)を開くと、空のアプリケーションが表示されます。 + +### ページごとに整理する + +すべてのページのために空のコンポーネントを作成することから始めましょう。ターミナルで次のコマンドを実行します。 + +```bash +npx fsd pages feed sign-in article-read article-edit profile settings --segments ui +``` + +これにより、`pages/feed/ui/`のようなフォルダーと、各ページのインデックスファイル`pages/feed/index.ts`が作成されます。 + +### フィードページを接続する + +アプリケーションのルート(`/`)をフィードページに接続しましょう。`pages/feed/ui`に`FeedPage.tsx`コンポーネントを作成し、次の内容を入れます。 + +```tsx title="pages/feed/ui/FeedPage.tsx" +export function FeedPage() { + return ( +
+
+
+

conduit

+

知識を共有する場

+
+
+
+ ); +} +``` + +次に、このコンポーネントをフィードページの公開APIに再エクスポートします。 + +```tsx title="pages/feed/index.ts" +export { FeedPage } from "./ui/FeedPage"; +``` + +次に、ルートに接続します。Remixでは、ルーティングはファイルに基づいていて、ルートファイルは`app/routes`フォルダーにあります。これはFeature-Sliced Designとよく組み合っています。 + +`app/routes/_index.tsx`で`FeedPage`コンポーネントを使用します。 + +```tsx title="app/routes/_index.tsx" +import type { MetaFunction } from "@remix-run/node"; +import { FeedPage } from "pages/feed"; + +export const meta: MetaFunction = () => { + return [{ title: "Conduit" }]; +}; + +export default FeedPage; +``` + +これで、devサーバーを起動し、アプリケーションを開くと、Conduitのバナーが表示されるはずです! + +![Conduitのバナー](/img/tutorial/conduit-banner.jpg) + +### APIクライアント + +RealWorldのバックエンドと通信するために、Shared層内に便利なAPIクライアントを作成しましょう。クライアント用の`api`セグメントと、バックエンドの基本URLなどの変数用の`config`セグメントを作成します。 + +```bash +npx fsd shared --segments api config +``` + +次に、`shared/config/backend.ts`を作成します。 + +```tsx title="shared/config/backend.ts" +export const backendBaseUrl = "https://api.realworld.io/api"; +``` + +```tsx title="shared/config/index.ts" +export { backendBaseUrl } from "./backend"; +``` + +RealWorldプロジェクトは[OpenAPI仕様](https://github.com/gothinkster/realworld/blob/main/api/openapi.yml)を提供しているため、APIクライアントの型を自動的に生成できます。私たちは[`openapi-fetch`パッケージ](https://openapi-ts.pages.dev/openapi-fetch/)を使用します。このパッケージにはTypeScriptの型を自動生成するツールも含まれています。 + +次のコマンドを実行して、APIの最新の型を生成しましょう。 + +```bash +npm run generate-api-types +``` + +その結果、`shared/api/v1.d.ts`ファイルが作成されます。このファイルを使用して、`shared/api/client.ts`で型付きAPIクライアントを作成します。 + +```tsx title="shared/api/client.ts" +import createClient from "openapi-fetch"; + +import { backendBaseUrl } from "shared/config"; +import type { paths } from "./v1"; + +export const { GET, POST, PUT, DELETE } = createClient({ baseUrl: backendBaseUrl }); +``` + +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; +``` + +### フィード内の実データ + +これで、バックエンドから記事を取得し、フィードに追加できます。まず、記事プレビューコンポーネントを実装しましょう。 + +`pages/feed/ui/ArticlePreview.tsx`を作成し、次の内容を記述します。 + +```tsx title="pages/feed/ui/ArticlePreview.tsx" +export function ArticlePreview({ article }) { /* TODO */ } +``` + +私たちはTypeScriptを使っているので、型付きのArticleオブジェクトを持つと良いでしょう。生成された`v1.d.ts`を調べると、Articleオブジェクトは`components["schemas"]["Article"]`を介して利用可能であることがわかります。これでShared層内にデータモデルを持つファイルを作成し、モデルをエクスポートしましょう。 + +```tsx title="shared/api/models.ts" +import type { components } from "./v1"; + +export type Article = components["schemas"]["Article"]; +``` + +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; + +export type { Article } from "./models"; +``` + +これで、記事プレビューコンポーネントに戻って、データでマークアップを埋めることができます。次の内容をコンポーネントに追加します。 + +```tsx title="pages/feed/ui/ArticlePreview.tsx" +import { Link } from "@remix-run/react"; +import type { Article } from "shared/api"; + +interface ArticlePreviewProps { + article: Article; +} + +export function ArticlePreview({ article }: ArticlePreviewProps) { + return ( +
+
+ + + +
+ + {article.author.username} + + + {new Date(article.createdAt).toLocaleDateString(undefined, { + dateStyle: "long", + })} + +
+ +
+ +

{article.title}

+

{article.description}

+ 続きを読む... +
    + {article.tagList.map((tag) => ( +
  • + {tag} +
  • + ))} +
+ +
+ ); +} +``` + +「いいね」ボタンはまだ機能していません。それは記事の読み取りページに移動して、「いいね」機能を実装するときに修正します。 + +これで、記事を取得して、たくさんのプレビューカードを表示できます。Remixでは、データの取得は*ローダー*を使用して行われます。ローダーは、ページに必要なデータを収集するサーバー関数です。ローダーはページの代わりにAPIとやり取りをするため、`api`セグメントに配置します。 + +```tsx title="pages/feed/api/loader.ts" +import { json } from "@remix-run/node"; + +import { GET } from "shared/api"; + +export const loader = async () => { + const { data: articles, error, response } = await GET("/articles"); + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return json({ articles }); +}; +``` + +これをページに接続するには、ルートファイルから`loader`としてエクスポートする必要があります。 + +```tsx title="pages/feed/index.ts" +export { FeedPage } from "./ui/FeedPage"; +export { loader } from "./api/loader"; +``` + +```tsx title="app/routes/_index.tsx" +import type { MetaFunction } from "@remix-run/node"; +import { FeedPage } from "pages/feed"; + +export { loader } from "pages/feed"; + +export const meta: MetaFunction = () => { + return [{ title: "Conduit" }]; +}; + +export default FeedPage; +``` + +最後のステップは、これらのカードをフィードに表示することです。`FeedPage`を次のコードで更新します。 + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { useLoaderData } from "@remix-run/react"; + +import type { loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; + +export function FeedPage() { + const { articles } = useLoaderData(); + + return ( +
+
+
+

conduit

+

知識を共有する場

+
+
+ +
+
+
+ {articles.articles.map((article) => ( + + ))} +
+
+
+
+ ); +} +``` + +### タグによるフィルタリング + +タグに関しては、バックエンドから取得し、ユーザーが選択したタグを記憶する必要があります。私たちはバックエンドからの取得方法はすでに知っています。これはローダー関数からの別のリクエストです。すでにインストールされている`remix-utils`パッケージの便利な`promiseHash`関数を使用します。 + +`pages/feed/api/loader.ts`のローダーを次のコードで更新します。 + +```tsx title="pages/feed/api/loader.ts" +import { json } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +export const loader = async () => { + return json( + await promiseHash({ + articles: throwAnyErrors(GET("/articles")), + tags: throwAnyErrors(GET("/tags")), + }), + ); +}; +``` + +エラー処理を共通の`throwAnyErrors`関数に移したことに気付いたでしょうか。それはかなり使えそうに見えるので、後で再利用するかもしれません。 + +タグ一覧をインタラクティブにする必要があります。タグをクリックすると、そのタグが選択されるようにします。Remixの伝統に従い、選択されたタグのストレージとしてURLのクエリパラメータを使用します。ブラウザにストレージを任せ、私たちはより重要なことに集中しましょう。 + +`pages/feed/ui/FeedPage.tsx`を次のコードで更新します。 + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { Form, useLoaderData } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import type { loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; + +export function FeedPage() { + const { articles, tags } = useLoaderData(); + + return ( +
+
+
+

conduit

+

知識を共有する場

+
+
+ +
+
+
+ {articles.articles.map((article) => ( + + ))} +
+ +
+
+

人気のタグ

+ +
+ +
+ {tags.tags.map((tag) => ( + + ))} +
+ +
+
+
+
+
+ ); +} +``` + +次に、タグの検索パラメータをローダーで使用する必要があります。`pages/feed/api/loader.ts`の`loader`関数を次のように変更します。 + +```tsx title="pages/feed/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const selectedTag = url.searchParams.get("tag") ?? undefined; + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles", { params: { query: { tag: selectedTag } } }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); +}; +``` + +以上です。最終的に`model`セグメントは必要ありませんでした。Remixはすごいですよね。 + +### ページネーション + +同様に、ページネーションを実装できます。自分で実装してみても、以下のコードをコピーしても良いです。 + +```tsx title="pages/feed/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +/** 1ページあたりの記事の数。 */ +export const LIMIT = 20; + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const selectedTag = url.searchParams.get("tag") ?? undefined; + const page = parseInt(url.searchParams.get("page") ?? "", 10); + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles", { + params: { + query: { + tag: selectedTag, + limit: LIMIT, + offset: !Number.isNaN(page) ? page * LIMIT : undefined, + }, + }, + }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); +}; +``` + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { Form, useLoaderData, useSearchParams } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import { LIMIT, type loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; + +export function FeedPage() { + const [searchParams] = useSearchParams(); + const { articles, tags } = useLoaderData(); + const pageAmount = Math.ceil(articles.articlesCount / LIMIT); + const currentPage = parseInt(searchParams.get("page") ?? "1", 10); + + return ( +
+
+
+

conduit

+

知識を共有する場

+
+
+ +
+
+
+ {articles.articles.map((article) => ( + + ))} + +
+ +
    + {Array(pageAmount) + .fill(null) + .map((_, index) => + index + 1 === currentPage ? ( +
  • + {index + 1} +
  • + ) : ( +
  • + +
  • + ), + )} +
+ +
+ +
+
+

人気のタグ

+ +
+ +
+ {tags.tags.map((tag) => ( + + ))} +
+ +
+
+
+
+
+ ); +} +``` + +よし、これも実現しました。タグ一覧も同様に実装できますが、認証を実装するまで待ちましょう。ところで、認証についてですが! + +### 認証 + +認証には、ログイン用のページと登録用のページの2つがあります。これらは主に非常に似ているため、必要に応じてコードを再利用できるように、1つの`sign-in`セグメントに保持するのが理にかなっています。 + +`pages/sign-in`の`ui`セグメントに`RegisterPage.tsx`を作成し、次の内容を配置します。 + +```tsx title="pages/sign-in/ui/RegisterPage.tsx" +import { Form, Link, useActionData } from "@remix-run/react"; + +import type { register } from "../api/register"; + +export function RegisterPage() { + const registerData = useActionData(); + + return ( +
+
+
+
+

登録

+

+ アカウントをお持ちですか? +

+ + {registerData?.error && ( +
    + {registerData.error.errors.body.map((error) => ( +
  • {error}
  • + ))} +
+ )} + +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+ ); +} +``` + +これからは壊れたインポートを修正する必要があります。インポートが新しいセグメントにアクセスしているため、次のコマンドでそのセグメントを作成しましょう。 + +```bash +npx fsd pages sign-in -s api +``` + +ただし、登録のバックエンド部分を実装する前に、Remixのセッション処理のためのインフラコードが必要です。これは他のページでも必要になる可能性があるため、Shared層に配置します。 + +次のコードを`shared/api/auth.server.ts`に配置しましょう。このコードはRemixに特有のものであり、すべてが理解できなくても心配しないでください。単にコピーして貼り付けてください。 + +```tsx title="shared/api/auth.server.ts" +import { createCookieSessionStorage, redirect } from "@remix-run/node"; +import invariant from "tiny-invariant"; + +import type { User } from "./models"; + +invariant( + process.env.SESSION_SECRET, + "SESSION_SECRET must be set for authentication to work", +); + +const sessionStorage = createCookieSessionStorage<{ + user: User; +}>({ + cookie: { + name: "__session", + httpOnly: true, + path: "/", + sameSite: "lax", + secrets: [process.env.SESSION_SECRET], + secure: process.env.NODE_ENV === "production", + }, +}); + +export async function createUserSession({ + request, + user, + redirectTo, +}: { + request: Request; + user: User; + redirectTo: string; +}) { + const cookie = request.headers.get("Cookie"); + const session = await sessionStorage.getSession(cookie); + + session.set("user", user); + + return redirect(redirectTo, { + headers: { + "Set-Cookie": await sessionStorage.commitSession(session, { + maxAge: 60 * 60 * 24 * 7, // 7日間 + }), + }, + }); +} + +export async function getUserFromSession(request: Request) { + const cookie = request.headers.get("Cookie"); + const session = await sessionStorage.getSession(cookie); + + return session.get("user") ?? null; +} + +export async function requireUser(request: Request) { + const user = await getUserFromSession(request); + + if (user === null) { + throw redirect("/login"); + } + + return user; +} +``` + +また、`models.ts`ファイルから`User`モデルをエクスポートしてください。 + +```tsx title="shared/api/models.ts" +import type { components } from "./v1"; + +export type Article = components["schemas"]["Article"]; +export type User = components["schemas"]["User"]; +``` + +このコードが動作する前に、`SESSION_SECRET`環境変数を設定する必要があります。プロジェクトのルートに`.env`ファイルを作成し、`SESSION_SECRET=`を記述してから、適当にランダムな文字列を記入します。次のようになります。 + +```bash title=".env" +SESSION_SECRET=これをコピーしないでください +``` + +最後に、公開APIにいくつかのエクスポートを追加します。 + +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; + +export type { Article } from "./models"; + +export { createUserSession, getUserFromSession, requireUser } from "./auth.server"; +``` + +これで、RealWorldのバックエンドと通信するコードを書くことができます。これを`pages/sign-in/api`に保存します。`register.ts`ファイルを作成して、中に次のコードを配置しましょう。 + +```tsx title="pages/sign-in/api/register.ts" +import { json, type ActionFunctionArgs } from "@remix-run/node"; + +import { POST, createUserSession } from "shared/api"; + +export const register = async ({ request }: ActionFunctionArgs) => { + const formData = await request.formData(); + const username = formData.get("username")?.toString() ?? ""; + const email = formData.get("email")?.toString() ?? ""; + const password = formData.get("password")?.toString() ?? ""; + + const { data, error } = await POST("/users", { + body: { user: { email, password, username } }, + }); + + if (error) { + return json({ error }, { status: 400 }); + } else { + return createUserSession({ + request: request, + user: data.user, + redirectTo: "/", + }); + } +}; +``` + +```tsx title="pages/sign-in/index.ts" +export { RegisterPage } from './ui/RegisterPage'; +export { register } from './api/register'; +``` + +ほぼ完成です!残りの部分は、`/register`ルートにアクションとページを接続することだけです。`app/routes`で`register.tsx`を作成します。 + +```tsx title="app/routes/register.tsx" +import { RegisterPage, register } from "pages/sign-in"; + +export { register as action }; + +export default RegisterPage; +``` + +これで、[http://localhost:3000/register](http://localhost:3000/register)にアクセスすると、ユーザーを作成できます!アプリケーションの残りの部分は、まだ反応しませんが、近々対処します。 + +同様に、ログインページを実装することもできます。自分で実装してみるか、下記のコードをコピペするか、次に進みましょう。 + +```tsx title="pages/sign-in/api/sign-in.ts" +import { json, type ActionFunctionArgs } from "@remix-run/node"; + +import { POST, createUserSession } from "shared/api"; + +export const signIn = async ({ request }: ActionFunctionArgs) => { + const formData = await request.formData(); + const email = formData.get("email")?.toString() ?? ""; + const password = formData.get("password")?.toString() ?? ""; + + const { data, error } = await POST("/users/login", { + body: { user: { email, password } }, + }); + + if (error) { + return json({ error }, { status: 400 }); + } else { + return createUserSession({ + request: request, + user: data.user, + redirectTo: "/", + }); + } +}; +``` + +```tsx title="pages/sign-in/ui/SignInPage.tsx" +import { Form, Link, useActionData } from "@remix-run/react"; + +import type { signIn } from "../api/sign-in"; + +export function SignInPage() { + const signInData = useActionData(); + + return ( +
+
+
+
+

サインイン

+

+ アカウントが必要ですか? +

+ + {signInData?.error && ( +
    + {signInData.error.errors.body.map((error) => ( +
  • {error}
  • + ))} +
+ )} + +
+
+ +
+
+ +
+ +
+
+
+
+
+ ); +} +``` + +```tsx title="pages/sign-in/index.ts" +export { RegisterPage } from './ui/RegisterPage'; +export { register } from './api/register'; +export { SignInPage } from './ui/SignInPage'; +export { signIn } from './api/sign-in'; +``` + +```tsx title="app/routes/login.tsx" +import { SignInPage, signIn } from "pages/sign-in"; + +export { signIn as action }; + +export default SignInPage; +``` + +これで、ユーザーがこれらのページにアクセスできるようになりました。 + +### ヘッダー + +前章で説明されたように、アプリケーションのヘッダーは通常Widgets層、またはShared層に配置されます。ヘッダーは非常にシンプルで、すべてのビジネスロジックを外部に保持できるので、Shared層に配置しましょう。ヘッダー用のフォルダーを作成します。 + +```bash +npx fsd shared ui +``` + +次に、`shared/ui/Header.tsx`を作成し、次の内容を配置します。 + +```tsx title="shared/ui/Header.tsx" +import { useContext } from "react"; +import { Link, useLocation } from "@remix-run/react"; + +import { CurrentUser } from "../api/currentUser"; + +export function Header() { + const currentUser = useContext(CurrentUser); + const { pathname } = useLocation(); + + return ( + + ); +} +``` + +このコンポーネントを`shared/ui`からエクスポートします。 + +```tsx title="shared/ui/index.ts" +export { Header } from "./Header"; +``` + +ヘッダーで`shared/api`にあるコンテキストを使っているので、それを作成しましょう。 + +```tsx title="shared/api/currentUser.ts" +import { createContext } from "react"; + +import type { User } from "./models"; + +export const CurrentUser = createContext(null); +``` + +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; + +export type { Article } from "./models"; + +export { createUserSession, getUserFromSession, requireUser } from "./auth.server"; +export { CurrentUser } from "./currentUser"; +``` + +これで、ヘッダーをページに追加できます。すべてのページに表示されるように、ルートに追加し、アウトレット(ページがレンダリングされる場所)を`CurrentUser`のコンテキストプロバイダーで包みます。これにより、ヘッダーを含むアプリ全体が現在のユーザーオブジェクトにアクセスできるようになります。また、クッキーから現在のユーザーオブジェクトを取得するためのローダーも追加します。次のコードを`app/root.tsx`に追加しましょう。 + +```tsx title="app/root.tsx" +import { cssBundleHref } from "@remix-run/css-bundle"; +import type { LinksFunction, LoaderFunctionArgs } from "@remix-run/node"; +import { + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration, + useLoaderData, +} from "@remix-run/react"; + +import { Header } from "shared/ui"; +import { getUserFromSession, CurrentUser } from "shared/api"; + +export const links: LinksFunction = () => [ + ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []), +]; + +export const loader = ({ request }: LoaderFunctionArgs) => + getUserFromSession(request); + +export default function App() { + const user = useLoaderData(); + + return ( + + + + + + + + + + + + + +
+ + + + + + + + ); +} +``` + +最終的に、ホームページは次のようになります。 + +
+ ![ヘッダー、フィード、タグがあるConduitのフィードページ。タブはまだありません。](/img/tutorial/realworld-feed-without-tabs.jpg) + +
ヘッダー、フィード、タグがあるConduitのフィードページ。タブはまだない。
+
+ +### タブ + +これで認証状態を判断できるようになったので、タブと「いいね」ボタンをフィードページに実装しましょう。新しいフォームを作る必要がありますが、このページファイルはすでに大きすぎるので、これらのフォームを隣接するファイルに移動しましょう。`Tabs.tsx`、`PopularTags.tsx`、`Pagination.tsx`を作成し、次の内容を配置します。 + +```tsx title="pages/feed/ui/Tabs.tsx" +import { useContext } from "react"; +import { Form, useSearchParams } from "@remix-run/react"; + +import { CurrentUser } from "shared/api"; + +export function Tabs() { + const [searchParams] = useSearchParams(); + const currentUser = useContext(CurrentUser); + + return ( +
+
+
    + {currentUser !== null && ( +
  • + +
  • + )} +
  • + +
  • + {searchParams.has("tag") && ( +
  • + + {searchParams.get("tag")} + +
  • + )} +
+
+
+ ); +} +``` + +```tsx title="pages/feed/ui/PopularTags.tsx" +import { Form, useLoaderData } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import type { loader } from "../api/loader"; + +export function PopularTags() { + const { tags } = useLoaderData(); + + return ( +
+

人気のタグ

+ +
+ +
+ {tags.tags.map((tag) => ( + + ))} +
+ +
+ ); +} +``` + +```tsx title="pages/feed/ui/Pagination.tsx" +import { Form, useLoaderData, useSearchParams } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import { LIMIT, type loader } from "../api/loader"; + +export function Pagination() { + const [searchParams] = useSearchParams(); + const { articles } = useLoaderData(); + const pageAmount = Math.ceil(articles.articlesCount / LIMIT); + const currentPage = parseInt(searchParams.get("page") ?? "1", 10); + + return ( +
+ +
    + {Array(pageAmount) + .fill(null) + .map((_, index) => + index + 1 === currentPage ? ( +
  • + {index + 1} +
  • + ) : ( +
  • + +
  • + ), + )} +
+ + ); +} +``` + +これで、フィードページを大幅に簡素化できます。 + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { useLoaderData } from "@remix-run/react"; + +import type { loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; +import { Tabs } from "./Tabs"; +import { PopularTags } from "./PopularTags"; +import { Pagination } from "./Pagination"; + +export function FeedPage() { + const { articles } = useLoaderData(); + + return ( +
+
+
+

conduit

+

知識を共有する場

+
+
+ +
+
+
+ + + {articles.articles.map((article) => ( + + ))} + + +
+ +
+ +
+
+
+
+ ); +} +``` + +ローダー関数にも新しいタブを考慮する必要があります。 + +```tsx title="pages/feed/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET, requireUser } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + /* そのまま */ +} + +/** 1ページあたりの記事数。 */ +export const LIMIT = 20; + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const selectedTag = url.searchParams.get("tag") ?? undefined; + const page = parseInt(url.searchParams.get("page") ?? "", 10); + + if (url.searchParams.get("source") === "my-feed") { + const userSession = await requireUser(request); + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles/feed", { + params: { + query: { + limit: LIMIT, + offset: !Number.isNaN(page) ? page * LIMIT : undefined, + }, + }, + headers: { Authorization: `Token ${userSession.token}` }, + }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); + } + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles", { + params: { + query: { + tag: selectedTag, + limit: LIMIT, + offset: !Number.isNaN(page) ? page * LIMIT : undefined, + }, + }, + }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); +}; +``` + +フィードページを一旦置いておく前に、投稿へのいいねを処理するコードを追加しましょう。`ArticlePreview.tsx`を次のように変更します。 + + +```tsx title="pages/feed/ui/ArticlePreview.tsx" +import { Form, Link } from "@remix-run/react"; +import type { Article } from "shared/api"; + +interface ArticlePreviewProps { + article: Article; +} + +export function ArticlePreview({ article }: ArticlePreviewProps) { + return ( +
+
+ + + +
+ + {article.author.username} + + + {new Date(article.createdAt).toLocaleDateString(undefined, { + dateStyle: "long", + })} + +
+
+ +
+
+ +

{article.title}

+

{article.description}

+ もっと読む... +
    + {article.tagList.map((tag) => ( +
  • + {tag} +
  • + ))} +
+ +
+ ); +} +``` + +このコードは、`/article/:slug`にPOSTリクエストを送信し、`_action=favorite`を使用して記事をお気に入りにします。今は機能していませんが、記事リーダーの作成を始めると、これも実装します。 + +これで、フィードの作成が完了しました!やったね! + +### 記事リーダー + +まず、データが必要です。ローダーを作成しましょう。 + +```bash +npx fsd pages article-read -s api +``` + +```tsx title="pages/article-read/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import invariant from "tiny-invariant"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET, getUserFromSession } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +export const loader = async ({ request, params }: LoaderFunctionArgs) => { + invariant(params.slug, "スラッグパラメータが必要です"); + const currentUser = await getUserFromSession(request); + const authorization = currentUser + ? { Authorization: `Token ${currentUser.token}` } + : undefined; + + return json( + await promiseHash({ + article: throwAnyErrors( + GET("/articles/{slug}", { + params: { + path: { slug: params.slug }, + }, + headers: authorization, + }), + ), + comments: throwAnyErrors( + GET("/articles/{slug}/comments", { + params: { + path: { slug: params.slug }, + }, + headers: authorization, + }), + ), + }), + ); +}; +``` + +```tsx title="pages/article-read/index.ts" +export { loader } from "./api/loader"; +``` + +これで、`/article/:slug`ルートに接続できます。`article.$slug.tsx`というルートファイルを作成します。 + +```tsx title="app/routes/article.$slug.tsx" +export { loader } from "pages/article-read"; +``` + +ページ自体は、記事のタイトルとアクション、記事の本文、コメントセクションの3つの主要なブロックで構成されています。下記はページのマークアップで、特に興味深いものはありません。 + +```tsx title="pages/article-read/ui/ArticleReadPage.tsx" +import { useLoaderData } from "@remix-run/react"; + +import type { loader } from "../api/loader"; +import { ArticleMeta } from "./ArticleMeta"; +import { Comments } from "./Comments"; + +export function ArticleReadPage() { + const { article } = useLoaderData(); + + return ( +
+
+
+

{article.article.title}

+ + +
+
+ +
+
+
+

{article.article.body}

+
    + {article.article.tagList.map((tag) => ( +
  • + {tag} +
  • + ))} +
+
+
+ +
+ +
+ +
+ +
+ +
+
+
+ ); +} +``` + +興味深いのは`ArticleMeta`と`Comments`です。これらは、記事を「いいね」したり、コメントを残したりするための操作を含んでいます。これらが機能するためには、まずバックエンド部分を実装する必要があります。このページの`api`セグメントに`action.ts`ファイルを作成します。 + +```tsx title="pages/article-read/api/action.ts" +import { redirect, type ActionFunctionArgs } from "@remix-run/node"; +import { namedAction } from "remix-utils/named-action"; +import { redirectBack } from "remix-utils/redirect-back"; +import invariant from "tiny-invariant"; + +import { DELETE, POST, requireUser } from "shared/api"; + +export const action = async ({ request, params }: ActionFunctionArgs) => { + const currentUser = await requireUser(request); + + const authorization = { Authorization: `Token ${currentUser.token}` }; + + const formData = await request.formData(); + + return namedAction(formData, { + async delete() { + invariant(params.slug, "スラッグパラメータが必要です"); + await DELETE("/articles/{slug}", { + params: { path: { slug: params.slug } }, + headers: authorization, + }); + return redirect("/"); + }, + async favorite() { + invariant(params.slug, "スラッグパラメータが必要です"); + await POST("/articles/{slug}/favorite", { + params: { path: { slug: params.slug } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async unfavorite() { + invariant(params.slug, "スラッグパラメータが必要です"); + await DELETE("/articles/{slug}/favorite", { + params: { path: { slug: params.slug } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async createComment() { + invariant(params.slug, "スラッグパラメータが必要です"); + const comment = formData.get("comment"); + invariant(typeof comment === "string", "コメントパラメータが必要です"); + await POST("/articles/{slug}/comments", { + params: { path: { slug: params.slug } }, + headers: { ...authorization, "Content-Type": "application/json" }, + body: { comment: { body: comment } }, + }); + return redirectBack(request, { fallback: "/" }); + }, + async deleteComment() { + invariant(params.slug, "スラッグパラメータが必要です"); + const commentId = formData.get("id"); + invariant(typeof commentId === "string", "idパラメータが必要です"); + const commentIdNumeric = parseInt(commentId, 10); + invariant( + !Number.isNaN(commentIdNumeric), + "数値のidパラメータが必要です", + ); + await DELETE("/articles/{slug}/comments/{id}", { + params: { path: { slug: params.slug, id: commentIdNumeric } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async followAuthor() { + const authorUsername = formData.get("username"); + invariant( + typeof authorUsername === "string", + "ユーザーネームパラメータが必要です", + ); + await POST("/profiles/{username}/follow", { + params: { path: { username: authorUsername } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async unfollowAuthor() { + const authorUsername = formData.get("username"); + invariant( + typeof authorUsername === "string", + "ユーザーネームパラメータが必要です", + ); + await DELETE("/profiles/{username}/follow", { + params: { path: { username: authorUsername } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + }); +}; +``` + +これをスライスから再エクスポートし、ルートから再エクスポートします。ここにいる間に、ページ自体も接続しましょう。 + +```tsx title="pages/article-read/index.ts" +export { ArticleReadPage } from "./ui/ArticleReadPage"; +export { loader } from "./api/loader"; +export { action } from "./api/action"; +``` + +```tsx title="app/routes/article.$slug.tsx" +import { ArticleReadPage } from "pages/article-read"; + +export { loader, action } from "pages/article-read"; + +export default ArticleReadPage; +``` + +これで、記事リーダーの「いいね」ボタンはまだ実装されていないにも関わらず、フィードの「いいね」ボタンは機能し始めます!それは、フィードの「いいね」ボタンもそのルートにリクエストを送っているからです。何かを「いいね」してみてください! + +`ArticleMeta`と`Comments`は、単なるフォームです。以前にこれを行ったので、コードをコピペして先に進みましょう。 + +```tsx title="pages/article-read/ui/ArticleMeta.tsx" +import { Form, Link, useLoaderData } from "@remix-run/react"; +import { useContext } from "react"; + +import { CurrentUser } from "shared/api"; +import type { loader } from "../api/loader"; + +export function ArticleMeta() { + const currentUser = useContext(CurrentUser); + const { article } = useLoaderData(); + + return ( +
+
+ + + + +
+ + {article.article.author.username} + + {article.article.createdAt} +
+ + {article.article.author.username == currentUser?.username ? ( + <> + + 記事を編集 + +    + + + ) : ( + <> + + +    + + + )} +
+
+ ); +} +``` + +```tsx title="pages/article-read/ui/Comments.tsx" +import { useContext } from "react"; +import { Form, Link, useLoaderData } from "@remix-run/react"; + +import { CurrentUser } from "shared/api"; +import type { loader } from "../api/loader"; + +export function Comments() { + const { comments } = useLoaderData(); + const currentUser = useContext(CurrentUser); + + return ( +
+ {currentUser !== null ? ( +
+
+ +
+
+ + +
+
+ ) : ( +
+
+

+ サインイン +   または   + 登録 +   して記事にコメントを追加しましょう! +

+
+
+ )} + + {comments.comments.map((comment) => ( +
+
+

{comment.body}

+
+ +
+ + + +   + + {comment.author.username} + + {comment.createdAt} + {comment.author.username === currentUser?.username && ( + +
+ + +
+
+ )} +
+
+ ))} +
+ ); +} +``` + +これで、記事リーダーが完成しました!「著者をフォローする」ボタン、「いいね」ボタン、「コメントを残す」ボタンがすべて正常に機能するはずです。 + +
+ ![記事リーダーの画像](/img/tutorial/realworld-article-reader.jpg) + +
記事リーダーの画像
+
+ +### 記事編集 + +これは、このガイドで最後に取り上げるページです。ここで最も興味深い部分は、フォームデータを検証する方法です。 + +`article-edit/ui/ArticleEditPage.tsx`ページ自体は、非常にシンプルで、追加のロジックは他の2つのコンポーネントに含まれます。 + +```tsx title="pages/article-edit/ui/ArticleEditPage.tsx" +import { Form, useLoaderData } from "@remix-run/react"; + +import type { loader } from "../api/loader"; +import { TagsInput } from "./TagsInput"; +import { FormErrors } from "./FormErrors"; + +export function ArticleEditPage() { + const article = useLoaderData(); + + return ( +
+
+
+
+ + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+
+
+
+
+
+ ); +} +``` + +このページは、存在する記事を取得し(新しい記事を作成する場合を除く)、対応するフォームフィールドを埋めます。これは以前に見たものです。着目すべき部分は`FormErrors`で、これは検証結果を取得し、ユーザーに表示します。 + +```tsx title="pages/article-edit/ui/FormErrors.tsx" +import { useActionData } from "@remix-run/react"; +import type { action } from "../api/action"; + +export function FormErrors() { + const actionData = useActionData(); + + return actionData?.errors != null ? ( +
    + {actionData.errors.map((error) => ( +
  • {error}
  • + ))} +
+ ) : null; +} +``` + +アクションが`errors`フィールドを返し、人間に理解できるエラーメッセージの配列を表示することを想定しています。アクションには後で移ります。 + +もう1つのコンポーネントはタグ入力フィールドです。これは、選択されたタグのプレビューできる通常の入力フィールドです。特に注目すべき点はありません。 + +```tsx title="pages/article-edit/ui/TagsInput.tsx" +import { useEffect, useRef, useState } from "react"; + +export function TagsInput({ + name, + defaultValue, +}: { + name: string; + defaultValue?: Array; +}) { + const [tagListState, setTagListState] = useState(defaultValue ?? []); + + function removeTag(tag: string): void { + const newTagList = tagListState.filter((t) => t !== tag); + setTagListState(newTagList); + } + + const tagsInput = useRef(null); + useEffect(() => { + tagsInput.current && (tagsInput.current.value = tagListState.join(",")); + }, [tagListState]); + + return ( + <> + + setTagListState(e.target.value.split(",").filter(Boolean)) + } + /> +
+ {tagListState.map((tag) => ( + + + [" ", "Enter"].includes(e.key) && removeTag(tag) + } + onClick={() => removeTag(tag)} + >{" "} + {tag} + + ))} +
+ + ); +} +``` + +次に、API部分に移ります。ローダーはURLを確認し、記事へのリンクがある場合、既存の記事を編集していることを意味し、そのデータをロードする必要があります。そうでない場合は、何も返しません。このローダーを作成しましょう。 + +```tsx title="pages/article-edit/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; + +import { GET, requireUser } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +export const loader = async ({ params, request }: LoaderFunctionArgs) => { + const currentUser = await requireUser(request); + + if (!params.slug) { + return { article: null }; + } + + return throwAnyErrors( + GET("/articles/{slug}", { + params: { path: { slug: params.slug } }, + headers: { Authorization: `Token ${currentUser.token}` }, + }), + ); +}; +``` + +アクションは新しいフィールドの値を受け取り、それらをデータスキーマに通し、すべてが正しければ、既存の記事を更新するか、新しい記事を作成することによって、バックエンドに変更を保存します。 + +```tsx title="pages/article-edit/api/action.ts" +import { json, redirect, type ActionFunctionArgs } from "@remix-run/node"; + +import { POST, PUT, requireUser } from "shared/api"; +import { parseAsArticle } from "../model/parseAsArticle"; + +export const action = async ({ request, params }: ActionFunctionArgs) => { + try { + const { body, description, title, tags } = parseAsArticle( + await request.formData(), + ); + const tagList = tags?.split(",") ?? []; + + const currentUser = await requireUser(request); + const payload = { + body: { + article: { + title, + description, + body, + tagList, + }, + }, + headers: { Authorization: `Token ${currentUser.token}` }, + }; + + const { data, error } = await (params.slug + ? PUT("/articles/{slug}", { + params: { path: { slug: params.slug } }, + ...payload, + }) + : POST("/articles", payload)); + + if (error) { + return json({ errors: error }, { status: 422 }); + } + + return redirect(`/article/${data.article.slug ?? ""}`); + } catch (errors) { + return json({ errors }, { status: 400 }); + } +}; +``` + +私たちのデータスキーマは、`FormData`を解析するので、最終的に処理するエラーメッセージを投げたりするのに役立ちます。この解析関数は次のようになります。 + +```tsx title="pages/article-edit/model/parseAsArticle.ts" +export function parseAsArticle(data: FormData) { + const errors = []; + + const title = data.get("title"); + if (typeof title !== "string" || title === "") { + errors.push("記事にタイトルを付けてください"); + } + + const description = data.get("description"); + if (typeof description !== "string" || description === "") { + errors.push("この記事が何についてか説明してください"); + } + + const body = data.get("body"); + if (typeof body !== "string" || body === "") { + errors.push("記事そのものを書いてください"); + } + + const tags = data.get("tags"); + if (typeof tags !== "string") { + errors.push("タグは文字列である必要があります"); + } + + if (errors.length > 0) { + throw errors; + } + + return { title, description, body, tags: data.get("tags") ?? "" } as { + title: string; + description: string; + body: string; + tags: string; + }; +} +``` + +少し長く繰り返しが多いように見えるかもしれませんが、これはエラーメッセージを人間に理解しやすくするための代償です。Zodのようなスキーマを使用することもできますが、その場合、フロントエンドでエラーメッセージを表示する必要があります。このフォームはそのような複雑さには値しません。 + +最後のステップは、ページ、ローダー、アクションをルートに接続することです。私たちは作成と編集の両方をきれいにサポートしているので、`editor._index.tsx`と`editor.$slug.tsx`の両方から同じアクションをエクスポートできます。 + +```tsx title="pages/article-edit/index.ts" +export { ArticleEditPage } from "./ui/ArticleEditPage"; +export { loader } from "./api/loader"; +export { action } from "./api/action"; +``` + +```tsx title="app/routes/editor._index.tsx, app/routes/editor.$slug.tsx (同じ内容)" +import { ArticleEditPage } from "pages/article-edit"; + +export { loader, action } from "pages/article-edit"; + +export default ArticleEditPage; +``` + +これで完成です!ログインして新しい記事を作成してみてください。あるいは、フィールドに何も記入せず進み、バリデーションがどのように機能するかを検証してみてください。 + +
+ ![記事編集者の画像](/img/tutorial/realworld-article-editor.jpg) + +
記事編集画像
+
+ +プロフィールページや設定ページは、記事の読み取りや編集ページに非常に似ていて、読者のための宿題として残されています。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/_category_.yaml b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/_category_.yaml new file mode 100644 index 0000000000..685e78df8d --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/_category_.yaml @@ -0,0 +1,2 @@ +label: 例 +position: 1 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/auth.md b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/auth.md new file mode 100644 index 0000000000..a35f108866 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/auth.md @@ -0,0 +1,229 @@ +--- +sidebar_position: 1 +--- + +# 認証 + +一般的に、認証は以下のステップで構成されます。 + +1. ユーザーから資格情報を取得する +2. それをバックエンドに送信する +3. 認証されたリクエストを送信するためのトークンを保存する + +## ユーザーの資格情報を取得する方法 + +OAuthを通じて認証を行う場合は、OAuthプロバイダーのページへのリンクを持つログインページを作成し、[ステップ3](#how-to-store-the-token-for-authenticated-requests)に進むことができます。 + +### ログイン用の別ページ + +通常、ウェブサイトにはユーザー名とパスワードを入力するためのログイン専用ページがあります。これらのページは非常にシンプルであるため、分解する必要はありません。さらに、ログインフォームと登録フォームは外見が非常に似ているため、同じページにグループ化することもできます。ログイン/登録ページ用のスライスをPages層に作成します。 + +- 📂 pages + - 📂 login + - 📂 ui + - 📄 LoginPage.tsx + - 📄 RegisterPage.tsx + - 📄 index.ts + - その他のページ… + +ここでは、2つのコンポーネントを作成し、インデックスで両方をエクスポートしました。これらのコンポーネントは、ユーザーが資格情報を入力するためのわかりやすい要素を含むフォームを持ちます。 + +### ログイン用のダイアログボックス + +アプリケーションにどのページでも使用できるログイン用のダイアログボックスがある場合は、そのダイアログボックス用のウィジェットを作成できます。これにより、フォーム自体をあまり分解せずに、どのページでもこのダイアログボックスを再利用できます。 + +- 📂 widgets + - 📂 login-dialog + - 📂 ui + - 📄 LoginDialog.tsx + - 📄 index.ts + - その他のウィジェット… + +このガイドの残りの部分は、ログインが別ページで行われる最初のアプローチに基づいていますが、同じ原則がダイアログボックス用のウィジェットにも適用されます。 + +### クライアントバリデーション + +たまには、特に登録時に、クライアント側で検証を行い、ユーザーにエラーを迅速に通知することがあります。この場合、検証は、ログインページの`model`セグメントで行うことができます。スキーマ検証ライブラリ、例えば[Zod][ext-zod]をJS/TS用に使用し、このスキーマを`ui`セグメントに提供します。 + + +```ts title="pages/login/model/registration-schema.ts" +import { z } from "zod"; + +export const registrationData = z.object({ + email: z.string().email(), + password: z.string().min(6), + confirmPassword: z.string(), +}).refine((data) => data.password === data.confirmPassword, { + message: "パスワードが一致しません", + path: ["confirmPassword"], +}); +``` + +次に、`ui`セグメントでこのスキーマを使用してユーザー入力を検証できます。 + +```tsx title="pages/login/ui/RegisterPage.tsx" +import { registrationData } from "../model/registration-schema"; + +function validate(formData: FormData) { + const data = Object.fromEntries(formData.entries()); + try { + registrationData.parse(data); + } catch (error) { + // TODO: ユーザーにエラーメッセージを表示 + } +} + +export function RegisterPage() { + return ( +
validate(new FormData(e.target))}> + + + + + + + + +
+ ) +} +``` + +## 資格情報をバックエンドに送信する方法 + +バックエンドのログインエンドポイントにリクエストを送信する関数を作成しましょう。この関数は、コンポーネントのコード内でミューテーションライブラリ(例えば、TanStack Query)を通じて直接呼び出すことも、状態管理ライブラリの副作用として呼び出すこともできます。 + +### リクエスト関数をどこに置くか + +この関数を置く場所は2つあります: `shared/api`またはページの`api`セグメントです。 + +#### `shared/api`に + +このアプローチは、すべてのリクエスト関数を`shared/api`に配置し、エンドポイントごとにグループ化するのに適しています。この場合、ファイル構造は次のようになります。 + +- 📂 shared + - 📂 api + - 📂 endpoints + - 📄 login.ts + - その他のリクエスト関数… + - 📄 client.ts + - 📄 index.ts + +`📄 client.ts`ファイルには、リクエストを実行するためのプリミティブのラッパーが含まれています(例えば、`fetch()`)。このラッパーは、バックエンドのベースURLを知っており、必要なヘッダーを設定し、データをシリアライズします。 + +```ts title="shared/api/endpoints/login.ts" +import { POST } from "../client"; + +export function login({ email, password }: { email: string, password: string }) { + return POST("/login", { email, password }); +} +``` + +```ts title="shared/api/index.ts" +export { login } from "./endpoints/login"; +``` + +#### ページの`api`セグメントに + +すべてのリクエストを1か所に保存していない場合は、ログインページの`api`セグメントにこのリクエスト関数を配置するのが適しているかもしれません。 + +- 📂 pages + - 📂 login + - 📂 api + - 📄 login.ts + - 📂 ui + - 📄 LoginPage.tsx + - 📄 index.ts + - その他のページ… + + +```ts title="pages/login/api/login.ts" +import { POST } from "shared/api"; + +export function login({ email, password }: { email: string, password: string }) { + return POST("/login", { email, password }); +} +``` + + +この関数は、ページのインデックスから再エクスポートする必要はありません。なぜなら、恐らくこのページ内でのみ使用されるからです。 + +### 2要素認証 + +アプリケーションが2要素認証(2FA)をサポートしている場合、ユーザーを一時的なパスワードを入力するための別のページにリダイレクトする必要があるかもしれません。通常、`POST /login`リクエストは、ユーザーに2FAが有効であることを示すフラグを持つユーザーオブジェクトを返します。このフラグが設定されている場合、ユーザーを2FAページにリダイレクトします。 + +このページはログインと非常に関連しているため、Pages層の同じ`login`スライスに配置することもできます。 + +また、上で作成した`login()`に似た別のリクエスト関数が必要になります。それらをShared層にまとめるか、ログインページの`api`セグメントに配置してください。 + +## 認証されたリクエスト用のトークンを保存する方法 {#how-to-store-the-token-for-authenticated-requests} + +使用する認証スキームに関係なく、単純なログインとパスワード、OAuth、または2要素認証であっても、最終的にはトークンを取得します。以降のリクエストで自分を識別できるように、このトークンは保存する必要があります。 + +ウェブアプリケーションにおけるトークンの理想的な保存場所は**クッキー**です。クッキーはトークンの手動保存や処理を必要としません。したがって、クッキーの保存はフロントエンドアーキテクチャにほとんど労力を必要としません。フロントエンドフレームワークにサーバーサイドがある場合(例えば、[Remix][ext-remix])、クッキーのサーバーインフラは`shared/api`に保存する必要があります。[「認証」チュートリアルセクション][tutorial-authentication]には、Remixでの実装例があります。 + +ただし、時にはトークンをクッキーに保存することができない場合もあります。この場合、トークンを自分で保存しなければなりません。その際、トークンの有効期限が切れたときに更新するロジックを書く手間がかかるかもしれません。FSDの枠組み内には、トークンを保存できるいくつかの場所と、そのトークンをアプリケーションの他の部分で利用できるようにするいくつかの方法があります。 + +### Shared層に保存する + +このアプローチは、APIクライアントが`shared/api`に定義されている場合にうまく機能します。なぜなら、APIクライアントがトークンに自由にアクセスできるからです。クライアントが状態を持つようにするには、リアクティブストアを使用するか、単にモジュールレベルの変数を使用することができます。その後、`login()`/`logout()`関数内でこの状態を更新できます。 + +トークンの自動更新は、APIクライアント内のミドルウェアとして実装できます。これは、リクエストを行うたびに実行されます。例えば、次のようにすることができます。 + +- 認証し、アクセストークンとリフレッシュトークンを保存する +- 認証を必要とするリクエストを行う +- リクエストがアクセストークンの有効期限切れを示すステータスコードで失敗した場合、ストレージにリフレッシュトークンがあれば、更新リクエストを行い、新しいアクセストークンとリフレッシュトークンを保存し、元のリクエストを再試行する + +このアプローチの欠点の1つは、トークンの保存と更新ロジックが専用の場所を持たないことです。これは、特定のアプリケーションやチームには適しているかもしれませんが、トークン管理のロジックがより複雑な場合、リクエスト送信とトークン管理の責任を分けたいと思うかもしれません。この場合、リクエストとAPIクライアントを`shared/api`に置き、トークンストレージと更新ロジックを`shared/auth`に配置します。 + +このアプローチのもう1つの欠点は、サーバーがトークンとともに現在のユーザーに関する情報を返す場合、その情報を保存する場所がなく、特別なエンドポイント(例えば`/me`や`/users/current`)から再度取得する必要があることです。 + +### Entities層に保存する + +FSDプロジェクトには、ユーザーエンティティや現在のユーザーエンティティが存在することがよくあります。これらは同じエンティティである場合もあります。 + +:::note + +**現在のユーザー**は時には「viewer」や「me」とも呼ばれます。これは、権限とプライベート情報を持つ認証されたユーザーと、公開情報を持つ他のすべてのユーザーを区別するために行われます。 + +::: + +ユーザーエンティティにトークンを保存するには、`model`セグメントにリアクティブストアを作成します。このストアには、トークンとユーザー情報のオブジェクトの両方を含めることができます。 + +APIクライアントは通常、`shared/api`に配置されるか、エンティティ間で分散されるため、このアプローチの主な問題は、他のリクエストがトークンにアクセスできるようにしつつ、[レイヤーのインポートルール][import-rule-on-layers]を破らないことです。 + +> スライス内のモジュール(ファイル)は、下層にあるスライスのみをインポートできる。 + +この問題にはいくつかの解決策があります。 + +1. **リクエストを行うたびにトークンを手動で渡す** + これは最も簡単な解決策ですが、すぐに不便になり、厳密な型付けがない場合は忘れやすくなります。この解決策は、Shared層のAPIクライアントのミドルウェアパターンとも互換性がありません。 + +2. **コンテキストや`localStorage`のようなグローバルストレージを介してアプリ全体にトークンへのアクセスを提供する** + トークンを取得するためのキーは`shared/api`に保存され、APIクライアントがそれを使用できるようにします。トークンのリアクティブストアはユーザーエンティティからエクスポートされ、必要に応じてコンテキストプロバイダーがApp層で設定されます。これにより、APIクライアントの設計に対する自由度が増しますが、このアプローチは暗黙の依存関係を生み出してしまいます。 + +3. **トークンが変更されるたびにAPIクライアントにトークンを挿入する** + リアクティブなストアであれば、変更を監視し、ユーザーエンティティのストアが変更されるたびにAPIクライアントのトークンを更新できます。この解決策は、前の解決策と同様に暗黙の依存関係を生み出してしまいますが、より命令的(「プッシュ」)であり、前のものはより宣言的(「プル」)です。 + +ユーザーエンティティのモデルに保存されたトークンの可用性の問題を解決したら、トークン管理に関連する追加のビジネスロジックを記述できます。例えば、`model`セグメントには、トークンを一定期間後に無効にするロジックや、期限切れのトークンを更新するロジックを含めることができます。これらのタスクを実行するために、ユーザーエンティティの`api`セグメント、または`shared/api`を使用します。 + +### Pages層/Widgets層に保存する(非推奨) + +アクセストークンのようなアプリ全体に関連する状態をページやウィジェットに保存することは推奨されません。ログインページの`model`セグメントにトークンストレージを配置しないでください。代わりに、最初の2つの解決策(Shared層配置かEntities層配置)のいずれかを選択してください。 + +## ログアウトとトークンの無効化 + +通常、アプリケーションではログアウト専用のページを作成しませんが、ログアウト機能は非常に重要です。この機能には、バックエンドへの認証リクエストとトークンストレージの更新が含まれます。 + +すべてのリクエストを`shared/api`に保存している場合は、ログイン関数の近くにログアウトリクエストの関数を配置してください。そうでない場合は、ログアウトを呼び出すボタンの近くに配置してください。例えば、すべてのページに存在し、ログアウトリンクを含むヘッダーウィジェットがある場合、そのリクエストをこのウィジェットの`api`セグメントに配置します。 + +トークンストレージの更新も、ログアウトボタンの場所からトリガーされる必要があります。リクエストとストレージの更新をこのウィジェットの`model`セグメントで統合できます。 + +### 自動ログアウト + +ログアウトリクエストやトークン更新リクエストの失敗を考慮することを忘れないでください。いずれの場合も、トークンストレージをリセットする必要があります。トークンをEntities層に保存している場合、このコードは`model`セグメントに配置できます。トークンをShared層に保存している場合、このロジックを`shared/api`に配置すると、セグメントが膨らみ、その目的が曖昧になってしまいます。`api`セグメントに無関係な2つのものが含まれていることに気づいた場合、トークン管理ロジックを別のセグメント、例えば`shared/auth`に分離することを検討してみてください。 + +[tutorial-authentication]: /docs/get-started/tutorial#authentication +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-remix]: https://remix.run +[ext-zod]: https://zod.dev \ No newline at end of file diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/autocompleted.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/autocompleted.mdx new file mode 100644 index 0000000000..98102f4f7f --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/autocompleted.mdx @@ -0,0 +1,13 @@ +--- +sidebar_position: 5 +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# 自動補完 + + + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/browser-api.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/browser-api.mdx new file mode 100644 index 0000000000..e5a3c130a8 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/browser-api.mdx @@ -0,0 +1,10 @@ +--- +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# ブラウザAPI + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/cms.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/cms.mdx new file mode 100644 index 0000000000..e53b6cc5ff --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/cms.mdx @@ -0,0 +1,10 @@ +--- +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# CMS + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/feedback.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/feedback.mdx new file mode 100644 index 0000000000..2f620c67cb --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/feedback.mdx @@ -0,0 +1,10 @@ +--- +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# フィードバック + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/i18n.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/i18n.mdx new file mode 100644 index 0000000000..e736c4fe37 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/i18n.mdx @@ -0,0 +1,11 @@ +--- +sidebar_position: 6 +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# i18n + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/index.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/index.mdx new file mode 100644 index 0000000000..9452069ba4 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/index.mdx @@ -0,0 +1,36 @@ +--- +hide_table_of_contents: true +--- + +# 例 + +

+小さな実践的な例を通じて、FSDの適用方法を示します。 +

+ +## 主要 {#main} + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { UserSwitchOutlined, LayoutOutlined, FontSizeOutlined } from "@ant-design/icons"; + + + + \ No newline at end of file diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/metric.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/metric.mdx new file mode 100644 index 0000000000..fe2dcfcac3 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/metric.mdx @@ -0,0 +1,10 @@ +--- +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# メトリクス + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/monorepo.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/monorepo.mdx new file mode 100644 index 0000000000..42cb3c59a9 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/monorepo.mdx @@ -0,0 +1,11 @@ +--- +sidebar_position: 9 +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# モノレポ + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md new file mode 100644 index 0000000000..baa8ef1a9f --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md @@ -0,0 +1,104 @@ +--- +sidebar_position: 3 +--- + +# ページレイアウト + +このガイドでは、複数のページが同じ構造を持ち、主な内容だけが異なる場合のページレイアウトの抽象化について説明します。 + +:::info + +あなたの質問がこのガイドにない場合は、この記事にフィードバックを残して質問を投稿してください(右側の青いボタン)、私たちはこのガイドを拡張する可能性を検討します! + +::: + +## シンプルなレイアウト + +最もシンプルなレイアウトは、このページで直接見ることができます。これは、サイトのナビゲーションを含むヘッダー、2つのサイドバー、外部リンクを含むフッターを持っています。ここには複雑なビジネスロジックはなく、唯一の動的部分はサイドバーとヘッダーの右側にあるトグルスイッチです。このレイアウトは、`shared/ui`または`app/layouts`に全体を配置でき、サイドバーのコンテンツはプロパティを通じて埋め込むことができます。 + +```tsx title="shared/ui/layout/Layout.tsx" +import { Link, Outlet } from "react-router-dom"; +import { useThemeSwitcher } from "./useThemeSwitcher"; + +export function Layout({ siblingPages, headings }) { + const [theme, toggleTheme] = useThemeSwitcher(); + + return ( +
+
+ + +
+
+ + {/* ここにページの主な内容が表示されます */} + +
+
+
    +
  • GitHub
  • +
  • X
  • +
+
+
+ ); +} +``` + +```ts title="shared/ui/layout/useThemeSwitcher.ts" +export function useThemeSwitcher() { + const [theme, setTheme] = useState("light"); + + function toggleTheme() { + setTheme(theme === "light" ? "dark" : "light"); + } + + useEffect(() => { + document.body.classList.remove("light", "dark"); + document.body.classList.add(theme); + }, [theme]); + + return [theme, toggleTheme] as const; +} +``` + + +サイドバーのコードは読者に課題として残されています! + +## レイアウトでのウィジェットの使用 + +時には、特定のビジネスロジックをレイアウトに組み込む必要があります。特に、[React Router][ext-react-router]のような深くネストされたルートを使用している場合、Shared層やWidgets層にレイアウトを保存することはできません。これは[レイヤーのインポートルール][import-rule-on-layers]に違反しています。 + +> スライス内のモジュールは、下層にあるスライスのみをインポートできる。 + +解決策を議論する前に、これが実際に問題かどうかを確認する必要があります。このレイアウトは本当に必要なのか?もしそうなら、ウィジェットとして実装することが最適なのかも再考する必要があるでしょう。もしビジネスロジックのブロックが2〜3ページで使用され、レイアウトがそのウィジェットの小さなラッパーに過ぎない場合、次の2つのオプションを検討してください。 + +1. **レイアウトをApp層のルーターで直接作成する** + これは、ネストをサポートするルーターに最適です。特定のルートをグループ化し、必要なレイアウトをそれらにのみ適用できます。 + +2. **単にコピーする** + コードを抽象化する欲求はしばしば過大評価されます。特にレイアウトに関しては、変更がほとんどないためです。ある時点で、これらのページの1つが変更を必要とする場合、他のページに影響を与えずに変更を加えることができます。他のページを更新することを忘れるかもしれないと心配している場合は、ページ間の関係を説明するコメントを残すことができます。 + +上記のいずれのオプションも適用できない場合、ウィジェットをレイアウトに組み込むための2つの解決策があります。 + +1. **レンダープロップまたはスロットを使用する** + ほとんどのフレームワークは、UIの一部を外部から渡すことを許可しています。Reactではこれを[レンダープロップ][ext-render-props]と呼び、Vueでは[スロット][ext-vue-slots]と呼びます。 + +2. **レイアウトをApp層に移動する** + レイアウトをApp層に保存し、必要なウィジェットを組み合わせることもできます。 + +## 追加資料 + +- ReactとRemixを使用した認証付きレイアウトの作成例は[チュートリアル][tutorial]で見つけることができます(React Routerに類似する)。 + +[tutorial]: /docs/get-started/tutorial +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-react-router]: https://reactrouter.com/ +[ext-render-props]: https://www.patterns.dev/react/render-props-pattern/ +[ext-vue-slots]: https://jp.vuejs.org/guide/components/slots \ No newline at end of file diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/platforms.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/platforms.mdx new file mode 100644 index 0000000000..79f3389bb6 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/platforms.mdx @@ -0,0 +1,10 @@ +--- +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# デスクトップ/タッチプラットフォーム + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/ssr.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/ssr.mdx new file mode 100644 index 0000000000..51fdbba8de --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/ssr.mdx @@ -0,0 +1,10 @@ +--- +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# SSR + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/theme.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/theme.mdx new file mode 100644 index 0000000000..9de66e03b0 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/theme.mdx @@ -0,0 +1,11 @@ +--- +sidebar_position: 4 +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# テーマ化 + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/types.md b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/types.md new file mode 100644 index 0000000000..e76320af1f --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/types.md @@ -0,0 +1,436 @@ +--- +sidebar_position: 2 +--- + +# 型 + +このガイドでは、TypeScriptのような型付き言語のデータの型と、それがFSDにどのように適合するかについて説明します。 + +:::info + +あなたの質問がこのガイドにない場合は、この記事にフィードバックを残して質問を投稿してください(右側の青いボタン)、私たちはこのガイドを拡張する可能性を検討します! + +::: + +## ユーティリティ型 + +ユーティリティ型は、特に意味を持たず、通常は他の型と一緒に使用される型です。例えば + +
+ + +```ts +type ArrayValues = T[number]; +``` + + +
+ 出典: https://github.com/sindresorhus/type-fest/blob/main/source/array-values.d.ts +
+ +
+ +ユーティリティ型をプロジェクトに追加するには、[`type-fest`][ext-type-fest]のようなライブラリをインストールするか、`shared/lib`に独自のライブラリを作成します。必ず、新しい型をこのライブラリに追加できるか、できないかを明確に示してください。例えば、`shared/lib/utility-types`と名付け、その中にユーティリティ型があなたのチームの理解において何であるかを説明するREADMEファイルを追加してください。 + +ユーティリティ型の再利用の可能性を過大評価しないでください。再利用可能であるからといって、必ずしも再利用されるわけではなく、したがってすべてのユーティリティ型がShared層に存在する必要はありません。一部のユーティリティ型は、使用される場所の近くに置くべきです。 + +- 📂 pages + - 📂 home + - 📂 api + - 📄 ArrayValues.ts (ユーティリティ型) + - 📄 getMemoryUsageMetrics.ts (このユーティリティを使用するコード) + +:::warning + +`shared/types`フォルダーを作成したり、スライスに`types`セグメントを追加する誘惑に負けないでください。「型」というカテゴリは「コンポーネント」や「フック」と同様に、内容を説明するものであり、目的を示すものではありません。セグメントはコードの目的を説明するべきであり、その本質を説明するべきではありません。 + +::: + +## ビジネスエンティティと相互参照 + +アプリケーションで最も重要な型の一つは、ビジネスエンティティの型、つまりアプリケーションが扱う実際のオブジェクトです。例えば、オンライン音楽サービスのアプリケーションでは、ビジネスエンティティとして「曲」(song)や「アルバム」(album)などがあります。 + +ビジネスエンティティは、しばしばバックエンドから提供されるため、最初のステップはバックエンドのレスポンスを型付けすることです。各エンドポイントに対してリクエスト関数を持ち、その関数の呼び出し結果を型付けするのが便利です。型の安全性を高めるために、Zodのようなスキーマ検証ライブラリを通じて結果を通過させることができます。 + +例えば、すべてのリクエストをShared層に保存している場合、次のようにできます。 + +```ts title="shared/api/songs.ts" +import type { Artist } from "./artists"; + +interface Song { + id: number; + title: string; + artists: Array; +} + +export function listSongs() { + return fetch('/api/songs').then((res) => res.json() as Promise>); +} +``` + + +`Song`型が他の`Artist`エンティティを参照していることに気付くかもしれません。これはリクエストをShared層に保存する利点です。実際の型が相互に参照されることが多いです。この関数を`entities/song/api`に置いた場合、`entities/artist`から`Artist`を単純にインポートすることはできません。なぜなら、FSDはスライス間のクロスインポートを[レイヤーのインポートルール][import-rule-on-layers]によって制限しているからです。 + +> スライス内のモジュールは、下層にあるスライスのみをインポートできる。 + +この問題を解決する方法は2つあります。 + +1. **型をパラメーター化する** + 型が他のエンティティと接続するためのスロットとして型引数を受け取るようにすることができます。さらに、これらのスロットに制約を課すこともできます。例えば + + ```ts title="entities/song/model/song.ts" + interface Song { + id: number; + title: string; + artists: Array; + } + ``` + + これはいくつかの型に対してはうまく機能しますが、機能しないケースもあります。`Cart = { items: Array }`のような単純な型は、任意のプロダクト型で簡単に機能させることができます。しかし、`Country`と`City`のようなより関連性の高い型は、分離するのが難しいかもしれません。 + +2. **クロスインポートする(正しく)** + FSD内でエンティティ間のクロスインポートを行うには、各スライス専用の特別の公開APIを使用することができます。例えば、`song`(曲)、`artist`(アーティスト)、`playlist`(プレイリスト)のエンティティがあり、後者の2つが`song`を参照する必要がある場合、`@x`ノーテーションを通じて`song`エンティティ内に2つの特別な公開APIを作成できます。 + + - 📂 entities + - 📂 song + - 📂 @x + - 📄 artist.ts (公開API、`artist`エンティティをインポートする) + - 📄 playlist.ts (公開API、`playlist`エンティティをインポートする) + - 📄 index.ts (通常の公開API) + + `📄 entities/song/@x/artist.ts`ファイルの内容は、`📄 entities/song/index.ts`と似ています。 + + ```ts title="entities/song/@x/artist.ts" + export type { Song } from "../model/song.ts"; + ``` + + その後、`📄 entities/artist/model/artist.ts`は次のように`Song`をインポートできます。 + + ```ts title="entities/artist/model/artist.ts" + import type { Song } from "entities/song/@x/artist"; + + export interface Artist { + name: string; + songs: Array; + } + ``` + + エンティティ間の明示的な関係を持つことで、依存関係を正確に制御し、ドメインの分離を十分に保つことができます。 + +## データ転送オブジェクト(DTO)とマッパー {#data-transfer-objects-and-mappers} + +データ転送オブジェクト、またはDTO(Data Transfer Object)は、バックエンドから送信されるデータの形式を説明する用語です。時にはDTOをそのまま使用できますが、時にはその形式がフロントエンドにとって不便な場合があります。ここでマッパーが役立ちます。マッパーは、DTOをより使いやすい形式に変換する関数です。 + +### DTOをどこに置くか + +バックエンドの型が別のパッケージにある場合(例えば、フロントエンドとバックエンド間でコードを共有している場合)、そこからDTOをインポートするだけで済みます。バックエンドとフロントエンド間でコードを共有していない場合、DTOをフロントエンドコードのどこかに保存する必要があります。この場合については以下で説明します。 + +リクエスト関数を`shared/api`に保存している場合、その関数で使用するDTOも、ちょうどその関数の近くに配置するべきです。 + + +```ts title="shared/api/songs.ts" +import type { ArtistDTO } from "./artists"; + +interface SongDTO { + id: number; + title: string; + artist_ids: Array; +} + +export function listSongs() { + return fetch('/api/songs').then((res) => res.json() as Promise>); +} +``` + +前のセクションで述べたように、リクエストとDTOをShared層に保存する利点は、他のDTOを参照できることです。 + +### マッパーをどこに置くか + +マッパーは、DTOを変換するための関数であり、したがってDTOの定義の近くに置くべきです。実際には、リクエストとDTOが`shared/api`に定義されている場合、マッパーもそこに置くべきです。 + +```ts title="shared/api/songs.ts" +import type { ArtistDTO } from "./artists"; + +interface SongDTO { + id: number; + title: string; + disc_no: number; + artist_ids: Array; +} + +interface Song { + id: string; + title: string; + /** 曲の完全なタイトル、ディスク番号を含む。 */ + fullTitle: string; + artistIds: Array; +} + +function adaptSongDTO(dto: SongDTO): Song { + return { + id: String(dto.id), + title: dto.title, + fullTitle: `${dto.disc_no} / ${dto.title}`, + artistIds: dto.artist_ids.map(String), + }; +} + +export function listSongs() { + return fetch('/api/songs').then(async (res) => (await res.json()).map(adaptSongDTO)); +} +``` + +リクエストとストレージがエンティティスライスに定義されている場合、このすべてのコードはそこに置くべきであり、エンティティ間のクロスインポートの制限を考慮する必要があります。 + +```ts title="entities/song/api/dto.ts" +import type { ArtistDTO } from "entities/artist/@x/song"; + +export interface SongDTO { + id: number; + title: string; + disc_no: number; + artist_ids: Array; +} +``` + +```ts title="entities/song/api/mapper.ts" +import type { SongDTO } from "./dto"; + +export interface Song { + id: string; + title: string; + /** 曲の完全なタイトル、ディスク番号を含む。 */ + fullTitle: string; + artistIds: Array; +} + +export function adaptSongDTO(dto: SongDTO): Song { + return { + id: String(dto.id), + title: dto.title, + fullTitle: `${dto.disc_no} / ${dto.title}`, + artistIds: dto.artist_ids.map(String), + }; +} +``` + +```ts title="entities/song/api/listSongs.ts" +import { adaptSongDTO } from "./mapper"; + +export function listSongs() { + return fetch('/api/songs').then(async (res) => (await res.json()).map(adaptSongDTO)); +} +``` + +```ts title="entities/song/model/songs.ts" +import { createSlice, createEntityAdapter } from "@reduxjs/toolkit"; + +import { listSongs } from "../api/listSongs"; + +export const fetchSongs = createAsyncThunk('songs/fetchSongs', listSongs); + +const songAdapter = createEntityAdapter(); +const songsSlice = createSlice({ + name: "songs", + initialState: songAdapter.getInitialState(), + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchSongs.fulfilled, (state, action) => { + songAdapter.upsertMany(state, action.payload); + }) + }, +}); +``` + +### ネストされたDTOをどう扱うか + +最も問題となるのは、バックエンドからのレスポンスが複数のエンティティを含む場合です。例えば、曲がアーティストのIDだけでなく、アーティストのデータオブジェクト全体を含む場合です。この場合、エンティティは互いに知らないわけにはいきません(データを捨てたり、バックエンドチームと真剣に話し合いたくない場合を除いて)。スライス間の暗黙的な関係の解決策を考えるのではなく、`@x`ノーテーションを通じて明示的なクロスインポートを選ぶべきです。Redux Toolkitを使用してこれを実装する方法は次のとおりです。 + +```ts title="entities/song/model/songs.ts" +import { + createSlice, + createEntityAdapter, + createAsyncThunk, + createSelector, +} from '@reduxjs/toolkit' +import { normalize, schema } from 'normalizr' + +import { getSong } from "../api/getSong"; + +// normalizrでエンティティのスキーマを宣言 +export const artistEntity = new schema.Entity('artists') +export const songEntity = new schema.Entity('songs', { + artists: [artistEntity], +}) + +const songAdapter = createEntityAdapter() + +export const fetchSong = createAsyncThunk( + 'songs/fetchSong', + async (id: string) => { + const data = await getSong(id) + // データを正規化して、リデューサーが予測可能なオブジェクトをロードできるようにします。例えば + // `action.payload = { songs: {}, artists: {} }` + const normalized = normalize(data, songEntity) + return normalized.entities + } +) + +export const slice = createSlice({ + name: 'songs', + initialState: songAdapter.getInitialState(), + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchSong.fulfilled, (state, action) => { + songAdapter.upsertMany(state, action.payload.songs) + }) + }, +}) + +const reducer = slice.reducer +export default reducer +``` + +```ts title="entities/song/@x/artist.ts" +export { fetchSong } from "../model/songs"; +``` + +```ts title="entities/artist/model/artists.ts" +import { createSlice, createEntityAdapter } from '@reduxjs/toolkit' + +import { fetchSong } from 'entities/song/@x/artist' + +const artistAdapter = createEntityAdapter() + +export const slice = createSlice({ + name: 'users', + initialState: artistAdapter.getInitialState(), + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchSong.fulfilled, (state, action) => { + // ここでバックエンドからの同じレスポンスを処理し、ユーザーを追加します + artistAdapter.upsertMany(state, action.payload.artists) + }) + }, +}) + +const reducer = slice.reducer +export default reducer +``` + +これはスライスの分離の利点を少し制限しますが、私たちが制御できないこれらの2つのエンティティ間の関係を明確に示します。これらのエンティティがリファクタリングされる場合、同時にリファクタリングする必要があります。 + +## グローバルの型とRedux + +グローバルの型とは、アプリケーション全体で使用される型のことです。グローバルの型には、必要な情報に応じて2種類があります。 +1. アプリケーションに特有の情報を持たないユニバーサル型 +2. アプリケーション全体について知る必要がある型 + +最初のケースは簡単に解決できます。型をShared層の適切なセグメントに置くだけです。例えば、分析用のグローバル変数のインターフェースがある場合、それを`shared/analytics`に置くことができます。 + +:::warning + +`shared/types`フォルダーを作成することは避けてください。これは「型である」という特性に基づいて無関係なものをグループ化するだけであり、この特性はプロジェクト内でコードを検索する際には通常無意味です。 + +::: + +2番目のケースは、RTKなしでReduxを使用しているプロジェクトでよく見られます。最終的なストアの型は、すべてのリデューサーを結合した後にのみ利用可能ですが、このストアの型はアプリケーションで使用されるセレクターに必要です。例えば、以下はReduxでのストアの典型的な定義です。 + +```ts title="app/store/index.ts" +import { combineReducers, rootReducer } from "redux"; + +import { songReducer } from "entities/song"; +import { artistReducer } from "entities/artist"; + +const rootReducer = combineReducers(songReducer, artistReducer); + +const store = createStore(rootReducer); + +type RootState = ReturnType; +type AppDispatch = typeof store.dispatch; +``` + +`shared/store`に型付けされた`useAppDispatch`と`useAppSelector`のフックを持つことは良いアイデアですが、[レイヤーのインポートルール][import-rule-on-layers]のために、App層から`RootState`と`AppDispatch`をインポートすることはできません。 + +> スライス内のモジュールは、下層にあるスライスのみをインポートできる。 + +この場合の推奨解決策は、Shared層とApp層の間に暗黙の依存関係を作成することです。これらの2つの型、`RootState`と`AppDispatch`は、変更される可能性が低く、Reduxの開発者には馴染みのあるものであるため、暗黙の関係は問題にならないでしょう。 + +TypeScriptでは、これらの型をグローバルとして宣言することで実現できます。例えば + +```ts title="app/store/index.ts" +/* 上記のコードブロックと同じ内容… */ + +declare type RootState = ReturnType; +declare type AppDispatch = typeof store.dispatch; +``` + +```ts title="shared/store/index.ts" +import { useDispatch, useSelector, type TypedUseSelectorHook } from "react-redux"; + +export const useAppDispatch = useDispatch.withTypes() +export const useAppSelector: TypedUseSelectorHook = useSelector; +``` + +## 型のバリデーションスキーマとZod + +データが特定の形式や制約に従っていることを確認したい場合、バリデーションスキーマを作成できます。TypeScriptでこの目的に人気のあるライブラリは[Zod][ext-zod]です。バリデーションスキーマは、可能な限りそれを使用するコードの近くに配置する必要があります。 + +バリデーションスキーマは、データ転送オブジェクト(DTO)と似ており([DTOとマッパー](#data-transfer-objects-and-mappers)のセクションで説明)、DTOを受け取り、それを解析し、解析に失敗した場合はエラーを返します。 + +バリデーションの最も一般的なケースの一つは、バックエンドからのデータです。通常、データがスキーマに従わない場合、リクエストを失敗としてマークしたいので、リクエスト関数と同じ場所にスキーマを置くのが良いでしょう。通常、これは`api`セグメントになります。 + +ユーザー入力を介してデータが送信される場合、例えばフォームを通じて、バリデーションはデータ入力時に行わなければなりません。この場合、スキーマを`ui`セグメントに配置し、フォームコンポーネントの近くに置くか、`ui`セグメントが過負荷である場合は`model`セグメントに配置できます。 + +## コンポーネントのプロップスとコンテキストの型付け + +一般的に、プロップスやコンテキストのインターフェースは、それを使用するコンポーネントやコンテキストと同じファイルに保存するのが最良です。VueやSvelteのように、単一ファイルコンポーネントを持つフレームワークの場合、インターフェースを同じファイルに定義できない場合や、複数のコンポーネント間でこのインターフェースを再利用したい場合は、通常は`ui`セグメント内の同じフォルダーに別のファイルを作成します。 + +以下はJSX(ReactまたはSolid)の例です。 + +```ts title="pages/home/ui/RecentActions.tsx" +interface RecentActionsProps { + actions: Array<{ id: string; text: string }>; +} + +export function RecentActions({ actions }: RecentActionsProps) { + /* … */ +} +``` + +以下は、Vueのために別のファイルにインターフェースを保存する例です。 + +```ts title="pages/home/ui/RecentActionsProps.ts" +export interface RecentActionsProps { + actions: Array<{ id: string; text: string }>; +} +``` + +```html title="pages/home/ui/RecentActions.vue" + +``` + +## 環境宣言ファイル(`*.d.ts`) + +一部のパッケージ、例えば[Vite][ext-vite]や[ts-reset][ext-ts-reset]は、アプリケーションで動作するために環境宣言ファイルを必要とします。通常、これらは小さくて簡単なので、特にアーキテクチャを必要とせず、単に`src/`に置くことができます。`src`をより整理されたものにするために、App層の`app/ambient/`に保存することもできます。 + +他のパッケージは単に型を持たず、その型を未定義として宣言する必要があるか、あるいは自分で型を作成する必要があるかもしれません。これらの型の良い場所は`shared/lib`で、`shared/lib/untyped-packages`のようなフォルダーです。そこに`%LIBRARY_NAME%.d.ts`というファイルを作成し、必要な型を宣言します。 + +```ts title="shared/lib/untyped-packages/use-react-screenshot.d.ts" +// このライブラリには型がなく、自分で型を書くのは億劫です。 +declare module "use-react-screenshot"; +``` + +## 型の自動生成 + +外部ソースから型を生成することは、しばしば便利です。例えば、OpenAPIスキーマからバックエンドの型を生成することができます。この場合、これらの型のためにコード内に特別な場所を作成します。例えば、`shared/api/openapi`のようにします。これらのファイルが何であるか、どのように再生成されるかを説明するREADMEをこのフォルダーに含めておくと理想的です。 + +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-type-fest]: https://github.com/sindresorhus/type-fest +[ext-zod]: https://zod.dev +[ext-vite]: https://vitejs.dev +[ext-ts-reset]: https://www.totaltypescript.com/ts-reset diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx new file mode 100644 index 0000000000..b536ecddbc --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx @@ -0,0 +1,11 @@ +--- +sidebar_position: 8 +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# ホワイトラベル + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/index.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/index.mdx new file mode 100644 index 0000000000..f6b7376683 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/index.mdx @@ -0,0 +1,46 @@ +--- +hide_table_of_contents: true +pagination_prev: get-started/index +--- + +# 🎯 ガイド + +実践指向 + +

+Feature-Sliced Designの適用に関する実践的なガイドと例です。このセクションでは、移行ガイドや悪習のハンドブックも説明されています。具体的な何かを実現しようとしているときや、FSDを「実戦」で見たいときに最も役立ちます。 +

+ +## 主な内容 {#main} + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { ToolOutlined, ImportOutlined, BugOutlined, FunctionOutlined } from "@ant-design/icons"; + + + + + + \ No newline at end of file diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/issues/_category_.yaml b/i18n/ja/docusaurus-plugin-content-docs/current/guides/issues/_category_.yaml new file mode 100644 index 0000000000..8d2bd9c676 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/issues/_category_.yaml @@ -0,0 +1,2 @@ +label: コードの臭いと問題 +position: 4 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx new file mode 100644 index 0000000000..92183efece --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx @@ -0,0 +1,13 @@ +--- +sidebar_position: 4 +sidebar_class_name: sidebar-item--wip +pagination_next: reference/index +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# クロスインポート + + + +> クロスインポートは、レイヤーや抽象化が本来の責任以上に多くの責任を持ち始めると発生する。そのため、FSDは新しいレイヤーを設けて、これらのクロスインポートを分離することを可能にしている。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/issues/desegmented.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/issues/desegmented.mdx new file mode 100644 index 0000000000..28f3bddc42 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/issues/desegmented.mdx @@ -0,0 +1,96 @@ +--- +sidebar_position: 2 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# デセグメンテーション + + + +## 状況 {#situation} + +プロジェクトでは、特定のドメインに関連するモジュールが過度にデセグメント化され、プロジェクト全体に散らばっていることがよくあります。 + +```sh +├── components/ +| ├── DeliveryCard +| ├── DeliveryChoice +| ├── RegionSelect +| ├── UserAvatar +├── actions/ +| ├── delivery.js +| ├── region.js +| ├── user.js +├── epics/ +| ├── delivery.js +| ├── region.js +| ├── user.js +├── constants/ +| ├── delivery.js +| ├── region.js +| ├── user.js +├── helpers/ +| ├── delivery.js +| ├── region.js +| ├── user.js +├── entities/ +| ├── delivery/ +| | ├── getters.js +| | ├── selectors.js +| ├── region/ +| ├── user/ +``` + + +## 問題 {#problem} + +問題は、**高い凝集性**の原則の違反と、**変更の軸**の過度な拡張として現れます。 + +## 無視する場合 {#if-you-ignore-it} + +- 例えば、配達に関するロジックに触れる必要がある場合、このロジックが複数の箇所に分散していることを考慮しなければならず、コード内で複数の箇所に触れる必要がある。これにより、**変更の軸**が過度に引き伸ばされる +- ユーザーに関するロジックを調べる必要がある場合、**actions、epics、constants、entities、components**の詳細を調べるためにプロジェクト全体を巡回しなければならない +- 暗黙関係と拡大するドメインの制御不能 + - このアプローチでは、視野が狭くなり、「定数のための定数」を作成し、プロジェクトの該当ディレクトリをごちゃごちゃさせてしまうことに気づかないことがよくある + +## 解決策 {#solution} + +特定のドメイン/ユースケースに関連するすべてのモジュールを近くに配置することです。 + +これは特定のモジュールを調べる際に、そのすべての構成要素がプロジェクト全体に散らばらず、近くに配置されるためです。 + +> これにより、コードベースとモジュール間の関係の発見しやすさと明確さが向上します。 + +```diff +- ├── components/ +- | ├── DeliveryCard +- | ├── DeliveryChoice +- | ├── RegionSelect +- | ├── UserAvatar +- ├── actions/ +- | ├── delivery.js +- | ├── region.js +- | ├── user.js +- ├── epics/{...} +- ├── constants/{...} +- ├── helpers/{...} + ├── entities/ + | ├── delivery/ ++ | | ├── ui/ # ~ components/ ++ | | | ├── card.js ++ | | | ├── choice.js ++ | | ├── model/ ++ | | | ├── actions.js ++ | | | ├── constants.js ++ | | | ├── epics.js ++ | | | ├── getters.js ++ | | | ├── selectors.js ++ | | ├── lib/ # ~ helpers + | ├── region/ + | ├── user/ +``` + +## 参照 {#see-also} +* [(記事) Cohesion and Coupling: the difference](https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/) diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/issues/routes.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/issues/routes.mdx new file mode 100644 index 0000000000..10dc329e10 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/issues/routes.mdx @@ -0,0 +1,42 @@ +--- +sidebar_position: 3 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# ルーティング + + + +## 状況 {#situation} + +ページへのURLが、pages層より下の層にハードコーディングされています。 + +```tsx title="entities/post/card" + + + + ... + +``` + + +## 問題 {#problem} + +URLがページ層に集中しておらず、責任範囲において適切な場所に配置されていません。 + +## 無視する場合 {#if-you-ignore-it} + +URLを変更する際に、URL(およびURL/リダイレクトのロジック)がpages層以外のすべての層に存在する可能性があることを考慮しなければなりません。 + +また、これは単純な商品カードでさえ、ページからの一部の責任を引き受けることを意味し、プロジェクト全体にロジックが分散してしまいます。 + +## 解決策 {#solution} + +URLやリダイレクトの処理をページ層およびそれ以上の層で定義することです。 + +URLを下層の層には、コンポジション/プロパティ/ファクトリーを通じて渡すことができます。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/_category_.yaml b/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/_category_.yaml new file mode 100644 index 0000000000..32b7491cea --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/_category_.yaml @@ -0,0 +1,2 @@ +label: 移行 +position: 2 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md b/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md new file mode 100644 index 0000000000..c0a0f2e9e2 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md @@ -0,0 +1,306 @@ +--- +sidebar_position: 1 +sidebar_label: カスタムアーキテクチャからの移行 +--- + +# カスタムアーキテクチャからの移行 + +このガイドは、カスタムのアーキテクチャからFeature-Sliced Designへの移行に役立つアプローチを説明します。 + +以下は、典型的なカスタムアーキテクチャのフォルダ構造です。このガイドでは、これを例として使用します。フォルダの内容が見えるように、フォルダの横にある青い矢印をクリックすることができます。 + +
+ 📁 src +
    +
  • +
    + 📁 actions +
      +
    • 📁 product
    • +
    • 📁 order
    • +
    +
    +
  • +
  • 📁 api
  • +
  • 📁 components
  • +
  • 📁 containers
  • +
  • 📁 constants
  • +
  • 📁 i18n
  • +
  • 📁 modules
  • +
  • 📁 helpers
  • +
  • +
    + 📁 routes +
      +
    • 📁 products.jsx
    • +
    • 📄 products.[id].jsx
    • +
    +
    +
  • +
  • 📁 utils
  • +
  • 📁 reducers
  • +
  • 📁 selectors
  • +
  • 📁 styles
  • +
  • 📄 App.jsx
  • +
  • 📄 index.js
  • +
+
+ +## 開始前に {#before-you-start} + +Feature-Sliced Designへの移行を検討する際に、チームに最も重要な質問は「本当に必要か?」です。私たちはFeature-Sliced Designが好きですが、いくつかのプロジェクトはそれなしでうまくいけることを認めています。 + +移行を検討すべき理由はいくつかあります。 + +1. 新しいチームメンバーが生産的なレベルに達するのが難しいと不満を言う。 +2. コードの一部を変更すると、**しばしば**他の無関係な部分が壊れる。 +3. 巨大なコードベースのため、新しい機能を追加するのが難しい。 + +**同僚の意に反してFSDに移行することは避けてください**。たとえあなたがチームリーダーであっても、まずは同僚を説得し、移行の利点がコストを上回ることを理解させる必要があります。 + +また、アーキテクチャの変更は、瞬時には経営陣には見えないことを覚えておいてください。始める前に、経営陣が移行を支持していることを確認し、この移行がプロジェクトにどのように役立つかを説明してください。 + +:::tip + +プロジェクトマネージャーをFSDの有用性に納得させるためのアイデアをいくつか紹介します。 + +1. FSDへの移行は段階的に行えるため、新機能の開発を止めることはありません。 +2. 良いアーキテクチャは、新しい開発者が生産性を達成するのにかかる時間を大幅に短縮できます。 +3. FSDは文書化されたアーキテクチャであるため、チームは独自の文書を維持するために常に時間を費やす必要がありません。 + +::: + +--- + +もし移行を始める決断をした場合、最初に行うべきことは`📁 src`のエイリアスを設定することです。これは、後で上位フォルダを参照するのに便利です。以降のテキストでは、`@`を`./src`のエイリアスとして扱います。 + +## ステップ1。コードをページごとに分割する {#divide-code-by-pages} + +ほとんどのカスタムアーキテクチャは、ロジックのサイズに関係なく、すでにページごとに分割されています。もし`📁 pages`がすでに存在する場合は、このステップをスキップできます。 + +もし`📁 routes`しかない場合は、`📁 pages`を作成し、できるだけ多くのコンポーネントコードを`📁 routes`から移動させてみてください。理想的には、小さなルートファイルと大きなページファイルがあることです。コードを移動させる際には、各ページのためのフォルダを作成し、その中にインデックスファイルを追加します。 + +:::note + +現時点では、ページ同士が互いにインポートすることは問題ありません。後でこれらの依存関係を解消するための別のステップがあります。今はページごとの明確な分割を確立することに集中します。 + +::: + +ルートファイル + +```js title="src/routes/products.[id].js" +export { ProductPage as default } from "@/pages/product" +``` + +ページのインデックスファイル + +```js title="src/pages/product/index.js" +export { ProductPage } from "./ProductPage.jsx" +``` + +ページコンポーネントファイル + +```jsx title="src/pages/product/ProductPage.jsx" +export function ProductPage(props) { + return
; +} +``` + +## ステップ2。 ページ以外のすべてを分離する {#separate-everything-else-from-pages} + +`📁 src/shared`フォルダを作成し、`📁 pages`や`📁 routes`からインポートされないすべてをそこに移動します。`📁 src/app`フォルダを作成し、ページやルートをインポートするすべてをそこに移動します。 + +Shared層にはスライスがないことを覚えておいてください。したがって、セグメントは互いにインポートできます。 + +最終的には、次のようなファイル構造になるはずです。 + +
+ 📁 src +
    +
  • +
    + 📁 app +
      +
    • +
      + 📁 routes +
        +
      • 📄 products.jsx
      • +
      • 📄 products.[id].jsx
      • +
      +
      +
    • +
    • 📄 App.jsx
    • +
    • 📄 index.js
    • +
    +
    +
  • +
  • +
    + 📁 pages +
      +
    • +
      + 📁 product +
        +
      • +
        + 📁 ui +
          +
        • 📄 ProductPage.jsx
        • +
        +
        +
      • +
      • 📄 index.js
      • +
      +
      +
    • +
    • 📁 catalog
    • +
    +
    +
  • +
  • +
    + 📁 shared +
      +
    • 📁 actions
    • +
    • 📁 api
    • +
    • 📁 components
    • +
    • 📁 containers
    • +
    • 📁 constants
    • +
    • 📁 i18n
    • +
    • 📁 modules
    • +
    • 📁 helpers
    • +
    • 📁 utils
    • +
    • 📁 reducers
    • +
    • 📁 selectors
    • +
    • 📁 styles
    • +
    +
    +
  • +
+
+ +## ステップ3。 ページ間のクロスインポートを解消する {#tackle-cross-imports-between-pages} + +あるページが他のページから何かをインポートしているすべての箇所を見つけ、次のいずれかを行います。 + +1. インポートされているコードを依存するページにコピーして、依存関係を取り除く。 +2. コードをShared層の適切なセグメントに移動する + - UIキットの一部であれば、`📁 shared/ui`に移動。 + - 設定の定数であれば、`📁 shared/config`に移動。 + - バックエンドとのやり取りであれば、`📁 shared/api`に移動。 + +:::note + +**コピー自体はアーキテクチャの問題ではありません**。実際、時には新しい再利用可能なモジュールに何かを抽象化するよりも、何かを複製する方が正しい場合もあります。ページの共通部分が異なってくることがあるため、その場合、依存関係が妨げにならないようにする必要があります。 + +ただし、DRY("don't repeat yourself" — "繰り返さない")の原則には意味があるため、ビジネスロジックをコピーしないようにしてください。そうしないと、バグを複数の箇所で修正することになることを頭に入れておく必要があります。 + +::: + +## ステップ4。 Shared層を分解する {#unpack-shared-layer} + +この段階では、Shared層に多くのものが入っているかもしれませんが、一般的にはそのような状況を避けるべきです。理由は、Shared層に依存している他の層にあるコードが存在している可能性があるため、そこに変更を加えることは予期しない結果を引き起こす可能性が高くなります。 + +特定のページでのみ使用されるすべてのオブジェクトを見つけ、それらをそのページのスライスに移動します。そして、_これにはアクション、リデューサー、セレクターも含まれます_。すべてのアクションを一緒にグループ化することには意味がありませんが、関連するアクションをその使用場所の近くに置くことには意味があります。 + +最終的には、次のようなファイル構造になるはずです。 + +
+ 📁 src +
    +
  • 📁 app (変更なし)
  • +
  • +
    + 📁 pages +
      +
    • +
      + 📁 product +
        +
      • 📁 actions
      • +
      • 📁 reducers
      • +
      • 📁 selectors
      • +
      • +
        + 📁 ui +
          +
        • 📄 Component.jsx
        • +
        • 📄 Container.jsx
        • +
        • 📄 ProductPage.jsx
        • +
        +
        +
      • +
      • 📄 index.js
      • +
      +
      +
    • +
    • 📁 catalog
    • +
    +
    +
  • +
  • +
    + 📁 shared (再利用されるオブジェクトのみ) +
      +
    • 📁 actions
    • +
    • 📁 api
    • +
    • 📁 components
    • +
    • 📁 containers
    • +
    • 📁 constants
    • +
    • 📁 i18n
    • +
    • 📁 modules
    • +
    • 📁 helpers
    • +
    • 📁 utils
    • +
    • 📁 reducers
    • +
    • 📁 selectors
    • +
    • 📁 styles
    • +
    +
    +
  • +
+
+ +## ステップ5。 コードを技術的な目的に基づいて整理する {#organize-by-technical-purpose} + +FSDでは、技術的な目的に基づく分割がセグメントによって行われます。よく見られるセグメントはいくつかあります。 + +- `ui` — インターフェースの表示に関連するすべて: UIコンポーネント、日付のフォーマット、スタイルなど。 +- `api` — バックエンドとのやり取り: リクエスト関数、データ型、マッパーなど。 +- `model` — データモデル: スキーマ、インターフェース、ストレージ、ビジネスロジック。 +- `lib` — 他のモジュールに必要なライブラリコード。 +- `config` — 設定ファイルやフィーチャーフラグ。 + +必要に応じて独自のセグメントを作成できます。ただし、コードをその性質によってグループ化するセグメント(例: `components`、`actions`、`types`、`utils`)を作成しないようにしてください。代わりに、コードの目的に基づいてグループ化してください。 + +ページのコードをセグメントに再分配します。すでに`ui`セグメントがあるはずなので、今は他のセグメント(例えば、アクション、リデューサー、セレクターのための`model`や、サンクやミューテーションのための`api`)を作成するときです。 + +また、Shared層を再分配して、次のフォルダを削除します。 + +- `📁 components`、`📁 containers` — その内容のほとんどは`📁 shared/ui`になるべきです。 +- `📁 helpers`、`📁 utils` — 再利用可能なヘルパー関数が残っている場合は、目的に基づいてグループ化し、これらのグループを`📁 shared/lib`に移動します。 +- `📁 constants` — 同様に、目的に基づいてグループ化し、`📁 shared/config`に移動します。 + +## 任意のステップ {#optional-steps} + +### ステップ6。 複数のページで使用されるReduxスライスからエンティティ/フィーチャーを形成する {#form-entities-features-from-redux} + +通常、これらの再利用可能なReduxスライスは、ビジネスに関連する何かを説明します(例えば、プロダクトやユーザーなど)。したがって、それらをエンティティ層に移動できます。1つのエンティティにつき1つのフォルダです。Reduxスライスが、ユーザーがアプリケーションで実行したいアクションに関連している場合(例えば、コメントなど)、それをフィーチャー層に移動できます。 + +エンティティとフィーチャーは互いに独立している必要があります。ビジネス領域に組み込まれたエンティティ間の関係がある場合は、[ビジネスエンティティに関するガイド][business-entities-cross-relations]を参照して、これらの関係を整理する方法を確認してください。 + +これらのスライスに関連するAPI関数は、`📁 shared/api`に残すことができます。 + +### ステップ7。 モジュールをリファクタリングする {#refactor-your-modules} + +`📁 modules`フォルダは通常、ビジネスロジックに使用されるため、すでにFSDのフィーチャー層に似た性質を持っています。一部のモジュールは、アプリケーションの大きな部分(例えば、アプリのヘッダーなど)を説明することもあります。この場合、それらをウィジェット層に移動できます。 + +### ステップ8。 `shared/ui`にUI基盤を正しく形成する {#form-clean-ui-foundation} + +理想的には、`📁 shared/ui`にはビジネスロジックが含まれていないUI要素のセットが含まれるべきです。また、非常に再利用可能である必要があります。 + +以前`📁 components`や`📁 containers`にあったUIコンポーネントをリファクタリングして、ビジネスロジックを分離します。このビジネスロジックを上位層に移動します。あまり多くの場所で使用されていない場合は、コピーを検討することもできます。 + +[ext-steiger]: https://github.com/feature-sliced/steiger +[business-entities-cross-relations]: /docs/guides/examples/types#business-entities-and-their-cross-references diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md b/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md new file mode 100644 index 0000000000..282faaecdc --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md @@ -0,0 +1,154 @@ +--- +sidebar_position: 2 +--- + +# v1からv2への移行 + +## なぜv2なのか? {#why-v2} + +初期の**feature-slices**の概念は、2018年に提唱されました。 + +それ以来、FSD方法論は多くの変革を経てきましたが、基本的な原則は保持されています。 + +- *標準化された*フロントエンドプロジェクト構造の使用 +- アプリケーションを*ビジネスロジック*に基づいて分割 +- *孤立した機能*の使用により、暗黙の副作用や循環依存を防止 +- モジュールの「内部」にアクセスすることを禁止する*公開API*の使用 + +しかし、以前のバージョンのFSD方法論には依然として**弱点が残っていました**。 + +- ボイラープレートの発生 +- コードベースの過剰な複雑化と抽象化間の明確でないルール +- プロジェクトのメンテナンスや新しいメンバーのオンボーディングを妨げていた暗黙のアーキテクチャ的決定 + +新しいバージョンのFSD方法論([v2][ext-v2])は、**これらの欠点を解消しつつ、既存の利点を保持することを目的としています**。 + +2018年以降、[**feature-driven**][ext-fdd]という別の類似の方法論が[発展してきました][ext-fdd-issues]。それを最初に提唱したのは[Oleg Isonen][ext-kof]でした。 + +2つのアプローチの統合により、**既存のプラクティスが改善され、柔軟性、明確さ、効率が向上しました**。 + +> 結果として、方法論の名称も「feature-slice**d**」に変更されました。 + +## なぜプロジェクトをv2に移行する意味があるのか? {#why-does-it-make-sense-to-migrate-the-project-to-v2} + +> `WIP:` 現在の方法論のバージョンは開発中であり、一部の詳細は*変更される可能性があります*。 + +#### 🔍 より透明でシンプルなアーキテクチャ {#-more-transparent-and-simple-architecture} + +FSD(v2)は、**より直感的で、開発者の間で広く受け入れられている抽象化とロジックの分割方法を提供しています**。 + +これにより、新しいメンバーの参加やプロジェクトの現状理解、アプリケーションのビジネスロジック分配に非常に良い影響を与えます。 + +#### 📦 より柔軟で誠実なモジュール性 {#-more-flexible-and-honest-modularity} + +FSD(v2)は、**より柔軟な方法でロジックを分配することを可能にしています**。 + +- 孤立した部分をゼロからリファクタリングできる +- 同じ抽象化に依存しつつ、余計な依存関係の絡みを避けられる +- 新しいモジュールの配置をよりシンプルにできる *(layer → slice → segment)* + +#### 🚀 より多くの仕様、計画、コミュニティ {#-more-specifications-plans-community} + +`core-team`は最新の(v2)バージョンのFSD方法論に積極的に取り組んでいます。 + +したがって、以下のことが期待できます。 + +- より多くの記述されたケース/問題 +- より多くの適用ガイド +- より多くの実例 +- 新しいメンバーのオンボーディングや方法論概念の学習のための全体的な文書の増加 +- 方法論の概念とアーキテクチャに関するコンベンションを遵守するためのツールキットのさらなる発展 + +> もちろん、初版に対するユーザーサポートも行われますが、私たちにとっては最新のバージョンが最優先です。 + +> 将来的には、次のメジャーアップデートの際に、現在のバージョン(v2)へのアクセスが保持され、**チームやプロジェクトにリスクをもたらすことはありません**。 + +## Changelog + +### `BREAKING` Layers + +FSD方法論は上位レベルでの層の明示的な分離を前提としています。 + +- `/app` > `/processes` > **`/pages`** > **`/features`** > `/entities` > `/shared` +- *つまり、すべてがフィーチャーやページとして解釈されるわけではない* +- このアプローチにより、層のルールを明示的に設定することが可能になる + - モジュールの**層が高いほど**、より多くの**コンテキスト**を持つことができる + + *(言い換えれば、各層のモジュールは、下層のモジュールのみをインポートでき、上層のモジュールはインポートできない)* + - モジュールの**層が低いほど**、変更を加える際の**危険性と責任**が増す + + *(一般的に、再利用されるのは下層のモジュールらからである)* + +### `BREAKING` Shared層 + +以前はプロジェクトのsrcルートにあったインフラストラクチャの `/ui`, `/lib`, `/api` 抽象化は、現在 `/src/shared` という別のディレクトリに分離されています。 + +- `shared/ui` - アプリケーションの共通UIキット(オプション) + - *ここで`Atomic Design`を使用することは引き続き許可されている* +- `shared/lib` - ロジックを実装するための補助ライブラリセット + - *引き続き、ヘルパー関数の「ごみ屋敷」を作らずに* +- `shared/api` - APIへのアクセスのための共通エントリポイント + - *各フィーチャー/ページにローカルに記述することも可能だが、推奨されない* +- 以前と同様に、`shared`にはビジネスロジックへの明示的な依存関係があってはならない + - *必要に応じて、この依存関係は`entities`、またはそれ以上の層に移動する必要がある* + +### `新規` Entities層, Processes層 + +v2では、**ロジックの複雑さと強い結合の問題を解消するために、新しい抽象化が追加されました**。 + +- `/entities` - **ビジネスエンティティ**の層で、ビジネスモデルやフロントエンド専用の合成エンティティに関連するスライスを含む + - *例:`user`, `i18n`, `order`, `blog`* +- `/processes` - アプリケーション全体にわたる**ビジネスプロセス**の層 + - **この層はオプションであり、通常は*ロジックが拡大し、複数のページにまたがる場合に使用が推奨される*** + - *例:`payment`, `auth`, `quick-tour`* + +### `BREAKING` 抽象化と命名 + +具体的な抽象化とその命名に関する[明確なガイドライン][refs-adaptability]が定義されています。 + +#### Layers + +- `/app` — **アプリケーションの初期化層** + - *以前のバリエーション: `app`, `core`, `init`, `src/index`* +- `/processes` — **ビジネスプロセスの層** + - *以前のバリエーション: `processes`, `flows`, `workflows`* +- `/pages` — **アプリケーションのページ層** + - *以前のバリエーション: `pages`, `screens`, `views`, `layouts`, `components`, `containers`* +- `/features` — **機能部分の層** + - *以前のバリエーション: `features`, `components`, `containers`* +- `/entities` — **ビジネスエンティティの層** + - *以前のバリエーション: `entities`, `models`, `shared`* +- `/shared` — **再利用可能なインフラストラクチャコードの層** 🔥 + - *以前のバリエーション: `shared`, `common`, `lib`* + +#### Segments + +- `/ui` — **UIセグメント** 🔥 + - *以前のバリエーション:`ui`, `components`, `view`* +- `/model` — **ビジネスロジックのセグメント** 🔥 + - *以前のバリエーション:`model`, `store`, `state`, `services`, `controller`* +- `/lib` — **補助コードのセグメント** + - *以前のバリエーション:`lib`, `libs`, `utils`, `helpers`* +- `/api` — **APIセグメント** + - *以前のバリエーション:`api`, `service`, `requests`, `queries`* +- `/config` — **アプリケーション設定のセグメント** + - *以前のバリエーション:`config`, `env`, `get-env`* + +### `REFINED` 低結合 + +新しいレイヤーのおかげで、モジュール間の[低結合の原則][refs-low-coupling]を遵守することがはるかに簡単になりました。 + +*それでも、モジュールを「切り離す」ことが非常に難しい場合は、できるだけ避けることが推奨されます*。 + +## 参照 {#see-also} + +- [React Berlin Talk - Oleg Isonen "Feature Driven Architecture"][ext-kof-fdd] + +[refs-low-coupling]: /docs/reference/slices-segments#zero-coupling-high-cohesion +[refs-adaptability]: /docs/about/understanding/naming + +[ext-fdd]: https://github.com/feature-sliced/documentation/tree/rc/feature-driven +[ext-fdd-issues]: https://github.com/kof/feature-driven-architecture/issues +[ext-v2]: https://github.com/feature-sliced/documentation +[ext-kof]: https://github.com/kof +[ext-kof-fdd]: https://www.youtube.com/watch?v=BWAeYuWFHhs diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md b/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md new file mode 100644 index 0000000000..a29c7f7d2f --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md @@ -0,0 +1,45 @@ +--- +sidebar_position: 3 +--- + +# v2.0からv2.1への移行 + +v2.1の主な変更点は、インターフェースを分解するための「ページファースト」という新しいメンタルモデルです。 + +v2.0では、FSDは分解のためにエンティティ表現やインタラクティビティの最小部分まで考慮し、インターフェース内のエンティティとフィーチャーを特定することを推奨していました。そうしてから、エンティティとフィーチャーからウィジェットやページが構築されていきました。この分解モデルでは、ほとんどのロジックはエンティティとフィーチャーにあり、ページはそれ自体にはあまり重要性のない構成層に過ぎませんでした。 + +v2.1では、分解をページから始めること、または場合によってはページで止めることを推奨します。ほとんどの人はすでにアプリを個々のページに分ける方法を知っており、ページはコードベース内のコンポーネントを見つける際の一般的な出発点でもあります。この新しい分解モデルでは、各個別のページにほとんどのUIとロジックを保持し、Sharedに再利用可能な基盤を維持します。複数のページでビジネスロジックを再利用する必要が生じた場合は、それを下層のレイヤーに移動できます。 + +Feature-Sliced Designへのもう一つの追加は、`@x`表記を使用したエンティティ間のクロスインポートの標準化です。 + +## 移行方法 {#how-to-migrate} + +v2.1には破壊的な変更はなく、FSD v2.0で書かれたプロジェクトもFSD v2.1の有効なプロジェクトです。しかし、新しいメンタルモデルがチームや特に新しい開発者のオンボーディングにとってより有益であると考えているため、分解に対して小さな調整を行うことを推奨します。 + +### スライスのマージ + +移行を始めるための簡単な方法は、プロジェクトでFSDのリンターである[Steiger][steiger]を実行することです。Steigerは新しいメンタルモデルで構築されており、最も役立つルールは次のとおりです。 + +- [`insignificant-slice`][insignificant-slice] — エンティティ、またはフィーチャーが1ページでのみ使用されている場合、このルールはそのエンティティ、またはフィーチャーをページに完全にマージすることを提案します。 +- [`excessive-slicing`][excessive-slicing] — レイヤーにスライスが多すぎる場合、通常は分解が細かすぎるサインです。このルールは、プロジェクトのナビゲーションを助けるためにいくつかのスライスをマージ、またはグループ化することを提案します。 + +```bash +npx steiger src +``` + +これにより、1回だけ使用されるスライスを特定できるため、それらが本当に必要か再考することができます。そのような考慮において、レイヤーはその内部のすべてのスライスのための何らかのグローバル名前空間を形成することを念頭に置いてください。1回だけ使用される変数でグローバル名前空間を汚染しないようにするのと同様に、レイヤーの名前空間内の場所を貴重なものとして扱い、慎重に使用するべきです。 + +### クロスインポートの標準化 + +以前にプロジェクト内でクロスインポートがあった場合、Feature-Sliced Designでのクロスインポートのための新しい表記法`@x`を活用できます。これは次のようになります。 + +```ts title="entities/B/some/file.ts" +import type { EntityA } from "entities/A/@x/B"; +``` + +詳細については、リファレンスの[クロスインポートの公開API][public-api-for-cross-imports]セクションを参照してください。 + +[insignificant-slice]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice +[steiger]: https://github.com/feature-sliced/steiger +[excessive-slicing]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/excessive-slicing +[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/tech/_category_.yaml b/i18n/ja/docusaurus-plugin-content-docs/current/guides/tech/_category_.yaml new file mode 100644 index 0000000000..b4c34b26eb --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/tech/_category_.yaml @@ -0,0 +1,2 @@ +label: 技術 +position: 3 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx new file mode 100644 index 0000000000..67de70dec4 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx @@ -0,0 +1,109 @@ +--- +sidebar_position: 10 +--- +# NextJSとの併用 + +NextJSプロジェクトでFSDを実装することは可能ですが、プロジェクトの構造に関するNextJSの要件とFSDの原則の間に2つの点で対立が生じます。 + +- `pages`のファイルルーティング +- NextJSにおける`app`の対立、または欠如 + +## `pages`におけるFSDとNextJSの対立 {#pages-conflict} + +NextJSは、アプリケーションのルートを定義するために`pages`フォルダーを使用することを提案しています。`pages`フォルダー内のファイルがURLに対応することを期待しています。このルーティングメカニズムは、FSDの概念に**適合しません**。なぜなら、このようなルーティングメカニズムでは、スライスの平坦な構造を維持することができないからです。 + +### NextJSの`pages`フォルダーをプロジェクトのルートフォルダーに移動する(推奨) + +このアプローチは、NextJSの`pages`フォルダーをプロジェクトのルートフォルダーに移動し、FSDのページをNextJSの`pages`フォルダーにインポートすることにあります。これにより、`src`フォルダー内でFSDのプロジェクト構造を維持できます。 + +```sh +├── pages # NextJSのpagesフォルダー +├── src +│ ├── app +│ ├── entities +│ ├── features +│ ├── pages # FSDのpagesフォルダー +│ ├── shared +│ ├── widgets +``` + +### FSD構造における`pages`フォルダーの名前変更 + +もう一つの解決策は、FSD構造内の`pages`層の名前を変更して、NextJSの`pages`フォルダーとの名前衝突を避けることです。 +FSDの`pages`層を`views`層に変更することができます。 +このようにすることで、`src`フォルダー内のプロジェクト構造は、NextJSの要件と矛盾することなく保持されます。 + + +```sh +├── app +├── entities +├── features +├── pages # NextJSのpagesフォルダー +├── views # 名前が変更されたFSDのページフォルダー +├── shared +├── widgets +``` + +この場合、プロジェクトのREADMEや内部ドキュメントなど、目立つ場所にこの名前変更を文書化することをお勧めします。この名前変更は、[「プロジェクト知識」][project-knowledge]の一部です。 + +## NextJSにおける`app`フォルダーの欠如 {#app-absence} + +NextJSのバージョン13未満では、明示的な`app`フォルダーは存在せず、代わりにNextJSは`_app.tsx`ファイルを提供しています。このファイルは、プロジェクトのすべてのページのラッピングコンポーネントとして機能しています。 + +### `pages/_app.tsx`ファイルへの機能のインポート + +NextJSの構造における`app`フォルダーの欠如の問題を解決するために、`app`層内に`App`コンポーネントを作成し、NextJSがそれを使用できるように`pages/_app.tsx`に`App`コンポーネントをインポートすることができます。例えば + + +```tsx +// app/providers/index.tsx + +const App = ({ Component, pageProps }: AppProps) => { + return ( + + + + + + + + ); +}; + +export default App; +``` +その後、`App`コンポーネントとプロジェクトのグローバルスタイルを`pages/_app.tsx`に次のようにインポートできます。 + +```tsx +// pages/_app.tsx + +import 'app/styles/index.scss' + +export { default } from 'app/providers'; +``` + + +## App Routerの使用 {#app-router} + +App Routerは、Next.jsのバージョン13.4で安定版として登場しました。App Routerを使用すると、`pages`フォルダーの代わりに`app`フォルダーをルーティングに使用できます。 +FSDの原則に従うために、NextJSの`app`フォルダーを`pages`フォルダーとの名前衝突を解消するために推奨される方法で扱うべきです。 + +このアプローチは、NextJSの`app`フォルダーをプロジェクトのルートフォルダーに移動し、FSDのページをNextJSの`app`フォルダーにインポートすることに基づいています。これにより、`src`フォルダー内のFSDプロジェクト構造が保持されます。また、プロジェクトのルートフォルダーに`pages`フォルダーを追加することもお勧めします。なぜなら、App RouterはPages Routerと互換性があるからです。 + +``` +├── app # NextJSのappフォルダー +├── pages # 空のNextJSのpagesフォルダー +│ ├── README.md # このフォルダーの目的に関する説明 +├── src +│ ├── app # FSDのappフォルダー +│ ├── entities +│ ├── features +│ ├── pages # FSDのpagesフォルダー +│ ├── shared +│ ├── widgets +``` + +[![StackBlitzで開く](https://developer.stackblitz.com/img/open_in_stackblitz.svg)][ext-app-router-stackblitz] + +[project-knowledge]: /docs/about/understanding/knowledge-types +[ext-app-router-stackblitz]: https://stackblitz.com/edit/stackblitz-starters-aiez55?file=README.md \ No newline at end of file diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx new file mode 100644 index 0000000000..f3ec6eacb1 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx @@ -0,0 +1,178 @@ +--- +sidebar_position: 10 +--- +# NuxtJSとの併用 + +NuxtJSプロジェクトでFSDを実装することは可能ですが、NuxtJSのプロジェクト構造要件とFSDの原則の違いにより、以下の2点でコンフリクトが発生してしまいます。 + +- NuxtJSは`src`フォルダーなしでプロジェクトのファイル構造を提供している。つまり、ファイル構造がプロジェクトのルートに配置される。 +- ファイルルーティングは`pages`フォルダーにあるが、FSDではこのフォルダーはフラットなスライス構造に割り当てられている。 + +## `src`ディレクトリのエイリアスを追加する + +設定ファイルに`alias`オブジェクトを追加します。 +```ts +export default defineNuxtConfig({ + devtools: { enabled: true }, // FSDには関係なく、プロジェクト起動時に有効 + alias: { + "@": '../src' + }, +}) +``` +## ルーター設定方法の選択 + +NuxtJSには、コンフィグを使用する方法とファイル構造を使用する方法の2つのルーティング設定方法があります。 +ファイルベースのルーティングの場合、`app/routes`ディレクトリ内に`index.vue`ファイルを作成します。一方、コンフィグを使用する場合は、`router.options.ts`ファイルでルートを設定します。 + +### コンフィグによるルーティング + +`app`層に`router.options.ts`ファイルを作成し、設定オブジェクトをエクスポートします。 +```ts title="app/router.options.ts" +import type { RouterConfig } from '@nuxt/schema'; + +export default { + routes: (_routes) => [], +}; + +``` + +プロジェクトにホームページを追加するには、次の手順を行います。 +- `pages`層内にページスライスを追加する +- `app/router.config.ts`のコンフィグに適切なルートを追加する + +ページスライスを作成するには、[CLI](https://github.com/feature-sliced/cli)を使用します。 + +```shell +fsd pages home +``` + +`home-page.vue`ファイルを`ui`セグメント内に作成し、公開APIを介してアクセスできるようにします。 + +```ts title="src/pages/home/index.ts" +export { default as HomePage } from './ui/home-page'; +``` + +このように、ファイル構造は次のようになります。 +```sh +|── src +│ ├── app +│ │ ├── router.config.ts +│ ├── pages +│ │ ├── home +│ │ │ ├── ui +│ │ │ │ ├── home-page.vue +│ │ │ ├── index.ts +``` +最後に、ルートをコンフィグに追加します。 + +```ts title="app/router.config.ts" +import type { RouterConfig } from '@nuxt/schema' + +export default { + routes: (_routes) => [ + { + name: 'home', + path: '/', + component: () => import('@/pages/home.vue').then(r => r.default || r) + } + ], +} +``` + +### ファイルルーティング + +まず、プロジェクトのルートに`src`ディレクトリを作成し、その中に`app`層と`pages`層のレイヤー、`app`層内に`routes`フォルダーを作成します。 +このように、ファイル構造は次のようになります。 + +```sh +├── src +│ ├── app +│ │ ├── routes +│ ├── pages # FSDに割り当てられたpagesフォルダー +``` + + +NuxtJSが`app`層内の`routes`フォルダーをファイルルーティングに使用するには、`nuxt.config.ts`を次のように変更します。 +```ts title="nuxt.config.ts" +export default defineNuxtConfig({ + devtools: { enabled: true }, // FSDには関係なく、プロジェクト起動時に有効 + alias: { + "@": '../src' + }, + dir: { + pages: './src/app/routes' + } +}) +``` + + +これで、`app`層内のページに対してルートを作成し、`pages`層からページを接続できます。 + +例えば、プロジェクトに`Home`ページを追加するには、次の手順を行います。 +- `pages`層内にページスライスを追加する +- `app`層内に適切なルートを追加する +- スライスのページをルートに接続する + +ページスライスを作成するには、[CLI](https://github.com/feature-sliced/cli)を使用します。 +```shell +fsd pages home +``` + + +`home-page.vue`ファイルを`ui`セグメント内に作成し、公開APIを介してアクセスできるようにします。  + +```ts title="src/pages/home/index.ts" +export { default as HomePage } from './ui/home-page'; +``` + +このページのルートを`app`層内に作成します。 + +```sh + +├── src +│ ├── app +│ │ ├── routes +│ │ │ ├── index.vue +│ ├── pages +│ │ ├── home +│ │ │ ├── ui +│ │ │ │ ├── home-page.vue +│ │ │ ├── index.ts +``` + +`index.vue`ファイル内にページコンポーネントを追加します。 + +```html title="src/app/routes/index.vue" + + + +``` + +## `layouts`について + +`layouts`は`app`層内に配置できます。そのためには、コンフィグを次のように変更します。 + +```ts title="nuxt.config.ts" +export default defineNuxtConfig({ + devtools: { enabled: true }, // FSDには関係なく、プロジェクト起動時に有効 + alias: { + "@": '../src' + }, + dir: { + pages: './src/app/routes', + layouts: './src/app/layouts' + } +}) +``` + + +## 参照 + +- [NuxtJSのディレクトリ設定変更に関するドキュメント](https://nuxt.com/docs/api/nuxt-config#dir) +- [NuxtJSのルーター設定変更に関するドキュメント](https://nuxt.com/docs/guide/recipes/custom-routing#router-config) +- [NuxtJSのエイリアス設定変更に関するドキュメント](https://nuxt.com/docs/api/nuxt-config#alias) + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx new file mode 100644 index 0000000000..991666764a --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx @@ -0,0 +1,435 @@ +--- +sidebar_position: 10 +--- + +# React Queryとの併用 + +## キーをどこに置くか問題 + +### 解決策 - エンティティごとに分割する + +プロジェクトにすでにエンティティの分割があり、各クエリが1つのエンティティに対応している場合、エンティティごとに分割するのが最良です。この場合、次の構造を使用することをお勧めします。 + +```sh +└── src/ # + ├── app/ # + | ... # + ├── pages/ # + | ... # + ├── entities/ # + | ├── {entity}/ # + | ... └── api/ # + | ├── `{entity}.query` # クエリファクトリー、キーと関数が定義されている + | ├── `get-{entity}` # エンティティを取得する関数 + | ├── `create-{entity}` # エンティティを作成する関数 + | ├── `update-{entity}` # オブジェクトを更新する関数 + | ├── `delete-{entity}` # オブジェクトを削除する関数 + | ... # + | # + ├── features/ # + | ... # + ├── widgets/ # + | ... # + └── shared/ # + ... # +``` + +もしエンティティ間に関係がある場合(例えば、「国」のエンティティに「都市」のエンティティ一覧フィールドがある場合)、`@x` アノテーションを使用した組織的なクロスインポートの[クロスインポート用のパブリックAPI][public-api-for-cross-imports]を利用するか、以下の代替案を検討できます。 + +### 代替案 — クエリを公開で保存する + +エンティティごとの分割が適さない場合、次の構造を考慮できます。 + +```sh +└── src/ # + ... # + └── shared/ # + ├── api/ # + ... ├── `queries` # クエリファクトリー + | ├── `document.ts` # + | ├── `background-jobs.ts` # + | ... # + └── index.ts # +``` + +次に、`@/shared/api/index.ts`に + +```ts title="@/shared/api/index.ts" +export { documentQueries } from "./queries/document"; +``` + +## 問題「ミューテーションはどこに?」 + +ミューテーションをクエリと混合することは推奨されません。2つの選択肢が考えられます。 + +### 1. 使用場所の近くにAPIセグメントにカスタムフックを定義する + +```tsx title="@/features/update-post/api/use-update-title.ts" +export const useUpdateTitle = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ id, newTitle }) => + apiClient + .patch(`/posts/${id}`, { title: newTitle }) + .then((data) => console.log(data)), + + onSuccess: (newPost) => { + queryClient.setQueryData(postsQueries.ids(id), newPost); + }, + }); +}; +``` + +### 2. 別の場所(Shared層やEntities層)にミューテーション関数を定義し、コンポーネント内で`useMutation`を直接使用する + +```tsx +const { mutateAsync, isPending } = useMutation({ + mutationFn: postApi.createPost, +}); +``` + +```tsx title="@/pages/post-create/ui/post-create-page.tsx" +export const CreatePost = () => { + const { classes } = useStyles(); + const [title, setTitle] = useState(""); + + const { mutate, isPending } = useMutation({ + mutationFn: postApi.createPost, + }); + + const handleChange = (e: ChangeEvent) => + setTitle(e.target.value); + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + mutate({ title, userId: DEFAULT_USER_ID }); + }; + + return ( +
+ + + Create + + + ); +}; +``` + +## クエリの組織化 + +### クエリファクトリー + +このガイドでは、クエリファクトリーの使い方について説明します。 + +:::note +クエリファクトリーとは、JSオブジェクトのことで、そのオブジェクトキーの値がクエリキー一覧を返す関数である。 +::: + +```ts +const keyFactory = { + all: () => ["entity"], + lists: () => [...postQueries.all(), "list"], +}; +``` + +:::info +`queryOptions` - react-query@v5に組み込まれたユーティリティ(オプション) + +```ts +queryOptions({ + queryKey, + ...options, +}); +``` + +より高い型安全性と将来のreact-queryのバージョンとの互換性を確保し、クエリの関数やキーへのアクセスを簡素化するために、`@tanstack/react-query`の`queryOptions`関数を使用することができる[(詳細はこちら)](https://tkdodo.eu/blog/the-query-options-api#queryoptions)。 + +::: + + +### 1. クエリファクトリーの作成 + +```tsx title="@/entities/post/api/post.queries.ts" +import { keepPreviousData, queryOptions } from "@tanstack/react-query"; +import { getPosts } from "./get-posts"; +import { getDetailPost } from "./get-detail-post"; +import { PostDetailQuery } from "./query/post.query"; + +export const postQueries = { + all: () => ["posts"], + + lists: () => [...postQueries.all(), "list"], + list: (page: number, limit: number) => + queryOptions({ + queryKey: [...postQueries.lists(), page, limit], + queryFn: () => getPosts(page, limit), + placeholderData: keepPreviousData, + }), + + details: () => [...postQueries.all(), "detail"], + detail: (query?: PostDetailQuery) => + queryOptions({ + queryKey: [...postQueries.details(), query?.id], + queryFn: () => getDetailPost({ id: query?.id }), + staleTime: 5000, + }), +}; +``` + +### 2. アプリケーションコードでのクエリファクトリーの適用 + +```tsx +import { useParams } from "react-router-dom"; +import { postApi } from "@/entities/post"; +import { useQuery } from "@tanstack/react-query"; + +type Params = { + postId: string; +}; + +export const PostPage = () => { + const { postId } = useParams(); + const id = parseInt(postId || ""); + const { + data: post, + error, + isLoading, + isError, + } = useQuery(postApi.postQueries.detail({ id })); + + if (isLoading) { + return
Loading...
; + } + + if (isError || !post) { + return <>{error?.message}; + } + + return ( +
+

Post id: {post.id}

+
+

{post.title}

+
+

{post.body}

+
+
+
Owner: {post.userId}
+
+ ); +}; +``` + +### クエリファクトリーを使用する利点 +- **クエリの構造化:** ファクトリーはすべてのAPIクエリを1か所に整理し、コードをより読みやすく、保守しやすくしている +- **クエリとキーへの便利なアクセス:** ファクトリーはさまざまなタイプのクエリとそのキーへの便利なメソッドを提供している +- **クエリの再フェッチ機能:** ファクトリーは、アプリケーションのさまざまな部分でクエリキーを変更することなく、簡単に再フェッチを行うことを可能にしている + +## ページネーション + +このセクションでは、ページネーションを使用して投稿エンティティを取得するためのAPIクエリを行う`getPosts`関数の例を挙げます。 + +### 1. `getPosts`関数の作成 + +`getPosts`関数は、APIセグメント内の`get-posts.ts`ファイルにあります。 + +```tsx title="@/pages/post-feed/api/get-posts.ts" +import { apiClient } from "@/shared/api/base"; + +import { PostWithPaginationDto } from "./dto/post-with-pagination.dto"; +import { PostQuery } from "./query/post.query"; +import { mapPost } from "./mapper/map-post"; +import { PostWithPagination } from "../model/post-with-pagination"; + +const calculatePostPage = (totalCount: number, limit: number) => + Math.floor(totalCount / limit); + +export const getPosts = async ( + page: number, + limit: number, +): Promise => { + const skip = page * limit; + const query: PostQuery = { skip, limit }; + const result = await apiClient.get("/posts", query); + + return { + posts: result.posts.map((post) => mapPost(post)), + limit: result.limit, + skip: result.skip, + total: result.total, + totalPages: calculatePostPage(result.total, limit), + }; +}; +``` + +### 2. ページネーション用のクエリファクトリー + +`postQueries`クエリファクトリーは、投稿に関するさまざまなクエリオプションを定義し、事前に定義されたページとリミットを使用して投稿一覧を取得するクエリを含みます。 + +```tsx +import { keepPreviousData, queryOptions } from "@tanstack/react-query"; +import { getPosts } from "./get-posts"; + +export const postQueries = { + all: () => ["posts"], + lists: () => [...postQueries.all(), "list"], + list: (page: number, limit: number) => + queryOptions({ + queryKey: [...postQueries.lists(), page, limit], + queryFn: () => getPosts(page, limit), + placeholderData: keepPreviousData, + }), +}; +``` + + +### 3. アプリケーションコードでの使用 + +```tsx title="@/pages/home/ui/index.tsx" +export const HomePage = () => { + const itemsOnScreen = DEFAULT_ITEMS_ON_SCREEN; + const [page, setPage] = usePageParam(DEFAULT_PAGE); + const { data, isFetching, isLoading } = useQuery( + postApi.postQueries.list(page, itemsOnScreen), + ); + return ( + <> + setPage(page)} + page={page} + count={data?.totalPages} + variant="outlined" + color="primary" + /> + + + ); +}; +``` +:::note +例は簡略化されている。 +::: + +## クエリ管理用の`QueryProvider` + +このガイドでは、`QueryProvider`をどのように構成するべきかを説明します。 + +### 1. `QueryProvider`の作成 + +`query-provider.tsx`ファイルは`@/app/providers/query-provider.tsx`にあります。 + +```tsx title="@/app/providers/query-provider.tsx" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { ReactNode } from "react"; + +type Props = { + children: ReactNode; + client: QueryClient; +}; + +export const QueryProvider = ({ client, children }: Props) => { + return ( + + {children} + + + ); +}; +``` + +### 2. `QueryClient`の作成 + +`QueryClient`はAPIクエリを管理するために使用されるインスタンスです。`query-client.ts`ファイルは`@/shared/api/query-client.ts`にあります。`QueryClient`はクエリキャッシング用の特定の設定で作成されます。 + +```tsx title="@/shared/api/query-client.ts" +import { QueryClient } from "@tanstack/react-query"; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, + gcTime: 5 * 60 * 1000, + }, + }, +}); +``` + +## コード生成 + +自動コード生成のためのツールが存在しますが、これらは上記のように設定可能なものと比較して柔軟性が低いです。Swaggerファイルが適切に構造化されている場合、これらのツールの1つを使用して`@/shared/api`ディレクトリ内のすべてのコードを生成することができます。 + +## RQの整理に関する追加のアドバイス + +### APIクライアント + +共有層であるshared層でカスタムのAPIクライアントクラスを使用することで、プロジェクト内でのAPI設定やAPI操作を標準化できます。これにより、ログ記録、ヘッダー、およびデータ交換形式(例: JSONやXML)を一元管理することができます。このアプローチにより、APIとの連携の変更や更新が簡単になり、プロジェクトのメンテナンスや開発が容易になります。 + +```tsx title="@/shared/api/api-client.ts" +import { API_URL } from "@/shared/config"; + +export class ApiClient { + private baseUrl: string; + + constructor(url: string) { + this.baseUrl = url; + } + + async handleResponse(response: Response): Promise { + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + try { + return await response.json(); + } catch (error) { + throw new Error("Error parsing JSON response"); + } + } + + public async get( + endpoint: string, + queryParams?: Record, + ): Promise { + const url = new URL(endpoint, this.baseUrl); + + if (queryParams) { + Object.entries(queryParams).forEach(([key, value]) => { + url.searchParams.append(key, value.toString()); + }); + } + const response = await fetch(url.toString(), { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + return this.handleResponse(response); + } + + public async post>( + endpoint: string, + body: TData, + ): Promise { + const response = await fetch(`${this.baseUrl}${endpoint}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + return this.handleResponse(response); + } +} + +export const apiClient = new ApiClient(API_URL); +``` + +## 参照 {#see-also} + +- [The Query Options API](https://tkdodo.eu/blog/the-query-options-api) + +[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx new file mode 100644 index 0000000000..17460fac88 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx @@ -0,0 +1,96 @@ +--- +sidebar_position: 10 +--- +# SvelteKitとの併用 + +SvelteKitプロジェクトでFSDを実装することは可能ですが、SvelteKitのプロジェクト構造要件とFSDの原則の違いにより、以下の2点でコンフリクトが発生してしまいます。 + +- SvelteKitは`src/routes`フォルダー内でファイル構造を作成することを提案しているが、FSDではルーティングは`app`層の一部である必要がある +- SvelteKitは、ルーティングに関係のないすべてのものを`src/lib`フォルダーに入れることを提案している + +## コンフィグファイルの設定 + +```ts title="svelte.config.ts" +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config}*/ +const config = { + preprocess: [vitePreprocess()], + kit: { + adapter: adapter(), + files: { + routes: 'src/app/routes', // ルーティングをapp層内に移動 + lib: 'src', + appTemplate: 'src/app/index.html', // アプリケーションのエントリーポイントをapp層内に移動 + assets: 'public' + }, + alias: { + '@/*': 'src/*' // srcディレクトリのエイリアスを作成 + } + } +}; +export default config; +``` + +## `src/app`内へのファイルルーティングの移動 + +`app`層を作成し、アプリケーションのエントリーポイントである`index.html`を移動し、`routes`フォルダーを作成します。 +最終的にファイル構造は次のようになります。 + +```sh +├── src +│ ├── app +│ │ ├── index.html +│ │ ├── routes +│ ├── pages # FSDに割り当てられたpagesフォルダー +``` + +これで、`app`内にページのルートを作成したり、`pages`からのページをルートに接続したりできます。 + +例えば、プロジェクトにホームページを追加するには、次の手順を実行します。 +- `pages`層内にホームページスライスを追加する +- `app`層の`routes`フォルダーに対応するルートを追加する +- スライスのページとルートを統合する + +ホームページスライスを作成するには、[CLI](https://github.com/feature-sliced/cli)を使用します。 + +```shell +fsd pages home +``` + +`ui`セグメント内に`home-page.svelte`ファイルを作成し、公開APIを介してアクセスできるようにします。  + +```ts title="src/pages/home/index.ts" +export { default as HomePage } from './ui/home-page.svelte'; +``` + +このページのルートを`app`層内に作成します。 + +```sh + +├── src +│ ├── app +│ │ ├── routes +│ │ │ ├── +page.svelte +│ │ ├── index.html +│ ├── pages +│ │ ├── home +│ │ │ ├── ui +│ │ │ │ ├── home-page.svelte +│ │ │ ├── index.ts +``` + +最後に`+page.svelte`ファイル内にページコンポーネントを追加します。 + +```html title="src/app/routes/+page.svelte" + + + + +``` + +## 参照 +- [SvelteKitのディレクトリ設定変更に関するドキュメント](https://kit.svelte.dev/docs/configuration#files) \ No newline at end of file diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/intro.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/intro.mdx new file mode 100644 index 0000000000..738b12efaf --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/intro.mdx @@ -0,0 +1,69 @@ +--- +sidebar_position: 1 +slug: / +pagination_next: get-started/index +--- + +# ドキュメント + +![feature-sliced-banner](/img/banner.jpg) + +**Feature-Sliced Design** (FSD) とは、フロントエンドアプリケーションの設計方法論です。簡単に言えば、コードを整理するためのルールと規約の集大成です。FSDの主な目的は、ビジネス要件が絶えず変化する中で、プロジェクトをより理解しやすく、構造化されたものにすることです。 + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { RocketOutlined, ThunderboltOutlined, FundViewOutlined } from "@ant-design/icons"; +import Link from "@docusaurus/Link"; + + + + + +
+ + + + + + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/reference/index.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/reference/index.mdx new file mode 100644 index 0000000000..c5b2c63852 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/reference/index.mdx @@ -0,0 +1,32 @@ +--- +hide_table_of_contents: true +pagination_prev: guides/index +--- + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { ApiOutlined, GroupOutlined, AppstoreOutlined, NodeIndexOutlined } from "@ant-design/icons"; + +# 📚 参考書 + +

+FSDの重要な概念に関するセクション +

+ + + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/reference/layers.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/reference/layers.mdx new file mode 100644 index 0000000000..7ed42c5c8e --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/reference/layers.mdx @@ -0,0 +1,155 @@ +--- +sidebar_position: 1 +pagination_next: reference/slices-segments +--- + +import useBaseUrl from "@docusaurus/useBaseUrl"; + +# レイヤー + +レイヤーは、Feature-Sliced Designにおける組織階層の最初のレベルです。その目的は、コードを責任の程度やアプリ内の他のモジュールへの依存度に基づいて分離することです。各レイヤーは、コードにどれだけの責任を割り当てるべきかを判断するための特別な意味を持っています。 + +合計で**7つのレイヤー**があり、責任と依存度が最も高いものから最も低いものへと配置されています。 + +A file system tree, with a single root folder called src and then seven subfolders: app, processes, pages, widgets, features, entities, shared. The processes folder is slightly faded out. +A file system tree, with a single root folder called src and then seven subfolders: app, processes, pages, widgets, features, entities, shared. The processes folder is slightly faded out. + + +1. App (アップ) +2. Processes (プロセス、非推奨) +3. Pages (ページ) +4. Widgets (ウィジェット) +5. Features (フィーチャー) +6. Entities (エンティティ) +7. Shared (シェアード) + +プロジェクトにすべてのレイヤーを使用する必要はありません。プロジェクトに価値をもたらすと思う場合のみ追加してください。通常、ほとんどのフロントエンドプロジェクトには、少なくともShared、Page、Appのレイヤーがあります。 + +実際には、レイヤーは小文字の名前のフォルダーです(例えば、`📁 shared`、`📁 pages`、`📁 app`)。新しいレイヤーを追加することは推奨されていません。なぜなら、その意味は標準化されているからです。 + +## レイヤーに関するインポートルール + +レイヤーは _スライス_ で構成されており、これは非常に凝集性の高いモジュールのグループです。スライス間の依存関係は、**レイヤーに関するインポートルール**によって規制されています。 + +> _スライス内のモジュール(ファイル)は、下位のレイヤーにある他のスライスのみをインポートできます。_ + +例えば、フォルダー `📁 ~/features/aaa` は「aaa」という名前のスライスです。その中のファイル `~/features/aaa/api/request.ts` は、`📁 ~/features/bbb` 内のファイルからコードをインポートすることはできませんが、`📁 ~/entities` や `📁 ~/shared` からコードをインポートすることができ、例えば `~/features/aaa/lib/cache.ts` などの同じスライス内の隣接コードもインポートできます。 + +AppとSharedのレイヤーは、このルールの**例外**です。これらは同時にレイヤーとスライスの両方です。スライスはビジネスドメインによってコードを分割しますが、これらの2つのレイヤーは例外です。なぜなら、Sharedはビジネスドメインを持たず、Appはすべてのビジネスドメインを統合しているからです。 + +実際には、AppとSharedのレイヤーはセグメントで構成されており、セグメントは自由に互いにインポートできます。 + +## レイヤーの定義 + +このセクションでは、各レイヤーの意味を説明し、どのようなコードがそこに属するかの直感を得るためのものです。 + +### Shared + +このレイヤーは、アプリの残りの部分の基盤を形成します。外部世界との接続を作成する場所であり、例えばバックエンド、サードパーティライブラリ、環境などです。また、特定のタスクに集中した自分自身のライブラリを作成する場所でもあります。 + +このレイヤーは、Appレイヤーと同様に、_スライスを含みません_。スライスはビジネスドメインによってレイヤーを分割することを目的としていますが、Sharedにはビジネスドメインが存在しないため、Shared内のすべてのファイルは互いに参照し、インポートすることができます。 + +このレイヤーで通常見られるセグメントは次のとおりです。 + +- `📁 api` — APIクライアントおよび特定のバックエンドエンドポイントへのリクエストを行う関数 +- `📁 ui` — アプリケーションのUIキット + このレイヤーのコンポーネントはビジネスロジックを含むべきではありませんが、ビジネスに関連することは許可されています。例えば、会社のロゴやページレイアウトをここに置くことができます。UIロジックを持つコンポーネントも許可されています(例えば、オートコンプリートや検索バー) +- `📁 lib` — 内部ライブラリのコレクション + このフォルダーはヘルパーやユーティリティとして扱うべきではありません([なぜこれらのフォルダーがしばしばダンプに変わるか][ext-sova-utility-dump])。このフォルダー内の各ライブラリは、日付、色、テキスト操作など、1つの焦点を持つべきです。その焦点はREADMEファイルに文書化されるべきです。チームの開発者は、これらのライブラリに何を追加でき、何を追加できないかを知っているべきです +- `📁 config` — 環境変数、グローバルフィーチャーフラグ、アプリの他のグローバル設定 +- `📁 routes` — ルート定数、またはルートをマッチさせるためのパターン +- `📁 i18n` — 翻訳のセットアップコード、グローバル翻訳文字列 + +さらにセグメントを追加することは自由ですが、これらのセグメントの名前は内容の目的を説明するものでなければなりません。例えば、`components`、`hooks`、`types` は、コードを探しているときにあまり役立たないため、悪いセグメント名です。 + +### Entities + +このレイヤーのスライスは、プロジェクトが扱う現実世界の概念を表します。一般的には、ビジネスがプロダクトを説明するために使用する用語です。例えば、SNSは、ユーザー、投稿、グループなどのビジネスエンティティを扱うかもしれません。 + +エンティティスライスには、データストレージ(`📁 model`)、データ検証スキーマ(`📁 model`)、エンティティ関連のAPIリクエスト関数(`📁 api`)、およびインターフェース内のこのエンティティの視覚的表現(`📁 ui`)が含まれる場合があります。視覚的表現は、完全なUIブロックを生成する必要はなく、アプリ内の複数のページで同じ外観を再利用することを主に目的としています。異なるビジネスロジックは、プロップスやスロットを通じてそれに付加されることがあります。 + +#### エンティティの関係 + +FSDにおけるエンティティはスライスであり、デフォルトではスライスは互いに知ることができません。しかし、現実の世界では、エンティティはしばしば互いに相互作用し、一方のエンティティが他のエンティティを所有、または含むことがあります。そのため、これらの相互作用のビジネスロジックは、フィーチャーやページのような上位のレイヤーに保持されるのが望ましいです。 + +一つのエンティティのデータオブジェクトが他のデータオブジェクトを含む場合、通常はエンティティ間の接続を明示的にし、`@x`表記を使用してスライスの隔離を回避するのが良いアイデアです。理由は、接続されたエンティティは一緒にリファクタリングする必要があるため、その接続を見逃すことができないようにするのが最善です。 + +例: + +```ts title="entities/artist/model/artist.ts" +import type { Song } from "entities/song/@x/artist"; + +export interface Artist { + name: string; + songs: Array; +} +``` + +```ts title="entities/song/@x/artist.ts" +export type { Song } from "../model/song.ts"; +``` + +`@x`表記の詳細については、[クロスインポートの公開API][public-api-for-cross-imports]セクションを参照してください。 + +### Features + +このレイヤーは、アプリ内の主要なインタラクション、つまりユーザーが行いたいことを対象としています。これらのインタラクションは、ビジネスエンティティを含むことが多いです。 + +アプリのフィーチャーレイヤーを効果的に使用するための重要な原則は、**すべてのものがフィーチャーである必要はない**ということです。何かがフィーチャーである必要がある良い指標は、それが複数のページで再利用されるという事実です。 + +例えば、アプリに複数のエディターがあり、すべてにコメントがある場合、コメントは再利用されるフィーチャーです。スライスはコードを迅速に見つけるためのメカニズムであり、フィーチャーが多すぎると重要なものが埋もれてしまいます。 + +理想的には、新しいプロジェクトに入ったとき、既存のページやフィーチャーを見ると、アプリの機能性が分かります。何がフィーチャーであるべきかを決定する際には、プロジェクトの新参者が重要なコードの大きな領域を迅速に発見できるように最適化してください。 + +フィーチャーのスライスには、インタラクションを実行するためのUI(例えばフォーム、`📁 ui`)、アクションを実行するために必要なAPI呼び出し(`📁 api`)、検証および内部状態(`📁 model`)、フィーチャーフラグ(`📁 config`)が含まれる場合があります。 + +### Widgets + +ウィジェットレイヤーは、大きな自己完結型のUIブロックを対象としています。ウィジェットは、複数のページで再利用される場合や、所属するページにある複数の大きな独立したブロックの一つである場合に最も便利です。 + +UIのブロックがページの大部分を構成し、再利用されない場合、それは**ウィジェットであるべきではなく**、代わりにそのページ内に直接配置するべきです。 + +:::tip + +ネストされたルーティングシステム([Remix][ext-remix]のルーターのような)を使用している場合、ウィジェットレイヤーを、フラットなルーティングシステムがページレイヤーを使用するのと同じように使用することが役立つかもしれません。それは関連データの取得、ローディング状態、エラーバウンダリを含む完全なルーターブロックを作成するためです。 + +同様に、このレイヤーにページレイアウトを保存することもできます。 + +::: + +### Pages + +ページは、ウェブサイトやアプリケーションを構成するものです(スクリーンやアクティビティとも呼ばれます)。通常、1ページは1つのスライスに対応しますが、非常に似たページが複数ある場合、それらを1つのスライスにまとめることができます。例えば、登録フォームとログインフォームです。 + +チームがナビゲートしやすい限り、ページスライスに配置できるコードの量に制限はありません。ページ上のUIブロックが再利用されない場合、それをページスライス内に保持することは完全に問題ありません。 + +ページスライスには、通常、ページのUIやローディング状態、エラーバウンダリ(`📁 ui`)、データの取得や変更リクエスト(`📁 api`)が含まれます。ページが専用のデータモデルを持つことは一般的ではなく、状態の小さな部分はコンポーネント自体に保持されることがあります。 + +### Processes + +:::caution + +このレイヤーは非推奨です。現在の仕様では、これを避け、その内容をFeaturesやAppに移動することを推奨しています。 + +::: + +プロセスは、マルチページインタラクションのための逃げ道です。 + +このレイヤーは意図的に未定義のままにされています。ほとんどのアプリケーションはこのレイヤーを使用せず、ルーターやサーバーレベルのロジックをAppレイヤーに保持するべきです。このレイヤーは、Appレイヤーが大きくなりすぎてメンテナンスが困難になった場合にのみ使用することを検討してください。 + +### App + +アプリ全体に関するあらゆるもの、技術的な意味(例えば、コンテキストプロバイダー)やビジネス的な意味(例えば、分析)を含みます。 + +このレイヤーには通常、スライスは含まれず、Sharedと同様に、セグメントが直接存在します。 + +このレイヤーで通常見られるセグメントは次のとおりです。 + +- `📁 routes` — ルーターの設定 +- `📁 store` — グローバルストアの設定 +- `📁 styles` — グローバルスタイル +- `📁 entrypoint` — アプリケーションコードへのエントリポイント、フレームワーク固有 + +[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports +[ext-remix]: https://remix.run +[ext-sova-utility-dump]: https://dev.to/sergeysova/why-utils-helpers-is-a-dump-45fo diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/reference/public-api.md b/i18n/ja/docusaurus-plugin-content-docs/current/reference/public-api.md new file mode 100644 index 0000000000..0800e8724b --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/reference/public-api.md @@ -0,0 +1,161 @@ +--- +sidebar_position: 3 +--- + +import useBaseUrl from "@docusaurus/useBaseUrl"; + +# 公開API + +公開APIは、モジュールのグループ(スライスなど)とそれを使用するコードとの間の**契約**です。また、特定のオブジェクトへのアクセスを制限し、その公開APIを通じてのみアクセスを許可します。 + +実際には、通常、再エクスポートを伴うインデックスファイルとして実装されます。 + +```js title="pages/auth/index.js" +export { LoginPage } from "./ui/LoginPage"; +export { RegisterPage } from "./ui/RegisterPage"; +``` + +## 良い公開APIとは? + +良い公開APIは、他のコードとの統合を便利で信頼性の高いものにします。これを達成するためには、以下の3つの目標を設定することが重要です。 + +1. アプリケーションの残りの部分は、スライスの構造的変更(リファクタリングなど)から保護されるべきです。 +2. スライスの動作における重要な変更が以前の期待を破る場合、公開APIに変更が必要です。 +3. スライスの必要な部分のみを公開するべきです。 + +最後の目標には重要な実践的な意味があります。特にスライスの初期開発段階では、すべてをワイルドカードで再エクスポートしたくなるかもしれません。なぜなら、ファイルからエクスポートする新しいオブジェクトは、スライスからも自動的にエクスポートされるからです。 + + +```js title="バッドプラクティス, features/comments/index.js" +// ❌ これは悪いコードの例です。このようにしないでください。 +export * from "./ui/Comment"; // 👎 +export * from "./model/comments"; // 💩 +``` + +これは、スライスの理解可能性を損ないます。インターフェースが理解できないと、スライスのコードを深く掘り下げて統合方法を調べなければいけなくなってしまいます。もう一つの問題は、モジュールの内部を誤って公開してしまう可能性があり、誰かがそれに依存し始めるとリファクタリングが難しくなることです。 + +## クロスインポートのための公開API {#public-api-for-cross-imports} + +クロスインポートは、同じレイヤーの別のスライスからインポートする状況です。通常、これは[レイヤーに関するインポートルール][import-rule-on-layers]によって禁止されていますが、しばしば正当な理由でクロスインポートが必要です。たとえば、ビジネスエンティティは現実世界で互いに参照し合うことが多く、これらの関係をコードに反映させるのが最善です。 + +この目的のために、`@x`表記として知られる特別な種類の公開APIがあります。エンティティAとBがあり、エンティティBがエンティティAからインポートする必要がある場合、エンティティAはエンティティB専用の別の公開APIを宣言できます。 + +- `📂 entities` + - `📂 A` + - `📂 @x` + - `📄 B.ts` — エンティティB内のコード専用の特別な公開API + - `📄 index.ts` — 通常の公開API + +その後、`entities/B/`内のコードは`entities/A/@x/B`からインポートできます。 + +```ts +import type { EntityA } from "entities/A/@x/B"; +``` + + +`A/@x/B`という表記は「AとBが交差している」と読むことを意図しています。 + +:::note + +クロスインポートは最小限に抑え、**この表記はエンティティレイヤーでのみ使用してください**。クロスインポートを排除することがしばしば非現実的だからです。 + +::: + +## インデックスファイルの問題 + +`index.js`のようなインデックスファイル(Barrelファイルとも呼ばれる)は、公開APIを定義する最も一般的な方法です。作成は簡単ですが、特定のバンドラーやフレームワークで問題を引き起こすことがあります。 + +### 循環インポート + +循環インポートとは、2つ以上のファイルが互いに循環的にインポートすることです。 + + + +
+ 三つのファイルが循環的にインポートしている + 三つのファイルが循環的にインポートしている +
+ 上の図には、`fileA.js`、`fileB.js`、`fileC.js`の三つのファイルが循環的にインポートしている様子が示されています。 +
+
+ +これらの状況は、バンドラーにとって扱いが難しく、場合によってはデバッグが難しいランタイムエラーを引き起こすことさえあります。 + +循環インポートはインデックスファイルなしでも発生する可能性がありますが、インデックスファイルがあると、循環インポートを誤って作成する明確な機会が生まれます。これは、公開APIのスライスに2つのオブジェクト(例えば、`HomePage`と`loadUserStatistics`)が存在し、`HomePage`が`loadUserStatistics`に以下のようにアクセスすると循環インポートが発生してしまいます。 + + +```jsx title="pages/home/ui/HomePage.jsx" +import { loadUserStatistics } from "../"; // pages/home/index.jsからインポート + +export function HomePage() { /* … */ } +``` + +```js title="pages/home/index.js" +export { HomePage } from "./ui/HomePage"; +export { loadUserStatistics } from "./api/loadUserStatistics"; +``` + + +この状況は循環インポートを作成します。なぜなら、`index.js`が`ui/HomePage.jsx`をインポートしますが、`ui/HomePage.jsx`が`index.js`をインポートするからです。 + +この問題を防ぐために、次の2つの原則を考慮してください。 +- ファイルが同じスライス内にある場合は、常に**相対インポート**を使用し、完全なインポートパスを記述すること +- ファイルが異なるスライスにある場合は、常に**絶対インポート**を使用すること(エイリアスなどで) + +### Sharedにおける大きなバンドルサイズと壊れたツリーシェイキング {#large-bundles} + +インデックスファイルがすべてを再エクスポートする場合、いくつかのバンドラーはツリーシェイキング(インポートされていないコードを削除すること)に苦労するかもしれません。 + +通常、これは公開APIにとって問題ではありません。なぜなら、モジュールの内容は通常非常に密接に関連しているため、1つのものをインポートし、他のものをツリーシェイキングする必要がほとんどないからです。しかし、公開APIのルールが問題を引き起こす非常に一般的なケースが2つあります。それは`shared/ui`と`shared/lib`です。 + +これらの2つのフォルダーは、しばしば一度にすべてが必要ではない無関係なもののコレクションです。たとえば、`shared/ui`にはUIライブラリのすべてのコンポーネントのモジュールが含まれているかもしれません。 + +- `📂 shared/ui/` + - `📁 button` + - `📁 text-field` + - `📁 carousel` + - `📁 accordion` + +この問題は、これらのモジュールの1つが重い依存関係(シンタックスハイライトやドラッグ&ドロップライブラリ)を持っている場合、悪化します。ボタンなど、`shared/ui`から何かを使用するすべてのページにそれらを引き込むことは望ましくありません。 + +`shared/ui`や`shared/lib`の単一の公開APIによってバンドルサイズが不適切に増加する場合は、各コンポーネントやライブラリのために別々のインデックスファイルを持つことをお勧めします。 + +- `📂 shared/ui/` + - `📂 button` + - `📄 index.js` + - `📂 text-field` + - `📄 index.js` + +その後、これらのコンポーネントの消費者は、次のように直接インポートできます。 + +```js title="pages/sign-in/ui/SignInPage.jsx" +import { Button } from '@/shared/ui/button'; +import { TextField } from '@/shared/ui/text-field'; +``` + + +### 公開APIを回避することに対する実質的な保護がない + +スライスのためにインデックスファイルを作成しても、誰もそれを使用せず、直接インポートを使用することができます。これは特に自動インポートにおいて問題です。なぜなら、オブジェクトをインポートできる場所がいくつかあるため、IDEがあなたのために決定しなければならないからです。時には、直接インポートを選択し、スライスの公開APIルールを破ることがあります。 + +これらの問題を自動的にキャッチするために、[Steiger][ext-steiger]を使用することをお勧めします。これは、Feature-Sliced Designのルールセットを持つアーキテクチャリンターです。 + +### 大規模プロジェクトにおけるバンドラーのパフォーマンスの低下 + +プロジェクト内に大量のインデックスファイルがあると、開発サーバーが遅くなる可能性があります。これは、TkDodoが[「Barrelファイルの使用をやめてください」][ext-please-stop-using-barrel-files]という記事で指摘しています。 + +この問題に対処するためにできることはいくつかあります。 + +1. [「Sharedにおける大きなバンドルサイズと壊れたツリーシェイキング」](#large-bundles)のセクションと同じアドバイス — `shared/ui`や`shared/lib`の各コンポーネントやライブラリのために別々のインデックスファイルを持つこと。 +2. スライスを持つレイヤーのセグメントにインデックスファイルを持たないようにすること。 + たとえば、「コメント」フィーチャーのインデックス(`📄 features/comments/index.js`)がある場合、そのフィーチャーの`ui`セグメントのために別のインデックスを持つ理由はありません(`📄 features/comments/ui/index.js`)。 +3. 非常に大きなプロジェクトがある場合、アプリケーションをいくつかの大きなチャンクに分割できる可能性が高いです。 + たとえば、Google Docsは、ドキュメントエディターとファイルブラウザに非常に異なる責任を持っています。各パッケージが独自のレイヤーセットを持つ別々のFSDルートとしてモノレポを作成できます。いくつかのパッケージは、SharedとEntitiesレイヤーのみを持つことができ、他のパッケージはPagesとAppのみを持つことができ、他のパッケージは独自の小さなSharedを含むことができますが、他のパッケージからの大きなSharedも使用できます。 + + + + + +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-please-stop-using-barrel-files]: https://tkdodo.eu/blog/please-stop-using-barrel-files diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx new file mode 100644 index 0000000000..e70267a271 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx @@ -0,0 +1,73 @@ +--- +title: スライスとセグメント +sidebar_position: 2 +pagination_next: reference/public-api +--- + +import useBaseUrl from "@docusaurus/useBaseUrl"; + +# スライスとセグメント + +## スライス + +スライスは、Feature-Sliced Designの組織階層の第2レベルです。主な目的は、プロダクト、ビジネス、または単にアプリケーションにとっての意味に基づいてコードをグループ化することです。 + +スライスの名前は標準化されていません。なぜなら、それらはアプリケーションのビジネス領域によって直接決定されるからです。たとえば、フォトギャラリーには `photo`、`effects`、`gallery-page` というスライスがあるかもしれません。SNSには、`post`、`comments`、`news-feed` などの別のスライスが必要です。 + +Shared層とApp層にはスライスが含まれていません。これは、Sharedがビジネスロジックを含むべきではないため、プロダクト的な意味を持たないからです。また、Appはアプリケーション全体に関わるコードのみを含むべきであり、したがって分割は必要ありません。 + +### 低結合と高凝集 {#zero-coupling-high-cohesion} + +スライスは、独立した強く凝集しているコードファイルのグループとして設計されています。以下の図は、凝集(cohesion)と結合(coupling)といった複雑な概念を視覚化するのに役立ちます。 + +
+ + +
+ この図は、https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/ に触発されています。 +
+
+ +理想的なスライスは、同じレベルの他のスライスから独立しており(低結合)、その主な目的に関連するコードの大部分を含んでいます(高凝集)。 + +スライスの独立性は、[層のインポートルール][layers--import-rule]によって保証されます。 + +> _スライス内のモジュール(ファイル)は、厳密に下の層にあるスライスのみをインポートできます。_ + +### スライスの公開APIルール + +スライス内では、コードは自由に整理できますが、スライスが質の高い公開APIを持っている限り、問題はありません。これがスライスの公開APIのルールの本質です。 + +> _各スライス(およびスライスを持たない層のセグメント)は、公開APIの定義を含む必要があります。_ +> +> _あるスライス/セグメントの外部モジュールは、そのスライス/セグメントの内部ファイル構造ではなく、公開APIのみを参照できます。_ + +公開APIの要求の理由や、作成のベストプラクティスについては、[公開APIのガイド][ref-public-api]を参照してください。 + +### スライスのグループ + +密接に関連するスライスは、フォルダに構造的にグループ化できますが、他のスライスと同じ隔離ルールを遵守する必要があります。グループ化用のフォルダ内でのコードの共有は許可されていません。 + +![「compose」、「like」、「delete」機能が「post」フォルダにグループ化されています。このフォルダには、禁止を示すために取り消し線が引かれた「some-shared-code.ts」ファイルもあります。](/img/graphic-nested-slices.svg) + +## セグメント + +セグメントは、組織階層の第3および最後のレベルであり、その目的は、技術的な目的に基づいてコードをグループ化することです。 + +いくつかの標準化されたセグメント名があります。 + +- `ui` — UIに関連するすべてのもの:UIコンポーネント、日付フォーマッタ、スタイルなど。 +- `api` — バックエンドとのインタラクション:リクエスト関数、データ型、マッパーなど。 +- `model` — データモデル:スキーマ、インターフェース、ストレージ、ビジネスロジック。 +- `lib` — スライス内のモジュールに必要なライブラリコード。 +- `config` — 設定ファイルとフィーチャーフラグ。 + +[レイヤーに関するページ][layers--layer-definitions]には、これらのセグメントが異なる層でどのように使用されるかの例があります。 + +独自のセグメントを作成することもできます。カスタムセグメントの最も一般的な場所は、スライスが意味を持たないApp層とShared層です。 + +これらのセグメントの名前が、その内容が何のために必要かを説明するものであることを確認してください。たとえば、`components`、`hooks`、`types` は、コードを探すときにあまり役に立たないため、悪いセグメント名です。 + +[layers--layer-definitions]: /docs/reference/layers#layer-definitions +[layers--import-rule]: /docs/reference/layers#import-rule-on-layers +[ref-public-api]: /docs/reference/public-api diff --git a/i18n/ja/docusaurus-theme-classic/footer.json b/i18n/ja/docusaurus-theme-classic/footer.json new file mode 100644 index 0000000000..97db11e26b --- /dev/null +++ b/i18n/ja/docusaurus-theme-classic/footer.json @@ -0,0 +1,66 @@ +{ + "link.title.Specs": { + "message": "仕様", + "description": "The title of the footer links column with title=Specs in the footer" + }, + "link.title.Community": { + "message": "コミュニティ", + "description": "The title of the footer links column with title=Community in the footer" + }, + "link.title.More": { + "message": "その他", + "description": "The title of the footer links column with title=More in the footer" + }, + "link.item.label.Documentation": { + "message": "ドキュメント", + "description": "The label of footer link with label=Документация linking to /docs" + }, + "link.item.label.Community": { + "message": "コミュニティ", + "description": "The label of the footer link with label=Community linking to /community" + }, + "link.item.label.Help": { + "message": "ヘルプ", + "description": "The label of the footer link with label=Help linking to /nav" + }, + "link.item.label.Discussions": { + "message": "ディスカッション", + "description": "The label of footer link with label=Обсуждения linking to https://github.com/feature-sliced/documentation/discussions" + }, + "link.item.label.License": { + "message": "ライセンス", + "description": "The label of footer link with label=License linking to LICENSE" + }, + "link.item.label.Contribution Guide (RU)": { + "message": "コントリビューションガイド (RU)", + "description": "The label of footer link with label=Contribution Guide (RU) linking to CONTRIBUTING.md" + }, + "link.item.label.Discord": { + "message": "Discord", + "description": "The label of footer link with label=Discord linking to https://discord.com/invite/S8MzWTUsmp" + }, + "link.item.label.Telegram": { + "message": "Telegram", + "description": "The label of footer link with label=Telegram linking to https://t.me/feature_sliced" + }, + "link.item.label.Twitter": { + "message": "X", + "description": "The label of footer link with label=X linking to https://x.com/feature_sliced" + }, + "link.item.label.Open Collective": { + "message": "Open Collective", + "description": "The label of footer link with label=Open Collective linking to https://opencollective.com/feature-sliced" + }, + "link.item.label.YouTube": { + "message": "YouTube", + "description": "The label of footer link with label=YouTube linking to https://www.youtube.com/c/FeatureSlicedDesign" + }, + "link.item.label.GitHub": { + "message": "GitHub", + "description": "The label of footer link with label=GitHub linking to https://github.com/feature-sliced" + }, + "copyright": { + "message": "Copyright © 2018-2025 Feature-Sliced Design", + "description": "The footer copyright" + } +} diff --git a/i18n/ja/docusaurus-theme-classic/navbar.json b/i18n/ja/docusaurus-theme-classic/navbar.json new file mode 100644 index 0000000000..344f9415ab --- /dev/null +++ b/i18n/ja/docusaurus-theme-classic/navbar.json @@ -0,0 +1,50 @@ +{ + "title": { + "message": "", + "description": "The title in the navbar" + }, + "item.label.🛠 Examples": { + "message": "🛠 実装例", + "description": "Navbar item with label Examples" + }, + "item.label.📖 Docs": { + "message": "📖 ドキュメント", + "description": "Navbar item with label Docs" + }, + "item.label.🔎 Intro": { + "message": "🔎 紹介", + "description": "Navbar item with label Intro" + }, + "item.label.🚀 Get Started": { + "message": "🚀 はじめに", + "description": "Navbar item with label Get Started" + }, + "item.label.🧩 Concepts": { + "message": "🧩 コンセプト", + "description": "Navbar item with label Concepts" + }, + "item.label.🎯 Guides": { + "message": "🎯 ガイド", + "description": "Navbar item with label Guides" + }, + "item.label.📚 Reference": { + "message": "📚 参考書", + "description": "Navbar item with label Reference" + }, + "item.label.🍰 About": { + "message": "🍰 FSD設計方法論について", + "description": "Navbar item with label About" + }, + "item.label.💫 Community": { + "message": "💫 コミュニティ", + "description": "Navbar item with label Community" + }, + "item.label.❔ Help": { + "message": "❔ ヘルプ", + "description": "Navbar item with label Help" + }, + "item.label.📝 Blog": { + "message": "📝 ブログ", + "description": "Navbar item with label Blog" + } +} diff --git a/i18n/kr/code.json b/i18n/kr/code.json new file mode 100644 index 0000000000..4ba5b98530 --- /dev/null +++ b/i18n/kr/code.json @@ -0,0 +1,398 @@ +{ + "pages.home.features.title": { + "message": "특징", + "description": "Features" + }, + "pages.home.features.logic.title": { + "message": "명시적인 비즈니스 로직", + "description": "Feature title" + }, + "pages.home.features.logic.description": { + "message": "도메인 스코프 덕분에 찾고자 하는 로직을 쉽게 발견할 수 있는 아키텍처입니다.", + "description": "Feature description" + }, + "pages.home.features.adaptability.title": { + "message": "유연성", + "description": "Feature title" + }, + "pages.home.features.adaptability.description": { + "message": "아키텍처 구성 요소를 새로운 요구사항에 맞춰 유연하게 교체하고 추가할 수 있습니다.", + "description": "Feature description" + }, + "pages.home.features.debt.title": { + "message": "기술 부채 및 리팩토링", + "description": "Feature title" + }, + "pages.home.features.debt.description": { + "message": "각 모듈을 부작용 없이 독립적으로 수정, 재작성할 수 있습니다.", + "description": "Feature description" + }, + "pages.home.features.shared.title": { + "message": "명시적 코드 재사용", + "description": "Feature title" + }, + "pages.home.features.shared.description": { + "message": "DRY 원칙과 로컬 커스터마이징 사이에 균형을 유지합니다.", + "description": "Feature description" + }, + "pages.home.concepts.title": { + "message": "개념", + "description": "Concepts" + }, + "pages.home.concepts.public.title": { + "message": "공용 API", + "description": "Concept title" + }, + "pages.home.concepts.public.description": { + "message": "각 모듈에는 최상위 레벨에 공용 API 선언이 있어야 합니다.", + "description": "Concept description" + }, + "pages.home.concepts.isolation.title": { + "message": "격리", + "description": "Concept title" + }, + "pages.home.concepts.isolation.description": { + "message": "같은 레이어 또는 상위 레이어의 모듈에 직접 의존하지 않아야 합니다.", + "description": "Concept description" + }, + "pages.home.concepts.needs.title": { + "message": "요구사항 중심", + "description": "Concept title" + }, + "pages.home.concepts.needs.description": { + "message": "비즈니스 및 사용자 요구사항을 중심으로 합니다.", + "description": "Concept description" + }, + "pages.home.scheme.title": { + "message": "구조", + "description": "Scheme" + }, + "pages.home.companies.using": { + "message": "FSD를 사용하는 기업", + "description": "Companies using FSD" + }, + "pages.home.companies.add_me": { + "message": "FSD를 사용하는 기업이신가요?", + "description": "FSD is used in your company?" + }, + "pages.home.companies.tell_us": { + "message": "알려주세요", + "description": "Tell us" + }, + "pages.examples.title": { + "message": "예제", + "description": "Page title" + }, + "pages.examples.subtitle": { + "message": "Feature-Sliced Design으로 사람들이 제작한 웹사이트 목록", + "description": "Page subtitle" + }, + "pages.examples.add_me.title": { + "message": "예제 추가하기", + "description": "Request to add example" + }, + "pages.examples.repo.title": { + "message": "저장소", + "description": "Examples repository label" + }, + "pages.examples.versions": { + "message": "버전 목록도 확인해보세요", + "description": "Versions reminder" + }, + "pages.versions.title": { + "message": "Feature-Sliced Design 버전", + "description": "Feature-Sliced Design versions" + }, + "pages.versions.current": { + "message": "현재 배포된 버전의 문서는 여기에서 확인할 수 있습니다.", + "description": "Description for current version" + }, + "pages.versions.legacy": { + "message": "{of}의 이전 버전에 대한 문서는 여기에서 확인할 수 있습니다.", + "description": "Description for legacy version" + }, + "pages.nav.title": { + "message": "🧭 내비게이션", + "description": "NavPage title" + }, + "pages.nav.legacy.title": { + "message": "이전 경로", + "description": "NavPage section=legacy title" + }, + "pages.nav.legacy.details": { + "message": "문서 구조가 변경되면서 일부 경로가 바뀌었습니다. 아래에서 찾고 계신 페이지를 확인할 수 있습니다.", + "description": "NavPage section=legacy details" + }, + "pages.nav.legacy.subdetails": { + "message": "그러나 호환성을 위해 이전 링크에서 리디렉션이 제공됩니다.", + "description": "NavPage section=legacy subdetails" + }, + "features.feedback-badge.label": { + "message": "문서에 대한 의견을 공유해주세요 🤙", + "description": "Feedback share button label" + }, + "features.feedback-badge.url": { + "message": "https://forms.gle/nsYua6bMMG5iBB3v7", + "description": "Feedback share form url" + }, + "features.feedback-doc.button-text": { + "message": "피드백", + "description": "The text on a floating button to leave feedback about the docs" + }, + "features.feedback-doc.email-placeholder": { + "message": "이메일 주소 (선택 사항)", + "description": "The placeholder for email input" + }, + "features.feedback-doc.error-message": { + "message": "문제가 발생했습니다. 나중에 다시 시도해주세요.", + "description": "The error message displayed when feedback form submission fails" + }, + "features.feedback-doc.modal-title-error-403": { + "message": "요청 URL이 이 프로젝트의 PushFeedback에 정의된 URL과 일치하지 않습니다.", + "description": "The title of the modal displayed when feedback form submission fails with 403 error" + }, + "features.feedback-doc.modal-title-error-404": { + "message": "PushFeedback에서 제공된 프로젝트 ID를 찾을 수 없습니다.", + "description": "The title of the modal displayed when feedback form submission fails with 404 error" + }, + "features.feedback-doc.message-placeholder": { + "message": "코멘트", + "description": "The placeholder for message input" + }, + "features.feedback-doc.modal-title": { + "message": "피드백을 공유해주세요", + "description": "The title of the modal to leave feedback about the docs" + }, + "features.feedback-doc.modal-title-error": { + "message": "이런!", + "description": "The title of the modal displayed when feedback form submission fails" + }, + "features.feedback-doc.modal-title-success": { + "message": "피드백을 주셔서 감사합니다!", + "description": "The title of the modal displayed when feedback form submission succeeds" + }, + "features.feedback-doc.screenshot-button-text": { + "message": "스크린샷 찍기", + "description": "The text on a button to take a screenshot" + }, + "features.feedback-doc.screenshot-topbar-text": { + "message": "페이지에서 요소를 선택하세요", + "description": "The text displayed in the top bar when selecting an element to take a screenshot" + }, + "features.feedback-doc.send-button-text": { + "message": "보내기", + "description": "The text on a button to send feedback" + }, + "features.feedback-doc.rating-placeholder": { + "message": "이 페이지가 도움이 되었나요?", + "description": "The placeholder for rating input" + }, + "features.feedback-doc.rating-stars-placeholder": { + "message": "이 페이지를 어떻게 평가하시겠습니까?", + "description": "The placeholder for rating stars input" + }, + "features.hero.tagline": { + "message": "프론트엔드 프로젝트를 위한 아키텍처 방법론", + "description": "Architectural methodology for frontend projects" + }, + "features.hero.get_started": { + "message": "시작하기", + "description": "Get Started" + }, + "features.hero.examples": { + "message": "예제", + "description": "Examples" + }, + "features.hero.previous": { + "message": "이전 버전", + "description": "Previous version" + }, + "shared.wip.title": { + "message": "이 글은 작성 중입니다", + "description": "Admonition title" + }, + "shared.wip.subtitle": { + "message": "이 글의 발행을 앞당기려면 다음을 수행할 수 있습니다:", + "description": "Admonition subtitle" + }, + "shared.wip.var.feedback.base": { + "message": "📢 의견을 공유해주세요 ", + "description": "Variant for contribute (base)" + }, + "shared.wip.var.feedback.link": { + "message": "글에서 (댓글/이모지 반응)", + "description": "Variant for contribute (link)" + }, + "shared.wip.var.material.base": { + "message": "💬 관련 자료를 모아주세요 ", + "description": "Variant for contribute (base)" + }, + "shared.wip.var.material.link": { + "message": "채팅에서 주제와 관련된 자료", + "description": "Variant for contribute (link)" + }, + "shared.wip.var.contribute.base": { + "message": "⚒️ 기여하기 ", + "description": "Variant for contribute (base)" + }, + "shared.wip.var.contribute.link": { + "message": "다른 방식으로", + "description": "Variant for contribute (link)" + }, + "theme.NotFound.title": { + "message": "페이지를 찾을 수 없습니다", + "description": "The title of the 404 page" + }, + "theme.NotFound.p1": { + "message": "찾으시는 내용을 찾을 수 없습니다.", + "description": "The first paragraph of the 404 page" + }, + "theme.NotFound.p2": { + "message": "해당 URL을 링크한 사이트 소유자에게 연락해 링크가 깨졌음을 알려주세요.", + "description": "The 2nd paragraph of the 404 page" + }, + "theme.AnnouncementBar.closeButtonAriaLabel": { + "message": "닫기", + "description": "The ARIA label for close button of announcement bar" + }, + "theme.blog.paginator.navAriaLabel": { + "message": "블로그 목록 페이지 탐색", + "description": "The ARIA label for the blog pagination" + }, + "theme.blog.paginator.newerEntries": { + "message": "최신 글", + "description": "The label used to navigate to the newer blog posts page (previous page)" + }, + "theme.blog.paginator.olderEntries": { + "message": "이전 글", + "description": "The label used to navigate to the older blog posts page (next page)" + }, + "theme.blog.post.readingTime.plurals": { + "message": "1분 읽기|{readingTime}분 읽기", + "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.tags.tagsListLabel": { + "message": "태그:", + "description": "The label alongside a tag list" + }, + "theme.blog.post.readMore": { + "message": "더 읽기", + "description": "The label used in blog post item excerpts to link to full blog posts" + }, + "theme.blog.post.paginator.navAriaLabel": { + "message": "블로그 글 페이지 탐색", + "description": "The ARIA label for the blog posts pagination" + }, + "theme.blog.post.paginator.newerPost": { + "message": "최신 글", + "description": "The blog post button label to navigate to the newer/previous post" + }, + "theme.blog.post.paginator.olderPost": { + "message": "이전 글", + "description": "The blog post button label to navigate to the older/next post" + }, + "theme.tags.tagsPageTitle": { + "message": "태그", + "description": "The title of the tag list page" + }, + "theme.blog.post.plurals": { + "message": "1개의 글|{count}개의 글", + "description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.blog.tagTitle": { + "message": "\"{tagName}\" 태그가 포함된 {nPosts}개의 글", + "description": "The title of the page for a blog tag" + }, + "theme.tags.tagsPageLink": { + "message": "모든 태그 보기", + "description": "The label of the link targeting the tag list page" + }, + "theme.CodeBlock.copyButtonAriaLabel": { + "message": "코드를 클립보드에 복사", + "description": "The ARIA label for copy code blocks button" + }, + "theme.CodeBlock.copied": { + "message": "복사됨", + "description": "The copied button label on code blocks" + }, + "theme.CodeBlock.copy": { + "message": "복사", + "description": "The copy button label on code blocks" + }, + "theme.docs.sidebar.expandButtonTitle": { + "message": "사이드바 확장", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.docs.sidebar.expandButtonAriaLabel": { + "message": "사이드바 확장", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.docs.paginator.navAriaLabel": { + "message": "문서 페이지 탐색", + "description": "The ARIA label for the docs pagination" + }, + "theme.docs.paginator.previous": { + "message": "이전", + "description": "The label used to navigate to the previous doc" + }, + "theme.docs.paginator.next": { + "message": "다음", + "description": "The label used to navigate to the next doc" + }, + "theme.docs.sidebar.collapseButtonTitle": { + "message": "사이드바 축소", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.collapseButtonAriaLabel": { + "message": "사이드바 축소", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.responsiveCloseButtonLabel": { + "message": "메뉴 닫기", + "description": "The ARIA label for close button of mobile doc sidebar" + }, + "theme.docs.sidebar.responsiveOpenButtonLabel": { + "message": "메뉴 열기", + "description": "The ARIA label for open button of mobile doc sidebar" + }, + "theme.docs.versions.unreleasedVersionLabel": { + "message": "이 문서는 {siteTitle} {versionLabel} 버전의 미발행 문서입니다.", + "description": "The label used to tell the user that he's browsing an unreleased doc version" + }, + "theme.docs.versions.unmaintainedVersionLabel": { + "message": "이 문서는 더 이상 유지 관리되지 않는 {siteTitle} {versionLabel} 버전의 문서입니다.", + "description": "The label used to tell the user that he's browsing an unmaintained doc version" + }, + "theme.docs.versions.latestVersionSuggestionLabel": { + "message": "최신 문서를 보려면 {latestVersionLink} ({versionLabel})를 확인하세요.", + "description": "The label userd to tell the user that he's browsing an unmaintained doc version" + }, + "theme.docs.versions.latestVersionLinkLabel": { + "message": "최신 버전", + "description": "The label used for the latest version suggestion link label" + }, + "theme.common.editThisPage": { + "message": "이 페이지 편집하기", + "description": "The link label to edit the current page" + }, + "theme.common.headingLinkTitle": { + "message": "해당 헤딩으로 직접 이동", + "description": "Title for link to heading" + }, + "theme.lastUpdated.atDate": { + "message": "{date}에 업데이트됨", + "description": "The words used to describe on which date a page has been last updated" + }, + "theme.lastUpdated.byUser": { + "message": "{user}에 의해 업데이트됨", + "description": "The words used to describe by who the page has been last updated" + }, + "theme.lastUpdated.lastUpdatedAtBy": { + "message": "마지막 업데이트: {atDate}{byUser}", + "description": "The sentence used to display when a page has been last updated, and by who" + }, + "theme.common.skipToMainContent": { + "message": "주요 콘텐츠로 건너뛰기", + "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation" + } +} diff --git a/i18n/kr/docusaurus-plugin-content-docs/community/index.mdx b/i18n/kr/docusaurus-plugin-content-docs/community/index.mdx new file mode 100644 index 0000000000..bb8a09a1a9 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/community/index.mdx @@ -0,0 +1,41 @@ +--- +hide_table_of_contents: true +--- + +# 💫 커뮤니티 + +

+커뮤니티 리소스, 추가 자료 +

+ +## Main + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { StarOutlined, SearchOutlined, TeamOutlined, VerifiedOutlined } from "@ant-design/icons"; + + + + + + diff --git a/i18n/kr/docusaurus-plugin-content-docs/community/team.mdx b/i18n/kr/docusaurus-plugin-content-docs/community/team.mdx new file mode 100644 index 0000000000..24ebc84ba9 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/community/team.mdx @@ -0,0 +1,18 @@ +--- +sidebar_class_name: sidebar-item--wip +sidebar_position: 2 +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# 팀 소개 + + + +## 코어 팀 + +### 챔피언 + +## 기여자 + +## 협력 기업 diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/about/mission.md b/i18n/kr/docusaurus-plugin-content-docs/current/about/mission.md new file mode 100644 index 0000000000..a13527ccc6 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/about/mission.md @@ -0,0 +1,51 @@ +--- +sidebar_position: 1 +--- + +# 미션 + +이 문서에서는 방법론을 개발할 때 우리가 추구하는 목표와 적용 가능성의 한계를 설명합니다. + +- 방법론 개발의 목표는 이념과 단순성 간의 균형을 맞추는 것입니다. +- 모든 사람에게 완벽하게 맞는 만능 해결책을 만들 수는 없습니다. + +**그럼에도, 방법론은 다양한 개발자들에게 접근하기 쉽고 실용적이어야 합니다.** + +## 목표 + +### 다양한 개발자에게 직관적이고 명확하게 + +방법론은 프로젝트에 참여하는 대부분의 팀원들이 쉽게 접근하고 이해할 수 있도록 설계되어야 합니다.
+ +*향후 어떤 도구가 추가되더라도, 시니어나 리더 개발자들만 이해할 수 있는 방법론이라면 충분하지 않습니다.* + +### 일상적인 문제 해결 + +방법론은 개발 프로젝트에서 일상적으로 발생하는 문제에 대해 명확한 이유와 해결책을 제시해야 합니다. + +**이를 위해 CLI와 린터(linter) 같은 도구들도 함께 제공해야 합니다.** + +이를 통해 개발자들은 아키텍처와 개발상의 오랜 문제를 우회할 수 있는 검증된 접근 방식을 활용할 수 있습니다. + +> *@sergeysova: 방법론을 기반으로 코드를 작성하는 개발자는 이미 많은 문제에 대한 해결책이 마련되어 있기 때문에, 문제 발생 빈도가 10배 정도 줄어들 것이라고 상상해보세요.* + +## 한계 + +우리는 *특정 관점을 강요하고* 싶지 않으며, 개발자로서의 *여러 습관이 문제 해결을 방해할 수 있다는 점도 이해합니다.* + +모든 개발자의 시스템을 설계하거나 개발하는 데 경험 수준이 다르기 떄문에, **다음 사항을 이해하는 것이 중요합니다:** + +- **모두에게 동일하게 적용되지 않을 수 있음:**: 너무 간단하거나 명확한 접근법이 모든 상황에서 항상 효과적이지는 않습니다. + > *@sergeysova: 어떤 개념들은 문제를 직접 겪고, 오랜 시간을 들여 해결하는 과정을 통해서만 직관적으로 이해할 수 있는 경우가 많습니다. + > + > - *수학: 그래프 이론.* + > - *물리학: 양자 역학.* + > - *프로그래밍: 애플리케이션 아키텍처.* + +- **가능하고 바람직한 방향**: 단순함과 확장 가능성의 조화 + +## 참고 자료 + +- [아키텍쳐 문제들][refs-architecture--problems] + +[refs-architecture--problems]: /docs/about/understanding/architecture#problems diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/about/motivation.md b/i18n/kr/docusaurus-plugin-content-docs/current/about/motivation.md new file mode 100644 index 0000000000..836bfadd20 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/about/motivation.md @@ -0,0 +1,149 @@ +--- +sidebar_position: 2 +--- + +# 동기 + +**Feature-Sliced Design**은 [여러 개발자들의 연구와 경험을 결합하여][ext-discussions] 복잡하고 점점 더 커지는 프로젝트의 개발을 용이하게 하고, 비용을 줄이려는 아이디어에서 출발했습니다. + +물론 이 방법론이 모든 문제를 해결하는 만능은 아니며, [적용 가능한 제한 사항][refs-mission]이 존재합니다. + +그럼에도 불구하고 *이 방법론이 주는 전반적인 효용성*에 대해 많은 개발자들이 관심을 가지고 있습니다. + +:::note + +자세한 논의 내용은 [토론 게시글][disc-src]에서 확인할 수 있습니다. + +::: + +## 기존 솔루션으로는 부족한 이유 + +> 일반적으로 다음과 같은 반문들이 제기됩니다: +> +> - *"이미 `SOLID`, `KISS`, `YAGNI`, `DDD`, `GRASP`, `DRY` 등 오랜 기간 확립된 접근 방식과 설계 원칙들이 있는데, 왜 새로운 방법론이 필요한가?"* +> - *"모든 문제는 적절한 프로젝트 문서화, 테스트, 구조화된 프로세스로 해결될 수 있다."* +> - *"모든 개발자들이 위의 원칙들을 제대로 따른다면 문제가 발생하지 않았을 것이다."* +> - *"이미 필요한 모든 것이 발명되었고, 당신은 단지 그것을 제대로 활용하지 못할 뿐이다."* +> - *"\{프레임워크_이름\}을 사용하라. 거기에는 이미 모든 것이 정해져 있다."* + +### 원칙만으로는 충분하지 않다 + +**좋은 아키텍처를 설계하기 위해 원칙들이 존재하는 것만으로는 충분하지 않습니다.** + +모든 개발자가 이러한 원칙들을 완벽히 알고 있는 것도 아니며, 이해하고 올바르게 적용할 수 있는 사람은 더욱 적습니다. + +*설계 원칙들은 일반적인 지침이기 때문에, "확장 가능하고 유연한 애플리케이션의 구조와 아키텍처를 어떻게 설계해야 하는가?"에 대한 구체적인 답을 제공하지 못합니다.* + +### 프로세스가 항상 작동하지는 않는다 + +*문서화, 테스트, 프로세스* 관리는 분명 중요하지만, 여기에 많은 비용을 들이더라도 **아키텍처 문제나 신규 인력의 프로젝트 적응 문제를 완전히 해결하지는 못합니다.** + +- 문서가 방대해지거나 오래되면 각 개발자가 프로젝트에 빠르게 적응하는 데 큰 도움이 되지 않습니다. +- 모든 구성원이 아키텍처를 동일하게 이해하고 있는지 지속적으로 확인하는 데에도 상당한 리소스가 소모됩니다. +- bus-factor에 대해서도 잊지 말아야 합니다 + +### 기존 프레임워크를 모든 상황에 적용할 수는 없다 + +- 기존 솔루션들은 보통 진입 장벽이 높아 새로운 개발자를 구하기가 어렵습니다. +- 또한 대부분의 경우, 프로젝트 초기에 기술 스택이 이미 결정되기 때문에 **기술에 얽매이지 않고** “주어진 조건에서 작업할 수 있어야” 합니다. + +> Q: "내 프로젝트에서 `React/Vue/Redux/Effector/Mobx/{당신의_기술}`을 사용할 때, 엔티티의 구조와 이들 간의 관계를 어떻게 더 잘 구축할 수 있을까요?" + +### 결과적으로 + +각 프로젝트는 팀원이 오랜 시간 몰입해야 하며, 다른 프로젝트에는 적용하기 어려운 *"눈송이처럼 독특한"* 형태로 남게 됩니다. + +> @sergeysova: *"이것이 바로 현재 프론트엔드 개발 분야에서 일어나고 있는 문제입니다. 각 리드는 각기 다른 아키텍처와 프로젝트 구조를 만들어 내지만, 그 구조가 시간의 시험을 견딜 수 있을지는 보장할 수 없습니다. 결과적으로 두세 명 정도의 개발자만 해당 프로젝트를 유지할 수 있고, 새로운 개발자가 참여할 때마다 다시 긴 적응 기간이 필요해집니다."* + +## 개발자들이 왜 방법론이 필요한가? + +### 개발자의 아키텍처 고민을 줄이고 비즈니스 기능에 집중 + +이 방법론은 아키텍처 설계에 드는 리소스를 줄여 개발자들이 비즈니스 개발에 더 집중할 수 있도록 돕습니다. 동시에 아키텍처 구조는 표준화되어 프로젝트 간에 일관성을 제공합니다. + +*이 방법론이 커뮤니티에서 신뢰를 얻기 위해서는 다른 개발자들이 이 방법론을 쉽게 익히고, 정해진 시간 내에 각자의 프로젝트 문제를 해결하는 데 도움이 되어야 합니다.* + +### 경험으로 입증된 솔루션 제공 + +이 방법론은 복잡한 비즈니스 로직을 설계하는 데 검증된 솔루션이 필요한 개발자들을 위해 만들어졌습니다. + +또한, 이 방법론은 개발 중 발생하는 특정 문제와 사례를 다루는 베스트 프랙티스와 참고 자료들의 모음이기도 하므로, 개발과 설계 과정에서 여러 문제를 겪는 다른 개발자들에게도 유용할 것입니다 + + +### 프로젝트의 장기적인 건강성을 유지 + +이 방법론은 *많은 리소스를 들이지 않고도 프로젝트에서 발생할 수 있는 문제를 사전에 해결하고 추적할 수 있도록* 돕습니다. + +**대부분의 경우 기술 부채는 시간이 지남에 따라 누적되며, 이를 해결하는 책임은 리드와 팀 모두에게 있습니다** + +이 방법론은 프로젝트 확장과 개발 과정에서 생길 수 있는 문제들을 미리 *경고할 수* 있게 해줍니다. + +## 비즈니스에게 왜 방법론이 필요한가? + +### 빠른 온보딩 + +이 방법론을 통해 **이미 이 접근 방식에 익숙한 사람을 프로젝트에 고용할 수 있어 추가 교육이 필요하지 않습니다.** + +*덕분에 팀원들이 프로젝트를 더 빨리 이해하고 기여할 수 있으며, 다음 단계에 필요한 인력을 확보하는 데도 유리한 조건을 갖추게 됩니다.* + +### 검증된 솔루션 제공 + +이 방법론을 통해 비즈니스는 *시스템 개발 중 발생하는 대부분의 문제들에 대한 해결책*을 얻을 수 있습니다. + +대부분의 경우, 비즈니스는 프로젝트 개발 중 발생하는 다양한 문제를 해결할 수 있는 프레임워크나 솔루션을 필요로 하기 때문입니다. + +### 프로젝트의 다양한 단계에 적용 가능 + +이 방법론은 *프로젝트 지원 및 개발 단계뿐만 아니라 MVP 단계에서도* 프로젝트에 도움이 될 수 있습니다. + +네, MVP에서 가장 중요한 것은 *"미래를 위해 설계된 아키텍처가 아닌 기능"*입니다. 하지만 제한된 기한 내에서도 방법론의 베스트 프랙티스를 알고 있다면, 시스템의 MVP 버전을 설계할 때 합리적인 타협점을 찾아 *"적은 희생으로"* 해결할 수 있습니다 +(기능을 "무작위로" 모델링하는 대신). + +*테스팅에 대해서도 같은 말을 할 수 있습니다* + +## 방법론이 필요하지 않은 경우 + +- 프로젝트 수명이 짧은 경우 +- 프로젝트가 지속적인 아키텍처 관리가 필요하지 않은 경우 +- 비즈니스가 코드베이스와 기능 전달 속도 간의 연관성을 충분히 인식하지 못하는 경우 +- 비즈니스가 사후 지원보다 신속한 주문 처리를 우선해야 하는 경우 + +### 비즈니스 규모 + +- **소규모 비즈니스** - 대부분 즉시 사용 가능하며 빠른 솔루션이 필요합니다. 비즈니스가 성장하여 최소한 중간 규모에 가까워질 때, 고객 유지와 더불어 솔루션의 품질과 안정성에 투자할 필요성을 이해하게 됩니다. +- **중간 규모 비즈니스** - 일반적으로 개발 과정에서 발생하는 문제들을 이해하고 있으며, *"기능 경쟁"*이 필요한 상황에서도 품질 개선, 리팩토링, 테스트에 시간을 투자합니다. 물론 확장 가능한 아키텍처 역시 중요하게 고려합니다. +- **대규모 비즈니스** - 대규모 비즈니스는 보통 이미 광범위한 사용자층, 많은 직원, 그리고 자체적인 관행과 아키텍처 접근 방식을 가지고 있습니다. 따라서 외부에서 가져온 새로운 접근 방식을 적용하려는 경우는 드뭅니다. + +## 계획 + +주요 목표는 [여기에 명시되어 있지만][refs-mission--goals], 앞으로의 방법론에 대해 우리가 기대하는 바를 추가로 설명할 필요가 있습니다. + +### 경험 결합 + +현재 우리는 `core-team`의 다양한 경험을 하나로 모아 견고하게 단련된 방법론을 만드는 데 힘쓰고 있습니다. + +물론 그 결과가 Angular 3.0과 같은 결과물일 수도 있겠지만, 여기서 더 중요한 것은 *복잡한 시스템 아키텍처 설계라는 문제를 심도 있게 탐구*하는 것입니다. + +*현재 버전의 방법론에 대해 불만족스러운 점이 있지만, 커뮤니티의 경험도 함께 고려하여 모두가 합의할 수 있는 단일하고 최적의 솔루션에 도달하는 것이 목표입니다.* + +### 사양을 넘어선 생명력 + +모든 것이 계획대로 진행된다면, 이 방법론은 단순히 사양과 툴킷에만 국한되지 않을 것입니다. + +- 보고서나 기사들이 생길 수도 있습니다. +- 방법론에 따라 작성된 프로젝트를 다른 기술로 마이그레이션하기 위한 CODE_MODE가 있을 수 있습니다. +- 이로 인해 대규모 기술 솔루션의 유지보수자들이 이 방법론을 활용할 기회를 가질 수 있습니다. + - *특히 React의 경우, 다른 프레임워크에 비해 특정 문제를 해결하는 구체적인 방안을 제시하지 않는다는 점이 주요 문제로 남아 있습니다.* + +## See also + +- [(토론) 방법론이 필요하지 않나요?][disc-src] +- [방법론의 목표와 한계에 대한 설명][refs-mission] +- [프로젝트에서 다루는 지식의 유형][refs-knowledge] + +[refs-mission]: /docs/about/mission +[refs-mission--goals]: /docs/about/mission#goals +[refs-knowledge]: /docs/about/understanding/knowledge-types + +[disc-src]: https://github.com/feature-sliced/documentation/discussions/27 +[ext-discussions]: https://github.com/feature-sliced/documentation/discussions \ No newline at end of file diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/about/understanding/architecture.md b/i18n/kr/docusaurus-plugin-content-docs/current/about/understanding/architecture.md new file mode 100644 index 0000000000..f5502053f0 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/about/understanding/architecture.md @@ -0,0 +1,95 @@ +--- +sidebar_position: 1 +--- + +# 아키텍처에 대하여 + +## 문제점들 + +일반적으로 아키텍처에 대한 논의는 프로젝트에서 특정 문제로 인해 개발이 중단될 때 제기됩니다. + +### Bus-factor & 온보딩 + +제한된 인원만이 프로젝트와 그 아키텍처를 이해합니다. + +**예시:** + +- *"개발에 새로운 인력을 추가하기가 어렵습니다"* +- *"문제 해결 방식에 대한 명확한 가이드라인이 없어 개발자마다 각기 다른 접근 방식을 사용합니다"* +- *"이 거대한 모놀리스에서 무슨 일이 일어나고 있는지 이해할 수 없습니다"* + +### 의도치 않은 부작용과 통제되지 않는 영향 + +개발/리팩토링 중 많은 의도치 않은 부작용들이 있습니다 *("모든 것이 모든 것에 의존합니다")* + +**예시:** + +- *"기능 간의 부적절한 의존성이 발생하고 있습니다"* +- *"한 페이지의 상태(store) 변경이 다른 페이지의 기능에 예기치 않은 영향을 미칩니다"* +- *"비즈니스 로직이 애플리케이션 전반에 분산되어 있어 로직의 흐름을 추적하기 어렵습니다"* + +### 통제되지 않는 로직의 재사용 + +기존 로직을 재사용하거나 수정하기 어렵습니다. + +동시에, 보통 [두 가지 극단적인 경우](https://github.com/feature-sliced/documentation/discussions/14)가 나타납니다: + +- 각 모듈에 대해 로직을 완전히 처음부터 작성하거나 *(기존 코드베이스에서 재사용 가능한 부분까지 포함하여)* +- 또는 모든 구현된 모듈을 `shared` 폴더로 이동하려는 경향이 있어, *대부분 단일 사용 목적의 모듈들이 무분별하게 축적되는 현상이 발생합니다.* + +**예시:** + +- *"프로젝트 내에 동일한 비즈니스 로직이 **N**번 중복 구현되어 있어 유지보수 비용이 지속적으로 발생합니다"* +- *"동일한 기능의 버튼/팝업 등 UI 컴포넌트가 여러 버전으로 존재합니다"* +- *"유틸리티 함수들이 체계 없이 누적되어 있습니다"* + +## 요구사항 + +이상적인 아키텍처를 위한 *핵심 요구사항*을 다음과 같이 정의할 수 있습니다. + +:::note +여기서 "쉽다"라는 표현은 "대다수의 개발자들이 합리적인 시간 내에 이해하고 적용할 수 있다"는 의미입니다. [모든 개발자와 상황에 완벽하게 부합하는 솔루션은 현실적으로 불가능하기 때문입니다.](/docs/about/mission#limitations) +::: + +### 명시성 + +- 팀원들이 프로젝트 구조와 아키텍처를 **직관적으로 이해하고 설명할 수 있어야** 합니다. +- 아키텍처는 프로젝트의 **비즈니스 도메인과 가치를 명확히 반영**해야 합니다. +- 추상화 계층 간의 **의존성과 부작용**이 명확히 파악되어야 합니다. +- **중복 로직을 효과적으로 식별**할 수 있어야 합니다. +- 프로젝트 전반에 걸쳐 **로직이 분산**되지 않도록 해야 합니다. +- **불필요한 추상화와 복잡한 규칙**을 최소화해야 합니다. + +### 제어 + +- 효과적인 아키텍처는 **새로운 기능 개발과 문제 해결의 생산성을 향상**시켜야 합니다. +- 프로젝트의 전반적인 개발 흐름을 효율적으로 관리할 수 있어야 합니다. +- **코드의 확장성, 유지보수성, 제거 용이성**을 보장해야 합니다. +- 기능 단위의 **명확한 경계와 격리**가 보장되어야 합니다. +- 각 컴포넌트는 **높은 교체성과 제거 용이성**을 가져야 합니다. + - *[변경을 위한 과도한 최적화는 지양합니다][ext-kof-not-modification] - 미래의 변경사항을 정확히 예측하기 어렵기 때문입니다.* + - *[제거 용이성을 위한 설계가 더 중요합니다][ext-kof-but-removing] - 현재의 컨텍스트를 기반으로 한 의사결정이 더 실용적이기 때문입니다.* + +### 적응성 + +- 효과적인 아키텍처는 **다양한 규모와 유형의 프로젝트**에 적용 가능해야 합니다. + - *기존 시스템 및 인프라와의 원활한 통합이 가능해야 합니다.* + - *프로젝트의 모든 개발 단계에서 일관되게 적용될 수 있어야 합니다.* +- 특정 기술 스택이나 플랫폼에 종속되지 않아야 합니다. +- **병렬 개발과 팀 확장**이 용이해야 합니다. +- **비즈니스 요구사항과 기술 환경의 변화**에 유연하게 대응할 수 있어야 합니다. + +## See also + +- [(React Berlin Talk) Oleg Isonen - Feature Driven Architecture][ext-kof] +- [(React SPB Meetup #1) Sergey Sova - Feature Slices][ext-slices-spb] +- [(Article) 프로젝트 모듈화에 대하여][ext-medium] +- [(Article) 관점 분리와 기능 기반 구조화에 대하여][ext-ryanlanciaux] + +[ext-kof-not-modification]: https://youtu.be/BWAeYuWFHhs?t=1631 +[ext-kof-but-removing]: https://youtu.be/BWAeYuWFHhs?t=1666 + +[ext-slices-spb]: https://t.me/feature_slices +[ext-kof]: https://youtu.be/BWAeYuWFHhs +[ext-medium]: https://alexmngn.medium.com/why-react-developers-should-modularize-their-applications-d26d381854c1 +[ext-ryanlanciaux]: https://ryanlanciaux.com/blog/2017/08/20/a-feature-based-approach-to-react-development/ diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/about/understanding/knowledge-types.md b/i18n/kr/docusaurus-plugin-content-docs/current/about/understanding/knowledge-types.md new file mode 100644 index 0000000000..19924dbbb8 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/about/understanding/knowledge-types.md @@ -0,0 +1,41 @@ +--- +sidebar_position: 3 +sidebar_label: 프로젝트의 지식 유형 +--- + +# 프로젝트의 지식 유형 + +소프트웨어 프로젝트를 개발할 때 다루게 되는 지식은 크게 세 가지로 나눌 수 있습니다: + +### 기반 지식 (Fundamental Knowledge) +프로그래밍의 기본이 되는 지식으로, 시간이 지나도 크게 변하지 않습니다: +- 알고리즘과 자료구조 +- 컴퓨터 과학의 핵심 개념 +- 프로그래밍 언어의 기본 원리 + +### 기술 스택 (Technical Stack) +프로젝트 개발에 직접적으로 사용되는 도구들에 대한 지식입니다: +- 프로그래밍 언어와 프레임워크 +- 라이브러리와 개발 도구 +- 개발 환경과 배포 도구 + +### 프로젝트 도메인 지식 (Project Domain Knowledge) +특정 프로젝트에만 해당하는 고유한 지식입니다: +- 비즈니스 로직과 규칙 +- 프로젝트만의 아키텍처 결정사항 +- 팀 내 개발 규칙과 관례 + +:::note +**Feature-Sliced Design**은 이러한 지식 유형을 고려하여 설계되었습니다: +- 프로젝트 도메인 지식에 대한 의존도를 최소화 +- 기술 스택 지식을 체계적으로 구조화 +- 새로운 팀원의 적응 과정을 단순화 +::: + +## 참고 자료 {#see-also} + +- [(영상 🇷🇺) Ilya Klimov - 지식 유형에 관하여][ext-klimov] + +[ext-klimov]: https://youtu.be/4xyb_tA-uw0?t=249 + + diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/about/understanding/naming.md b/i18n/kr/docusaurus-plugin-content-docs/current/about/understanding/naming.md new file mode 100644 index 0000000000..dd52de7e40 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/about/understanding/naming.md @@ -0,0 +1,65 @@ +--- +sidebar_position: 4 +--- + +# 네이밍 (Naming) + +개발자들은 같은 대상을 각자의 경험과 관점에 따라 다르게 부르곤 합니다. 예를 들어: + +- UI 컴포넌트를 "ui", "components", "ui-kit", "views" 등으로 표현 +- 공통 코드를 "core", "shared", "app" 등으로 지칭 +- 비즈니스 로직을 "store", "model", "state" 등으로 명명 + +## Feature-Sliced Design의 표준 네이밍 {#naming-in-fsd} + +FSD는 다음과 같이 명확한 네이밍 규칙을 제시합니다: + +### Layers (계층) +- `app` +- `processes` +- `pages` +- `features` +- `entities` +- `shared` + +### Segments (세그먼트) +- `ui` +- `model` +- `lib` +- `api` +- `config` + +이러한 표준 용어를 사용하면: +- 팀 내 의사소통이 명확해집니다 +- 새로운 팀원의 적응이 쉬워집니다 +- 커뮤니티와의 지식 공유가 용이해집니다 + +## 네이밍 충돌 해결 {#when-can-naming-interfere} + +FSD 용어가 프로젝트의 비즈니스 용어와 중복될 수 있습니다. 예시: + +- `FSD#process` vs 애플리케이션의 시뮬레이션 프로세스, +- `FSD#page` vs 로그 페이지, +- `FSD#model` vs 자동차 모델. + +### 용어 사용 가이드 + +1. 기술적 커뮤니케이션 + - FSD 용어 사용 시 "FSD" 접두어 사용을 권장합니다 + - 예: "이 기능을 FSD features 계층으로 이동하는 것이 좋겠습니다" + +2. 비기술적 커뮤니케이션 + - FSD 관련 용어 사용을 피하고 일반적인 비즈니스 용어 사용 + - 예: 코드 구조 대신 기능이나 목적 중심으로 설명 + +## 참고 {#see-also} + +- [(토론) Naming의 적응성][disc-src] +- [(토론) Entities Naming 설문조사][disc-naming] +- [(토론) "processes" vs "flows" vs ...][disc-processes] +- [(토론) "model" vs "store" vs ...][disc-model] + +[disc-model]: https://github.com/feature-sliced/documentation/discussions/68 +[disc-naming]: https://github.com/feature-sliced/documentation/discussions/31#discussioncomment-464894 +[disc-processes]: https://github.com/feature-sliced/documentation/discussions/20 +[disc-src]: https://github.com/feature-sliced/documentation/discussions/16 diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/about/understanding/needs-driven.md b/i18n/kr/docusaurus-plugin-content-docs/current/about/understanding/needs-driven.md new file mode 100644 index 0000000000..8fb7de96ed --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/about/understanding/needs-driven.md @@ -0,0 +1,160 @@ +--- +sidebar_position: 2 +--- + +# 필요 중심 + +:::note TL;DR + +— _새로운 Feature의 목표가 불분명하거나 작업 자체가 명확히 정의되지 않았나요? **이 방법론의 핵심은 작업과 목표를 명확히 정의하는 데 있습니다.**_ + +— _프로젝트는 정적인 것이 아닙니다. 요구 사항과 Feature는 계속해서 변화하며, 시간이 지남에 따라 코드는 점점 복잡해지고 관리가 어려워집니다. **우수한 아키텍처는 이러한 변화에 유연하게 적응할 수 있어야 합니다.**_ + +::: + + +## 왜 이런 접근이 필요할까요? + +각 Entity의 이름과 구성을 명확히 하려면, **해당 코드의 목적을 정확히 이해**해야 합니다. + +> _@sergeysova: 개발할 때 각 Entity와 함수의 의도를 이름에 명확히 반영하려고 노력합니다._ + +_명확하지 않은 작업은 테스트 작성을 어렵게 만들고, 오류 처리가 비효율적이 되어 사용자 경험을 저하시킬 수 있습니다._ + +## 우리가 말하는 작업이란? + +프론트엔드는 사용자의 문제를 해결하고 요구사항을 충족하는 인터페이스를 제공합니다. + +사용자는 서비스 사용 시 **자신의 문제를 해결하거나 필요를 충족**하려 합니다. + +_관리자와 분석가는 이러한 요구사항을 명확히 정의하고, 개발자가 웹의 특성(네트워크 지연, 오류, 사용자 실수 등)을 고려하여 구현할 수 있도록 지원합니다._ + +**사용자의 목표가 곧 개발자의 작업**입니다. + +> _Feature-Sliced Design의 핵심 철학 중 하나는 — 프로젝트의 전체 작업을 더 작은 목표 단위로 나누는 것입니다._ + +## 이것이 개발에 어떤 영향을 미치는가? + +### 작업 분해 + +개발자는 코드의 유지보수성을 위해 작업을 **단계적으로 분해**합니다: + +* _최상위 Entity로 분할_ +* _더 작은 단위로 세분화_ +* _각 Entity에 명확한 이름 부여_ + +_개발자는 코드가 해결하는 작업의 본질을 반영하도록 각 Entity와 구성 요소에 명확한 이름을 부여합니다._ +_이 과정에서 모든 작업이 사용자의 문제 해결에 기여해야 함을 항상 고려합니다._ + + +### 작업의 본질 이해 + +Entity에 명확한 이름을 부여하려면 **개발자가 해당 Entity의 목적과 역할을 충분히 이해**해야 합니다: + +* Entity의 구체적인 사용 목적 +* 구현하는 사용자 작업의 범위 +* 다른 작업과의 연관성 + +결론적으로, **개발자가 Entity의 이름을 고민하는 과정에서 불명확한 작업을 미리 발견할 수 있습니다.** + +> Entity의 이름을 정의하려면 해당 Entity가 해결할 작업을 정확히 이해해야 합니다. + +## 어떻게 정의할 것인가? + +**Feature가 해결할 작업을 정의하려면 그 본질을 이해해야 합니다. 이는 주로 프로젝트 관리자와 분석가의 역할입니다.** + +_방법론은 개발자에게 작업의 방향성을 제시할 뿐입니다._ + +> _@sergeysova: 프론트엔드는 정보 표시에 초점을 맞추지만, 단순히 "무엇을 보여준다"는 것은 충분한 가치를 가지지 못합니다. "왜 이것을 보여줘야 하는가?"라는 질문을 통해 사용자의 실제 필요를 이해해야 합니다._ +> +> _이는 프론트엔드의 기술적 측면을 넘어서, 사용자의 문제와 필요를 정확히 이해할 때까지 지속적으로 탐구해야 하는 과정입니다._ + +사용자의 필요를 이해한 후에는 **제품이 어떻게 사용자의 목표 달성을 도울 수 있는지 구체화**할 수 있습니다. + +모든 새로운 작업은 비즈니스와 사용자의 문제를 동시에 해결하는 것을 목표로 합니다. 각 작업은 명시적이지 않더라도 분명한 목적을 가집니다. + +_**개발자는 작업의 목표를 명확히 이해**해야 합니다. 완벽한 프로세스가 없더라도, 관리자와의 소통을 통해 이를 파악하고 효과적으로 구현할 수 있어야 합니다._ + +## 이점 + +전체 Process를 통해 얻을 수 있는 이점을 살펴보겠습니다. + +### 1. 사용자 작업 이해 + +개발자가 사용자의 문제와 비즈니스 해결방안을 이해하면, 기술적 제약 내에서도 더 나은 솔루션을 제안할 수 있습니다. + +> _이는 개발자가 자신의 역할과 목표에 관심을 가질 때만 가능합니다. 그렇지 않다면, 방법론 자체가 의미를 잃습니다._ + +### 2. 구조화 및 정리 + +작업을 이해하면 **사고 과정, 작업, 코드가 자연스럽게 체계화**됩니다. + +### 3. 기능과 그 구성 요소 이해 + +**하나의 Feature는 사용자에게 명확한 가치를 제공해야 합니다.** + +* 여러 기능이 섞이면 **경계 위반**입니다 +* Feature는 분리와 확장이 가능합니다. **이는 자연스러운 것입니다** +* **핵심은** _"이 Feature가 사용자에게 제공하는 가치가 무엇인가?"_ 입니다 +* 예시: + * ❌ "지도-사무실" (모호한 Feature) + * ⭕ `회의실-예약`, `직원-검색`, `근무지-변경` (**명확한 Feature**) + +> _@sergeysova: Feature는 핵심 구현 코드만 포함해야 합니다. +> +> **관련 없는 코드는 제외하고, 해당 Feature의 핵심 로직만 담아야 합니다.**_ + +### 4. 유지보수성 + +비즈니스 방향은 쉽게 바뀌지 않습니다. **따라서 비즈니스 로직을 코드에 명확히 반영하면 장기적인 이점이 있습니다.** + +_그 결과, 새로운 팀원이 합류할 때마다 코드가 무엇을 하고, 왜 추가되었는지 따로 설명할 필요가 없습니다. **코드 자체에 반영된 비즈니스 작업을 통해 모든 것이 설명될 것입니다.**_ + +> [도메인 주도 설계에서 "비즈니스 언어"라고 불리는 개념입니다.][ext-ubiq-lang] + +--- + +## 현실적 고려사항 + +비즈니스 Process가 명확하고 설계가 잘 되었다면, _이를 코드로 구현하는 것은 비교적 쉽습니다._ + +**하지만 현실에서는,** 작업과 Feature가 충분한 설계 시간 없이 반복적으로 처리되는 경우가 많습니다. + +**결과적으로, 현재는 적절해 보이는 Feature가 한 달 후 확장 과정에서 전체 프로젝트 구조를 변경해야 할 수도 있습니다.** + +> *[토론에서][disc-src]: 개발자는 보통 2~3단계 앞의 요구사항을 예측하려 노력하며, 이는 경험에 크게 좌우됩니다.* +> +> _숙련된 개발자는 약 10단계 앞까지 예측할 수 있어서, Feature의 분할 지점과 통합 방식을 더 잘 이해합니다._ +> +> _그러나 때로는 경험으로도 해결하기 어려운 복잡한 상황이 발생하며, 이때는 피해를 최소화하면서 문제를 적절히 분해하기가 매우 어렵습니다._ + +## 방법론의 역할 + +**방법론은 개발자가 사용자의 문제를 더 효과적으로 해결할 수 있도록 돕는 것이 목적입니다.** + +방법론은 단순히 개발자를 위한 것이 아닙니다. + +개발자가 좋은 결과를 내려면, **사용자의 문제와 요구사항을 정확히 이해해야 합니다.** 사용자의 문제를 모르면 좋은 해결책을 만들 수 없습니다. + +### 방법론 요구 사항 + +**Feature-Sliced Design**이 충족해야 할 두 가지 요구사항: + +1. **Feature, Process, Entity를 구성하는 명확한 방법 제시** + * _코드 분할 기준과 명명 규칙을 구체적으로 정의_ + +2. **[변화하는 요구사항에 유연한 아키텍처 제공][refs-arch--adaptability]** + +## 참고 자료 + +* [(포스트) 명확한 작업 정의 가이드 (+ 토론)][disc-src] + > _**이 문서는 해당 토론을 바탕**으로 작성되었습니다. 원문은 링크를 참고하세요._ +* [(토론) Feature 분해 방법론][tg-src] +* [(아티클) "효과적인 애플리케이션 구조화"][ext-medium] + +[refs-arch--adaptability]: architecture#adaptability + +[ext-medium]: https://alexmngn.medium.com/how-to-better-organize-your-react-applications-2fd3ea1920f1 +[disc-src]: https://t.me/sergeysova/318 +[tg-src]: https://t.me/atomicdesign/18972 +[ext-ubiq-lang]: https://thedomaindrivendesign.io/developing-the-ubiquitous-language diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/get-started/faq.md b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/faq.md new file mode 100644 index 0000000000..4b1d8468f4 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/faq.md @@ -0,0 +1,69 @@ +--- +sidebar_position: 20 +pagination_next: guides/index +--- + +# FAQ + +:::info + +여러분은 [Telegram chat][telegram], [Discord community][discord] 그리고 [GitHub Discussions][github-discussions]에서 질문을 할 수 있습니다. + +::: + +### toolkit이나 linter가 있나요? + +네! 우리는 CLI 또는 IDE를 통해 프로젝트의 아키텍처와 [폴더 생성기][ext-tools]를 확인하기 위한 [Steiger][ext-steiger]라는 linter를 가지고 있습니다. + +### Where to store the layout/template of pages? + +순수한 마크업 레이아웃이 필요하다면 `shared/ui`에 보관할 수 있습니다. 상위 계층을 사용해야 한다면 몇 가지 옵션이 있습니다. + +- 레이아웃이 필요 없을 수도 있습니다. 레이아웃이 몇 줄밖에 안 된다면, 추상화하려고 하기보다는 각 페이지에서 코드를 중복하는 것이 합리적일 수 있습니다. +- 레이아웃이 필요하다면, 별도의 위젯이나 페이지로 만들고 App의 라우터 설정에서 조합할 수 있습니다. 중첩 라우팅도 다른 옵션입니다. + +### feature와 entity의 차이점이 무엇인가요? + +*entity*는 앱이 다루는 실제 개념입니다. *feature*는 앱 사용자에게 실제 가치를 제공하는 상호작용, 즉 사람들이 entity로 하고 싶어하는 것입니다. + +더 자세한 정보와 예시는 [slices][reference-entities] 참조 페이지를 확인하세요. + +### pages/features/entities를 서로 포함시킬 수 있나요? + +네, 하지만 이런 포함은 상위 계층에서 이루어져야 합니다. 예를 들어, 위젯 내부에서 여러 기능을 가져와서 하나의 기능을 다른 기능의 props/children으로 삽입할 수 있습니다. + +한 기능을 다른 기능에서 가져올 수는 없습니다. 이는 [**계층에 대한 가져오기 규칙**][import-rule-layers]에 의해 금지됩니다. + +### 아토믹 디자인은 어떤가요? + +현재 버전의 방법론은 Feature-Sliced Design과 함께 아토믹 디자인을 사용하는 것을 요구하지도, 금지하지도 않습니다. + +예를 들어, 아토믹 디자인은 모듈의 `ui` 세그먼트에 [잘 적용될 수 있습니다](https://t.me/feature_sliced/1653). + +### FSD에 대한 유용한 리소스/기사 등이 있나요? + +네! https://github.com/feature-sliced/awesome 를 참조하세요. + +### Feature-Sliced Design이 왜 필요한가요? + +프로젝트를 주요 가치 창출 구성 요소 측면에서 빠르게 개요를 파악하는 데 도움이 됩니다. 표준화된 아키텍처는 온보딩 속도를 높이고 코드 구조에 대한 논쟁을 해결합니다. FSD가 만들어진 이유에 대해 더 자세히 알아보려면 [동기][motivation] 페이지를 참조하세요. + +### 초보 개발자에게 아키텍처/방법론이 필요한가요? + +그렇다고 볼 수 있습니다. + +*보통 한 사람이 프로젝트를 설계하고 개발할 때는 모든 것이 순조롭게 진행됩니다. 하지만 개발에 중단이 있거나 새로운 개발자가 팀에 합류하면 문제가 발생합니다* + + +### 인증 컨텍스트는 어떻게 다루나요? + +[여기](/docs/guides/examples/auth)에서 답변했습니다. + +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools +[import-rule-layers]: /docs/reference/layers#import-rule-on-layers +[reference-entities]: /docs/reference/layers#entities +[motivation]: /docs/about/motivation +[telegram]: https://t.me/feature_sliced +[discord]: https://discord.gg/S8MzWTUsmp +[github-discussions]: https://github.com/feature-sliced/documentation/discussions diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/get-started/index.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/index.mdx new file mode 100644 index 0000000000..39f2ac22a7 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/index.mdx @@ -0,0 +1,38 @@ +--- +hide_table_of_contents: true +pagination_prev: intro +--- + +import { PlaySquareOutlined, QuestionCircleOutlined, RocketOutlined } from "@ant-design/icons"; +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx"; + +# 🚀 Get Started + +

+환영합니다! 이 섹션은 Feature-Sliced Design의 적용과 방법론의 기본을 익히는 데 도움을 줍니다. 또한 이 방법론의 주요 장점과 그것이 만들어진 이유를 이해하게 될 것입니다. +

+ + + + +{/* */} diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/get-started/overview.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/overview.mdx new file mode 100644 index 0000000000..23967ce248 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/overview.mdx @@ -0,0 +1,137 @@ +--- +sidebar_position: 1 +--- + +# 둘러보기 + +**Feature-Sliced Design** (FSD)는 프론트엔드 애플리케이션의 구조를 잡는 아키텍처 방법론입니다. 간단히 말해, 코드 구성에 관한 규칙과 관례를 모아놓은 것입니다. 이 방법론의 주요 목적은 계속 변화하는 비즈니스 요구사항에 직면했을 때 프로젝트를 더 이해하기 쉽고 안정적으로 만드는 것입니다. + +FSD는 규칙을 제시할 뿐만 아니라, 실제 개발 과정을 돕는 다양한 도구들을 함께 제공합니다. 프로젝트 아키텍처를 확인하는 [린터][ext-steiger], CLI나 IDE를 통한 [폴더 생성기][ext-tools], 그리고 다양한 [예제][examples] 라이브러리를 제공합니다. + +## 내 프로젝트에 적합할까? {#is-it-right-for-me} + +FSD는 규모에 관계 없이 모든 팀과 프로젝트에서 사용할 수 있습니다. 다음과 같은 경우 여러분의 프로젝트에 적합합니다: + +- 여러분이 **프론트엔드**를 개발하고 있다면 (웹, 모바일, 데스크톱 등의 UI) +- 여러분이 라이브러리가 아닌 **애플리케이션**을 만들고 있다면 + +그게 다예요! 사용하는 프로그래밍 언어, UI 프레임워크, 상태 관리의 제한은 없습니다. FSD를 점진적으로 도입할 수 있고, 모노레포에서 사용할 수 있으며, 앱을 패키지로 분할하고 각각에 FSD를 개별적으로 적용하여 큰 규모로 확장할 수 있습니다. + +이미 아키텍처가 있고 FSD로의 전환을 고려 중이라면, 현재 아키텍처가 **문제를 일으키고 있는지** 확인하세요. 예를 들어, 프로젝트가 너무 커지고 상호 연결되어 새로운 기능을 효율적으로 구현하기 어려워졌거나, 많은 사람이 새롭게 팀에 합류할 것으로 예상되는 경우입니다. 현재 아키텍처가 잘 작동한다면 변경할 필요가 없을 수도 있습니다. 하지만 마이그레이션을 결정했다면, [마이그레이션][migration] 가이드를 확인하세요. + +## 간단한 예제 {#basic-example} + +다음은 FSD를 구현한 간단한 프로젝트입니다: + +- `📁 app` +- `📁 pages` +- `📁 shared` + +이 최상위 폴더들을 *레이어*(layers)라고 합니다. 더 자세히 살펴보겠습니다: + +- `📂 app` + - `📁 routes` + - `📁 analytics` +- `📂 pages` + - `📁 home` + - `📂 article-reader` + - `📁 ui` + - `📁 api` + - `📁 settings` +- `📂 shared` + - `📁 ui` + - `📁 api` + +📂 pages 내부의 폴더들을 *슬라이스*(slices)라고 합니다. 슬라이스는 도메인에 따라 레이어를 나눕니다.(이 경우에는 페이지별로) + +`📂 app`, `📂 shared`, 그리고 `📂 pages/article-reader` 내부의 폴더들을 *세그먼트*(segments)라고 합니다. 세그먼트는 코드의 기능적 목적, 다시말해 코드가 수행하는 역할 따라 슬라이스(또는 레이어)를 나눕니다. + +## 개념 {#concepts} + +레이어, 슬라이스, 세그먼트는 다음과 같은 계층 구조를 형성합니다: + +
+ ![아래에 설명된 FSD 개념의 계층 구조](/img/visual_schema.jpg) + +
+

위 그림: 세 개의 수직 구조가 있으며, 왼쪽부터 오른쪽으로 각각 "레이어", "슬라이스", "세그먼트"로 표시되어 있습니다.

+

"레이어"에는 위에서 아래로 7개의 구분이 있으며, "app", "processes", "pages", "widgets", "features", "entities", "shared"로 표시되어 있습니다. "processes" 구분은 취소선이 그어져 있습니다. "entities" 구분은 두 번째 기둥인 "슬라이스"와 연결되어 있어, 두 번째 기둥이 "entities"의 내용이라는 것을 나타냅니다.

+

"슬라이스"에는 위에서 아래로 3개의 구분이 있으며, "user", "post", "comment"로 표시되어 있습니다. "post" 구분은 세 번째 기둥인 "세그먼트"와 연결되어 있어, 세 번째 기둥이 "post"의 내용임을 나타냅니다.

+

"세그먼트"에는 위에서 아래로 3개의 구분이 있으며, "ui", "model", "api"로 표시되어 있습니다.

+
+
+ +### 레이어 {#layers} + +레이어는 모든 FSD 프로젝트에서 표준화되어 있습니다. 모든 레이어를 사용할 필요는 없지만, 이름은 중요합니다. 현재(위에서 아래로) 7개가 있습니다: + +1. **App\*** - 앱을 실행하는 모든 것 - 라우팅, 진입점, 전역 스타일, 프로바이더. +2. **Processes**(더 이상 사용되지 않음) - 페이지 간 복잡한 시나리오. +3. **Pages** - 전체 페이지 또는 중첩 라우팅에서 페이지의 주요 부분. +4. **Widgets** - 독립적으로 작동하는 대규모 기능 또는 UI 컴포넌트, 보통 하나의 완전한 기능. +5. **Features** - 제품 전반에 걸쳐 재사용되는 기능 구현체로, 사용자에게 실질적인 비즈니스 가치를 제공하는 동작. +6. **Entities** - 프로젝트가 다루는 비즈니스 엔티티, 예를 들어 user 또는 product. +7. **Shared*** - 재사용 가능한 기능, 특히 프로젝트/비즈니스의 특성과 분리되어 있을 때 (반드시 그럴 필요는 없음). + +_\* - **App**과 **Shared**는 다른 레이어들과 달리 슬라이스를 가지지 않으며, 직접 세그먼트로 구성됩니다._ + +레이어를 다룰 때의 중요한 점은 한 레이어의 구성 요소는 반드시 아래에 있는 레이어의 구성 요소만 알수있고 임포트할 수 있다는 것입니다. + + +### 슬라이스 {#slices} + +다음은 슬라이스입니다. 슬라이스는 비즈니스 도메인별로 코드를 분할합니다. 여러분은 자유롭게 이름을 선택할 수 있고, 원하는 만큼 많이 만들 수 있습니다. 슬라이스는 논리적으로 관련된 모듈들을 가까이 유지함으로써 코드베이스를 더 쉽게 탐색할 수 있게 해줍니다. + +슬라이스는 같은 레이어 안에서 다른 슬라이스를 참조할 수 없으며, 이 규칙은 높은 응집도와 낮은 결합도를 유지하는 데 도움이 됩니다. + +### 세그먼트 {#segments} + +슬라이스와 App, Shared 레이어는 세그먼트로 구성되며, 세그먼트는 목적에 따라 코드를 그룹화합니다. 세그먼트 이름은 표준에 의해 제한되지 않지만, 가장 일반적인 목적을 위한 몇 가지 관례적인 이름이 있습니다.: + +- `ui` - UI와 관련된 모든 것: UI 컴포넌트, 날짜 포맷터, 스타일 등. +- `api` - 백엔드 상호작용: request 함수, 데이터 타입, mapper 등. +- `model` - 데이터 모델: 스키마, 인터페이스, 스토어, 비즈니스 로직. +- `lib` - 슬라이스 안에 있는 다른 모듈이 필요로 하는 라이브러리 코드. +- `config` - 설정 파일과 기능 플래그. + +대부분의 레이어에서는 이 세그먼트들로 충분하며, Shared나 App에서만 자신만의 세그먼트를 만들 것입니다. 하지만 이건 꼭 지켜야하는 규칙은 아닙니다. + +## 장점 {#advantages} + +- **균일성** + 구조가 표준화되어 있어, 프로젝트가 더 균일해지며, 이는 새로운 멤버가 팀에 적응하는 것을 더 쉽게 만듭니다. + +- **변경과 리팩토링에 대한 안정성** + 한 레이어의 구성 요소는 같은 레이어나 상위 레이어의 다른 구성 요소를 사용할 수 없습니다. 이로 인해 앱의 사이드 이펙트 없이 격리된 수정을 할 수 있습니다. + +- **로직 재사용을 통제** + 레이어에 따라 코드를 매우 재사용 가능하게 또는 매우 지역적으로 만들 수 있습니다. 이는 **DRY** 원칙과 실용성 사이의 균형을 유지합니다. + +- **비즈니스와 사용자 요구에 대한 지향성** + 앱은 비즈니스 도메인으로 분할되며, 이름을 지을 때 비즈니스 언어 사용이 권장됩니다. 이로 인해 프로젝트와 무관한 부분을 완전히 이해하지 않고도 유용한 개발을 할 수 있습니다. + +## 점진적 도입 {#incremental-adoption} + +FSD로 마이그레이션하고자 하는 기존 코드베이스가 있다면, 다음과 같은 전략을 제안합니다. 이는 우리의 마이그레이션 경험에서 유용했던 방법입니다. + +1. App과 Shared 레이어를 모듈별로 천천히 구성하여 기반을 만드는 것으로 시작하세요. + +2. 기존의 모든 UI를 Widgets와 Pages에 큰 틀에서 분배하세요. FSD 규칙을 위반하는 의존성이 있더라도 괜찮습니다. + +3. 점진적으로 임포트 위반을 해결하고, Entities와 가능하다면 Features도 추출하기 시작하세요. + +리팩토링 중이거나 프로젝트의 특정 부분만 리팩토링할 때는 새로운 대규모 엔티티를 추가하지 않는 것이 좋습니다. + +## 다음 단계 {#next-steps} + +- **FSD로 사고하는 방법을 잘 이해하고 싶으신가요?** [튜토리얼][tutorial]을 확인해보세요. +- **예제를 통해 배우는 것을 선호하시나요?** [예제][examples] 섹션에 많은 예제가 있습니다. +- **질문이 있으신가요?** 우리의 [텔레그램 채팅방][ext-telegram]에 들러 커뮤니티의 도움을 받으세요. + +[tutorial]: /docs/get-started/tutorial +[examples]: /examples +[migration]: /docs/guides/migration/from-custom +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools +[ext-telegram]: https://t.me/feature_sliced + diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/get-started/tutorial.md b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/tutorial.md new file mode 100644 index 0000000000..ba8c07b495 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/tutorial.md @@ -0,0 +1,2270 @@ +--- +sidebar_position: 2 +--- +# 튜토리얼 + +## Part 1. 설계 + +이 튜토리얼에서는 Real World App이라고도 알려진 Conduit를 살펴보겠습니다. Conduit는 기본적인 [Medium](https://medium.com/) 클론입니다 - 글을 읽고 쓸 수 있으며 다른 사람의 글에 댓글을 달 수 있습니다. + +![Conduit home page](/img/tutorial/realworld-feed-anonymous.jpg) + +이 애플리케이션은 매우 작은 애플리케이션이므로 과도한 분해를 피하고 간단하게 유지할 것입니다. 전체 애플리케이션이 세 개의 레이어인 **App**, **Pages**, 그리고 **Shared**에 맞춰 들어갈 것입니다. 그렇지 않다면 우리는 계속해서 추가적인 레이어를 도입할 것입니다. 준비되셨나요? + +### 먼저 페이지를 나열해 봅시다. + +위의 스크린샷을 보면 최소한 다음과 같은 페이지들이 있다고 가정할 수 있습니다: + +- 홈 (글 피드) +- 로그인 및 회원가입 +- 글 읽기 +- 글 편집기 +- 사용자 프로필 보기 +- 사용자 프로필 편집 (사용자 설정) + +이 페이지들 각각은 Pages *레이어*의 독립된 *슬라이스*가 될 것입니다. 개요에서 언급했듯이 슬라이스는 단순히 레이어 내의 폴더이고, 레이어는 `pages`와 같은 미리 정의된 이름을 가진 폴더일 뿐입니다. + +따라서 우리의 Pages 폴더는 다음과 같이 보일 것입니다. + +``` +📂 pages/ + 📁 feed/ + 📁 sign-in/ + 📁 article-read/ + 📁 article-edit/ + 📁 profile/ + 📁 settings/ +``` + +Feature-Sliced Design이 규제되지 않은 코드 구조와 다른 주요 차이점은 페이지들이 서로를 참조할 수 없다는 것입니다. 즉, 한 페이지가 다른 페이지의 코드를 가져올 수 없습니다. 이는 **레이어의 import 규칙** 때문입니다. + +*슬라이스의 모듈은 엄격히 아래에 있는 레이어에 위치한 다른 슬라이스만 가져올 수 있습니다.* + +이 경우 페이지는 슬라이스이므로, 이 페이지 내의 모듈(파일)은 같은 레이어인 Pages가 아닌 아래 레이어의 코드만 참조할 수 있습니다. + +### 피드 자세히 보기 + +
+ ![Anonymous user’s perspective](/img/tutorial/realworld-feed-anonymous.jpg) +
+ _익명 사용자의 관점_ +
+
+ +
+ ![Authenticated user’s perspective](/img/tutorial/realworld-feed-authenticated.jpg) +
+ _인증된 사용자의 관점_ +
+
+ +피드 페이지에는 세 가지 동적 영역이 있습니다. + +1. 로그인 여부를 나타내는 로그인 링크 +2. 피드에서 필터링을 트리거하는 태그 목록 +3. 좋아요 버튼이 있는 하나/두 개의 글 피드 + +로그인 링크는 모든 페이지에 공통적인 헤더의 일부이므로 나중에 따로 다루겠습니다. + +#### 태그 목록 + +태그 목록을 만들기 위해서는 사용 가능한 태그를 가져오고, 각 태그를 칩으로 렌더링하고, 선택된 태그를 클라이언트 측 저장소에 저장해야 합니다. 이러한 작업들은 각각 "API 상호작용", "사용자 인터페이스", "저장소" 카테고리에 속합니다. Feature-Sliced Design에서는 코드를 *세그먼트*를 사용하여 목적별로 분리합니다. 세그먼트는 슬라이스 내의 폴더이며, 목적을 설명하는 임의의 이름을 가질 수 있지만, 일부 목적은 너무 일반적이어서 특정 세그먼트 이름에 대한 규칙이 있습니다. + + +- 📂 `api/` 백엔드 상호작용 +- 📂 `ui/` 렌더링과 외관을 다루는 코드 +- 📂 `model/` 저장소와 비즈니스 로직 +- 📂 `config/` 기능 플래그, 환경 변수 및 기타 구성 형식 + +태그를 가져오는 코드는 `api`에, 태그 컴포넌트는 `ui`에, 저장소 상호작용은 `model`에 배치할 것입니다. + +#### 글 + +같은 그룹화 원칙을 사용하여 글 피드를 같은 세 개의 세그먼트로 분해할 수 있습니다. + +- 📂 `api/`: 좋아요 수가 포함된 페이지네이션된 글 가져오기 +- 📂 `ui/`: + - 태그가 선택된 경우 추가 탭을 렌더링할 수 있는 탭 목록 + - 개별 글 + - 기능적 페이지네이션 +- 📂 `model/`: 현재 로드된 글과 현재 페이지의 클라이언트 측 저장소 (필요한 경우) + +### 일반적인 코드 재사용 + +대부분의 페이지는 의도가 매우 다르지만, 앱 전체에 걸쳐 일부 요소는 동일하게 유지됩니다. 예를 들어, 디자인 언어를 준수하는 UI 키트나 모든 것이 동일한 인증 방식으로 REST API를 통해 수행되는 백엔드의 규칙 등이 있습니다. 슬라이스는 격리되도록 설계되었기 때문에, 코드 재사용은 더 낮은 계층인 **Shared**에 의해 촉진됩니다. + + +Shared는 슬라이스가 아닌 세그먼트를 포함한다는 점에서 다른 계층과 다릅니다. 이런 면에서 Shared 계층은 계층과 슬라이스의 하이브리드로 생각할 수 있습니다. + +일반적으로 Shared의 코드는 미리 계획되지 않고 개발 중에 추출됩니다. 실제로 어떤 코드 부분이 공유되는지는 개발 중에만 명확해지기 때문입니다. 그러나 어떤 종류의 코드가 자연스럽게 Shared에 속하는지 머릿속에 메모해 두는 것은 여전히 도움이 됩니다. + + +- 📂 `ui/` — UI 키트, 비즈니스 로직이 없는 순수한 UI. 예: 버튼, 모달 대화 상자, 폼 입력. +- 📂 `api/` — 요청 생성 기본 요소(예: 웹의 `fetch()`)에 대한 편의 래퍼 및 선택적으로 백엔드 사양에 따라 특정 요청을 트리거하는 함수. +- 📂 `config/` — 환경 변수 파싱 +- 📂 `i18n/` — 언어 지원에 대한 구성 +- 📂 `router/` — 라우팅 기본 요소 및 라우트 상수 + +이는 Shared의 세그먼트 이름의 몇 가지 예시일 뿐이며, 이 중 일부를 생략하거나 자신만의 세그먼트를 만들 수 있습니다. 새로운 세그먼트를 만들 때 기억해야 할 유일한 중요한 점은 세그먼트 이름이 **본질(무엇인지)이 아닌 목적(왜)을 설명해야 한다**는 것입니다. "components", "hooks", "modals"과 같은 이름은 이 파일들이 무엇인지는 설명하지만 내부 코드를 탐색하는 데 도움이 되지 않기 때문에 사용해서는 안 됩니다. 이는 팀원들이 이러한 폴더의 모든 파일을 파헤쳐야 하며, 관련 없는 코드를 가까이 유지하게 되어 리팩토링의 영향을 받는 코드 영역이 넓어지고 결과적으로 코드 리뷰와 테스트를 더 어렵게 만듭니다. + +### 엄격한 공개 API 정의 + +Feature-Sliced Design의 맥락에서 *공개 API*라는 용어는 슬라이스나 세그먼트가 프로젝트의 다른 모듈에서 가져올 수 있는 것을 선언하는 것을 의미합니다. 예를 들어, JavaScript에서는 슬라이스의 다른 파일에서 객체를 다시 내보내는 `index.js` 파일일 수 있습니다. 이를 통해 외부 세계와의 계약(즉, 공개 API)이 동일하게 유지되는 한 슬라이스 내부의 코드를 자유롭게 리팩토링할 수 있습니다. + +슬라이스가 없는 Shared 계층의 경우, Shared의 모든 것에 대한 단일 인덱스를 정의하는 것과 반대로 각 세그먼트에 대해 별도의 공개 API를 정의하는 것이 일반적으로 더 편리합니다. 이렇게 하면 Shared에서의 가져오기가 자연스럽게 의도별로 구성됩니다. 슬라이스가 있는 다른 계층의 경우 반대가 사실입니다 — 일반적으로 슬라이스당 하나의 인덱스를 정의하고 슬라이스가 외부 세계에 알려지지 않은 자체 세그먼트 세트를 결정하도록 하는 것이 더 실용적입니다. 다른 계층은 일반적으로 내보내기가 훨씬 적기 때문입니다. + +우리의 슬라이스/세그먼트는 서로에게 다음과 같이 나타날 것입니다. + +``` +📂 pages/ + 📂 feed/ + 📄 index + 📂 sign-in/ + 📄 index + 📂 article-read/ + 📄 index + 📁 … +📂 shared/ + 📂 ui/ + 📄 index + 📂 api/ + 📄 index + 📁 … +``` + +`pages/feed`나 `shared/ui`와 같은 폴더 내부의 내용은 해당 폴더에만 알려져 있으며, 다른 파일은 이러한 폴더의 내부 구조에 의존해서는 안 됩니다. + + +### UI의 큰 재사용 블록 + +앞서 모든 페이지에 나타나는 헤더를 다시 살펴보기로 했습니다. 모든 페이지에서 처음부터 다시 만드는 것은 비실용적이므로 재사용하고 싶을 것입니다. 우리는 이미 코드 재사용을 용이하게 하는 Shared를 가지고 있지만, Shared에 큰 UI 블록을 넣는 데는 주의할 점이 있습니다 — Shared 계층은 위의 계층에 대해 알지 못해야 합니다. + +Shared와 Pages 사이에는 Entities, Features, Widgets의 세 가지 다른 계층이 있습니다. 일부 프로젝트는 이러한 계층에 큰 재사용 가능한 블록에 필요한 것이 있을 수 있으며, 이는 해당 재사용 가능한 블록을 Shared에 넣을 수 없다는 것을 의미합니다. 그렇지 않으면 상위 계층에서 가져오게 되어 금지됩니다. 이것이 Widgets 계층이 필요한 이유입니다. Widgets는 Shared, Entities, Features 위에 위치하므로 이들 모두를 사용할 수 있습니다. + +우리의 경우, 헤더는 매우 간단합니다 — 정적 로고와 최상위 탐색입니다. 탐색은 사용자가 현재 로그인했는지 여부를 확인하기 위해 API에 요청을 해야 하지만, 이는 `api` 세그먼트에서 간단한 가져오기로 처리할 수 있습니다. 따라서 우리는 헤더를 Shared에 유지할 것입니다. + +### 폼이 있는 페이지 자세히 보기 + +읽기가 아닌 편집을 위한 페이지도 살펴보겠습니다. + +![Conduit post editor](/img/tutorial/realworld-editor-authenticated.jpg) + +간단해 보이지만, 폼 유효성 검사, 오류 상태, 데이터 지속성 등 아직 탐구하지 않은 애플리케이션 개발의 여러 측면을 포함하고 있습니다. + +이 페이지를 만들려면 Shared에서 일부 입력과 버튼을 가져와 이 페이지의 `ui` 세그먼트에서 폼을 구성할 것입니다. 그런 다음 `api` 세그먼트에서 백엔드에 글을 생성하는 변경 요청을 정의할 것입니다. + +요청을 보내기 전에 유효성을 검사하려면 유효성 검사 스키마가 필요하며, 이를 위한 좋은 위치는 데이터 모델이기 때문에 `model` 세그먼트입니다. 여기서 오류 메시지를 생성하고 `ui` 세그먼트의 다른 컴포넌트를 사용하여 표시할 것입니다. + +사용자 경험을 개선하기 위해 우발적인 데이터 손실을 방지하기 위해 입력을 지속시킬 수도 있습니다. 이것도 `model` 세그먼트의 작업입니다. + +### 요약 + +우리는 여러 페이지를 검토하고 애플리케이션의 예비 구조를 개략적으로 설명했습니다. + +1. Shared layer + 1. `ui`는 재사용 가능한 UI 키트를 포함할 것입니다. + 2. `api`는 백엔드와의 기본적인 상호작용을 포함할 것입니다. + 3. 나머지는 필요에 따라 정리될 것입니다. +2. Pages layer — 각 페이지는 별도의 슬라이스입니다. + 1. `ui`는 페이지 자체와 모든 부분을 포함할 것입니다. + 2. `api`는 `shared/api`를 사용하여 더 특화된 데이터 가져오기를 포함할 것입니다. + 3. `model`은 표시할 데이터의 클라이언트 측 저장소를 포함할 수 있습니다. + +이제 코드 작성을 시작해 봅시다! + +## Part 2. 코드 작성 + +이제 설계를 완료했으니 실제로 코드를 작성해 봅시다. React와 [Remix](https://remix.run)를 사용할 것입니다. + +이 프로젝트를 위한 템플릿이 준비되어 있습니다. GitHub에서 클론하여 시작하세요. [https://github.com/feature-sliced/tutorial-conduit/tree/clean](https://github.com/feature-sliced/tutorial-conduit/tree/clean). + +`npm install`로 의존성을 설치하고 `npm run dev`로 개발 서버를 시작하세요. [http://localhost:3000](http://localhost:3000)을 열면 빈 앱이 보일 것입니다. + + +### 페이지 레이아웃 + +모든 페이지에 대한 빈 컴포넌트를 만드는 것부터 시작하겠습니다. 프로젝트에서 다음 명령을 실행하세요. + +```bash +npx fsd pages feed sign-in article-read article-edit profile settings --segments ui +``` + +이렇게 하면 `pages/feed/ui/`와 같은 폴더와 모든 페이지에 대한 인덱스 파일인 `pages/feed/index.ts`가 생성됩니다. + +### 피드 페이지 연결 + +애플리케이션의 루트 경로를 피드 페이지에 연결해 봅시다. `pages/feed/ui`에 `FeedPage.tsx` 컴포넌트를 만들고 다음 내용을 넣으세요: + +```tsx title="pages/feed/ui/FeedPage.tsx" +export function FeedPage() { + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+
+ ); +} +``` + +그런 다음 피드 페이지의 공개 API인 `pages/feed/index.ts` 파일에서 이 컴포넌트를 다시 내보내세요. + +```ts title="pages/feed/index.ts" +export { FeedPage } from "./ui/FeedPage"; +``` + +이제 루트 경로에 연결합니다. Remix에서 라우팅은 파일 기반이며, 라우트 파일은 `app/routes` 폴더에 있어 Feature-Sliced Design과 잘 맞습니다. + +`app/routes/_index.tsx`에서 `FeedPage` 컴포넌트를 사용하세요. + +```tsx title="app/routes/_index.tsx" +import type { MetaFunction } from "@remix-run/node"; +import { FeedPage } from "pages/feed"; + +export const meta: MetaFunction = () => { + return [{ title: "Conduit" }]; +}; + +export default FeedPage; +``` + +그런 다음 개발 서버를 실행하고 애플리케이션을 열면 Conduit 배너가 보일 것입니다! + +![The banner of Conduit](/img/tutorial/conduit-banner.jpg) + +### API 클라이언트 + +RealWorld 백엔드와 통신하기 위해 Shared에 편리한 API 클라이언트를 만들어 봅시다. 클라이언트를 위한 `api`와 백엔드 기본 URL과 같은 변수를 위한 `config`, 두 개의 세그먼트를 만드세요. + + +```bash +npx fsd shared --segments api config +``` + +그런 다음 `shared/config/backend.ts`를 만드세요. + +```tsx title="shared/config/backend.ts" +export const backendBaseUrl = "https://api.realworld.io/api"; +``` + +```tsx title="shared/config/index.ts" +export { backendBaseUrl } from "./backend"; +``` + +RealWorld 프로젝트는 편리하게 [OpenAPI 사양](https://github.com/gothinkster/realworld/blob/main/api/openapi.yml)을 제공하므로, 클라이언트를 위한 자동 생성 타입을 활용할 수 있습니다. 추가 타입 생성기가 포함된 [`openapi-fetch` 패키지](https://openapi-ts.pages.dev/openapi-fetch/)를 사용할 것입니다. + +다음 명령을 실행하여 최신 API 타입을 생성하세요. + +```bash +npm run generate-api-types +``` + +이렇게 하면 `shared/api/v1.d.ts` 파일이 생성됩니다. 이 파일을 사용하여 `shared/api/client.ts`에 타입이 지정된 API 클라이언트를 만들 것입니다. + +```tsx title="shared/api/client.ts" +import createClient from "openapi-fetch"; + +import { backendBaseUrl } from "shared/config"; +import type { paths } from "./v1"; + +export const { GET, POST, PUT, DELETE } = createClient({ baseUrl: backendBaseUrl }); +``` + +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; +``` + +### 피드의 실제 데이터 + +이제 백엔드에서 가져온 글을 피드에 추가할 수 있습니다. 글 미리보기 컴포넌트를 구현하는 것부터 시작하겠습니다. + +다음 내용으로 `pages/feed/ui/ArticlePreview.tsx`를 만드세요. + +```tsx title="pages/feed/ui/ArticlePreview.tsx" +export function ArticlePreview({ article }) { /* TODO */ } +``` + +TypeScript를 사용하고 있으므로 글 객체에 타입을 지정하면 좋을 것 같습니다. 생성된 `v1.d.ts`를 살펴보면 글 객체가 `components["schemas"]["Article"]`을 통해 사용 가능한 것을 볼 수 있습니다. 그럼 Shared에 데이터 모델이 있는 파일을 만들고 모델을 내보내겠습니다. + +```tsx title="shared/api/models.ts" +import type { components } from "./v1"; + +export type Article = components["schemas"]["Article"]; +``` + +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; + +export type { Article } from "./models"; +``` + +이제 글 미리보기 컴포넌트로 돌아가 데이터로 마크업을 채울 수 있습니다. 컴포넌트를 다음 내용으로 업데이트하세요. + +```tsx title="pages/feed/ui/ArticlePreview.tsx" +import { Link } from "@remix-run/react"; +import type { Article } from "shared/api"; + +interface ArticlePreviewProps { + article: Article; +} + +export function ArticlePreview({ article }: ArticlePreviewProps) { + return ( +
+
+ + + +
+ + {article.author.username} + + + {new Date(article.createdAt).toLocaleDateString(undefined, { + dateStyle: "long", + })} + +
+ +
+ +

{article.title}

+

{article.description}

+ Read more... +
    + {article.tagList.map((tag) => ( +
  • + {tag} +
  • + ))} +
+ +
+ ); +} +``` + +좋아요 버튼은 지금은 아무 작업도 하지 않습니다. 글 읽기 페이지를 만들고 좋아요 기능을 구현할 때 수정하겠습니다. + +이제 글을 가져와서 이러한 카드를 여러 개 렌더링할 수 있습니다. Remix에서 데이터 가져오기는 *로더* — 페이지가 필요로 하는 것을 정확히 가져오는 서버 측 함수 — 를 통해 수행됩니다. 로더는 페이지를 대신하여 API와 상호 작용하므로 페이지의 `api` 세그먼트에 넣을 것입니다: + +```tsx title="pages/feed/api/loader.ts" +import { json } from "@remix-run/node"; + +import { GET } from "shared/api"; + +export const loader = async () => { + const { data: articles, error, response } = await GET("/articles"); + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return json({ articles }); +}; +``` + +페이지에 연결하려면 라우트 파일에서 `loader`라는 이름으로 내보내야 합니다. + +```tsx title="pages/feed/index.ts" +export { FeedPage } from "./ui/FeedPage"; +export { loader } from "./api/loader"; +``` + +```tsx title="app/routes/_index.tsx" +import type { MetaFunction } from "@remix-run/node"; +import { FeedPage } from "pages/feed"; + +export { loader } from "pages/feed"; + +export const meta: MetaFunction = () => { + return [{ title: "Conduit" }]; +}; + +export default FeedPage; +``` + +마지막 단계는 피드에 이러한 카드를 렌더링하는 것입니다. `FeedPage`를 다음 코드로 업데이트하세요. + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { useLoaderData } from "@remix-run/react"; + +import type { loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; + +export function FeedPage() { + const { articles } = useLoaderData(); + + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ {articles.articles.map((article) => ( + + ))} +
+
+
+
+ ); +} +``` + +### 태그로 필터링 + +태그와 관련해서는 백엔드에서 태그를 가져오고 현재 선택된 태그를 저장해야 합니다. 가져오기 방법은 이미 알고 있습니다 — 로더에서 또 다른 요청을 하면 됩니다. `remix-utils` 패키지에서 `promiseHash`라는 편리한 함수를 사용할 것입니다. 이 패키지는 이미 설치되어 있습니다. + +로더 파일인 `pages/feed/api/loader.ts`를 다음 코드로 업데이트하세요. + +```tsx title="pages/feed/api/loader.ts" +import { json } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +export const loader = async () => { + return json( + await promiseHash({ + articles: throwAnyErrors(GET("/articles")), + tags: throwAnyErrors(GET("/tags")), + }), + ); +}; +``` + + +오류 처리를 일반 함수 `throwAnyErrors`로 추출했다는 점에 주목하세요. 꽤 유용해 보이므로 나중에 재사용할 수 있을 것 같습니다. 지금은 그냥 주목해 두겠습니다. + +이제 태그 목록으로 넘어갑시다. 이는 상호작용이 가능해야 합니다 — 태그를 클릭하면 해당 태그가 선택되어야 합니다. Remix 규칙에 따라 URL 검색 매개변수를 선택된 태그의 저장소로 사용할 것입니다. 브라우저가 저장을 처리하게 하고 우리는 더 중요한 일에 집중하겠습니다. + +`pages/feed/ui/FeedPage.tsx`를 다음 코드로 업데이트하세요. + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { Form, useLoaderData } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import type { loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; + +export function FeedPage() { + const { articles, tags } = useLoaderData(); + + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ {articles.articles.map((article) => ( + + ))} +
+ +
+
+

Popular Tags

+ +
+ +
+ {tags.tags.map((tag) => ( + + ))} +
+ +
+
+
+
+
+ ); +} +``` + +그런 다음 로더에서 `tag` 검색 매개변수를 사용해야 합니다. `pages/feed/api/loader.ts`의 `loader` 함수를 다음과 같이 변경하세요. + +```tsx title="pages/feed/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const selectedTag = url.searchParams.get("tag") ?? undefined; + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles", { params: { query: { tag: selectedTag } } }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); +}; +``` + +이게 전부입니다. `model` 세그먼트가 필요하지 않습니다. Remix는 꽤 깔끔하죠. + +### 페이지네이션 + +비슷한 방식으로 페이지네이션을 구현할 수 있습니다. 직접 시도해 보거나 아래 코드를 복사하세요. 어차피 당신을 판단할 사람은 없습니다. + +```tsx title="pages/feed/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +/** Amount of articles on one page. */ +export const LIMIT = 20; + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const selectedTag = url.searchParams.get("tag") ?? undefined; + const page = parseInt(url.searchParams.get("page") ?? "", 10); + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles", { + params: { + query: { + tag: selectedTag, + limit: LIMIT, + offset: !Number.isNaN(page) ? page * LIMIT : undefined, + }, + }, + }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); +}; +``` + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { Form, useLoaderData, useSearchParams } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import { LIMIT, type loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; + +export function FeedPage() { + const [searchParams] = useSearchParams(); + const { articles, tags } = useLoaderData(); + const pageAmount = Math.ceil(articles.articlesCount / LIMIT); + const currentPage = parseInt(searchParams.get("page") ?? "1", 10); + + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ {articles.articles.map((article) => ( + + ))} + +
+ +
    + {Array(pageAmount) + .fill(null) + .map((_, index) => + index + 1 === currentPage ? ( +
  • + {index + 1} +
  • + ) : ( +
  • + +
  • + ), + )} +
+ +
+ +
+
+

Popular Tags

+ +
+ +
+ {tags.tags.map((tag) => ( + + ))} +
+ +
+
+
+
+
+ ); +} +``` + +이것으로 완료되었습니다. 탭 목록도 비슷하게 구현할 수 있지만, 인증을 구현할 때까지 잠시 보류하겠습니다. 그런데 말이 나왔으니! + +### 인증 + +인증에는 두 개의 페이지가 관련됩니다 - 로그인과 회원가입입니다. 이들은 대부분 동일하므로 필요한 경우 코드를 재사용할 수 있도록 `sign-in`이라는 동일한 슬라이스에 유지하는 것이 합리적입니다. + +`pages/sign-in`의 `ui` 세그먼트에 다음 내용으로 `RegisterPage.tsx`를 만드세요. + +```tsx title="pages/sign-in/ui/RegisterPage.tsx" +import { Form, Link, useActionData } from "@remix-run/react"; + +import type { register } from "../api/register"; + +export function RegisterPage() { + const registerData = useActionData(); + + return ( +
+
+
+
+

Sign up

+

+ Have an account? +

+ + {registerData?.error && ( +
    + {registerData.error.errors.body.map((error) => ( +
  • {error}
  • + ))} +
+ )} + +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+ ); +} +``` + +이제 고쳐야 할 깨진 import가 있습니다. 새로운 세그먼트가 필요하므로 다음과 같이 만드세요. + +```bash +npx fsd pages sign-in -s api +``` + +그러나 등록의 백엔드 부분을 구현하기 전에 Remix가 세션을 처리할 수 있도록 일부 인프라 코드가 필요합니다. 다른 페이지에서도 필요할 수 있으므로 이는 Shared로 갑니다. + +다음 코드를 `shared/api/auth.server.ts`에 넣으세요. 이는 Remix에 매우 특화된 것이므로 너무 걱정하지 마세요. 그냥 복사-붙여넣기 하세요. + +```tsx title="shared/api/auth.server.ts" +import { createCookieSessionStorage, redirect } from "@remix-run/node"; +import invariant from "tiny-invariant"; + +import type { User } from "./models"; + +invariant( + process.env.SESSION_SECRET, + "SESSION_SECRET must be set for authentication to work", +); + +const sessionStorage = createCookieSessionStorage<{ + user: User; +}>({ + cookie: { + name: "__session", + httpOnly: true, + path: "/", + sameSite: "lax", + secrets: [process.env.SESSION_SECRET], + secure: process.env.NODE_ENV === "production", + }, +}); + +export async function createUserSession({ + request, + user, + redirectTo, +}: { + request: Request; + user: User; + redirectTo: string; +}) { + const cookie = request.headers.get("Cookie"); + const session = await sessionStorage.getSession(cookie); + + session.set("user", user); + + return redirect(redirectTo, { + headers: { + "Set-Cookie": await sessionStorage.commitSession(session, { + maxAge: 60 * 60 * 24 * 7, // 7 days + }), + }, + }); +} + +export async function getUserFromSession(request: Request) { + const cookie = request.headers.get("Cookie"); + const session = await sessionStorage.getSession(cookie); + + return session.get("user") ?? null; +} + +export async function requireUser(request: Request) { + const user = await getUserFromSession(request); + + if (user === null) { + throw redirect("/login"); + } + + return user; +} +``` + +그리고 바로 옆에 있는 `models.ts` 파일에서 `User` 모델도 내보내세요. + +```tsx title="shared/api/models.ts" +import type { components } from "./v1"; + +export type Article = components["schemas"]["Article"]; +export type User = components["schemas"]["User"]; +``` + +이 코드가 작동하려면 `SESSION_SECRET` 환경 변수를 설정해야 합니다. 프로젝트 루트에 `.env` 파일을 만들고 `SESSION_SECRET=`을 작성한 다음 키보드에서 무작위로 키를 눌러 긴 무작위 문자열을 만드세요. 다음과 같은 결과가 나와야 합니다. + + +```bash title=".env" +SESSION_SECRET=dontyoudarecopypastethis +``` + +마지막으로 이 코드를 사용하기 위해 공개 API에 일부 내보내기를 추가하세요. + +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; + +export type { Article } from "./models"; + +export { createUserSession, getUserFromSession, requireUser } from "./auth.server"; +``` + +이제 RealWorld 백엔드와 실제로 통신하여 등록을 수행하는 코드를 작성할 수 있습니다. 그것을 `pages/sign-in/api`에 유지할 것입니다. `register.ts`라는 파일을 만들고 다음 코드를 넣으세요. + + +```tsx title="pages/sign-in/api/register.ts" +import { json, type ActionFunctionArgs } from "@remix-run/node"; + +import { POST, createUserSession } from "shared/api"; + +export const register = async ({ request }: ActionFunctionArgs) => { + const formData = await request.formData(); + const username = formData.get("username")?.toString() ?? ""; + const email = formData.get("email")?.toString() ?? ""; + const password = formData.get("password")?.toString() ?? ""; + + const { data, error } = await POST("/users", { + body: { user: { email, password, username } }, + }); + + if (error) { + return json({ error }, { status: 400 }); + } else { + return createUserSession({ + request: request, + user: data.user, + redirectTo: "/", + }); + } +}; +``` + +```tsx title="pages/sign-in/index.ts" +export { RegisterPage } from './ui/RegisterPage'; +export { register } from './api/register'; +``` + +거의 다 왔습니다! 페이지와 액션을 `/register` 라우트에 연결하기만 하면 됩니다. `app/routes`에 `register.tsx`를 만드세요. + +```tsx title="app/routes/register.tsx" +import { RegisterPage, register } from "pages/sign-in"; + +export { register as action }; + +export default RegisterPage; +``` + +이제 [http://localhost:3000/register](http://localhost:3000/register)로 가면 사용자를 생성할 수 있어야 합니다! 애플리케이션의 나머지 부분은 아직 이에 반응하지 않을 것입니다. 곧 그 문제를 해결하겠습니다. + +매우 유사한 방식으로 로그인 페이지를 구현할 수 있습니다. 직접 시도해 보거나 그냥 코드를 가져와서 계속 진행하세요. + +```tsx title="pages/sign-in/api/sign-in.ts" +import { json, type ActionFunctionArgs } from "@remix-run/node"; + +import { POST, createUserSession } from "shared/api"; + +export const signIn = async ({ request }: ActionFunctionArgs) => { + const formData = await request.formData(); + const email = formData.get("email")?.toString() ?? ""; + const password = formData.get("password")?.toString() ?? ""; + + const { data, error } = await POST("/users/login", { + body: { user: { email, password } }, + }); + + if (error) { + return json({ error }, { status: 400 }); + } else { + return createUserSession({ + request: request, + user: data.user, + redirectTo: "/", + }); + } +}; +``` + +```tsx title="pages/sign-in/ui/SignInPage.tsx" +import { Form, Link, useActionData } from "@remix-run/react"; + +import type { signIn } from "../api/sign-in"; + +export function SignInPage() { + const signInData = useActionData(); + + return ( +
+
+
+
+

Sign in

+

+ Need an account? +

+ + {signInData?.error && ( +
    + {signInData.error.errors.body.map((error) => ( +
  • {error}
  • + ))} +
+ )} + +
+
+ +
+
+ +
+ +
+
+
+
+
+ ); +} +``` + +```tsx title="pages/sign-in/index.ts" +export { RegisterPage } from './ui/RegisterPage'; +export { register } from './api/register'; +export { SignInPage } from './ui/SignInPage'; +export { signIn } from './api/sign-in'; +``` + +```tsx title="app/routes/login.tsx" +import { SignInPage, signIn } from "pages/sign-in"; + +export { signIn as action }; + +export default SignInPage; +``` + +이제 사용자가 이 페이지에 실제로 접근할 수 있는 방법을 제공해 봅시다. + +### 헤더 + +1부에서 논의했듯이, 앱 헤더는 일반적으로 Widgets나 Shared에 배치됩니다. 매우 간단하고 모든 비즈니스 로직을 외부에 유지할 수 있기 때문에 Shared에 넣을 것입니다. 이를 위한 장소를 만들어 봅시다. + +```bash +npx fsd shared ui +``` + +이제 다음 내용으로 `shared/ui/Header.tsx`를 만드세요. + +```tsx title="shared/ui/Header.tsx" +import { useContext } from "react"; +import { Link, useLocation } from "@remix-run/react"; + +import { CurrentUser } from "../api/currentUser"; + +export function Header() { + const currentUser = useContext(CurrentUser); + const { pathname } = useLocation(); + + return ( + + ); +} +``` + +이 컴포넌트를 `shared/ui`에서 내보내세요. + +```tsx title="shared/ui/index.ts" +export { Header } from "./Header"; +``` + +헤더에서는 `shared/api`에 유지되는 컨텍스트에 의존합니다. 그것도 만드세요. + +```tsx title="shared/api/currentUser.ts" +import { createContext } from "react"; + +import type { User } from "./models"; + +export const CurrentUser = createContext(null); +``` + +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; + +export type { Article } from "./models"; + +export { createUserSession, getUserFromSession, requireUser } from "./auth.server"; +export { CurrentUser } from "./currentUser"; +``` + +이제 페이지에 헤더를 추가해 봅시다. 모든 페이지에 있어야 하므로 루트 라우트에 추가하고 outlet(페이지가 렌더링될 위치)을 `CurrentUser` 컨텍스트 제공자로 감싸는 것이 합리적입니다. 이렇게 하면 전체 앱과 헤더가 현재 사용자 객체에 접근할 수 있습니다. 또한 쿠키에서 실제로 현재 사용자 객체를 가져오는 로더를 추가할 것입니다. `app/root.tsx`에 다음 내용을 넣으세요. + +```tsx title="app/root.tsx" +import { cssBundleHref } from "@remix-run/css-bundle"; +import type { LinksFunction, LoaderFunctionArgs } from "@remix-run/node"; +import { + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration, + useLoaderData, +} from "@remix-run/react"; + +import { Header } from "shared/ui"; +import { getUserFromSession, CurrentUser } from "shared/api"; + +export const links: LinksFunction = () => [ + ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []), +]; + +export const loader = ({ request }: LoaderFunctionArgs) => + getUserFromSession(request); + +export default function App() { + const user = useLoaderData(); + + return ( + + + + + + + + + + + + + +
+ + + + + + + + ); +} +``` + +이 시점에서 홈 페이지에 다음과 같은 내용이 표시되어야 합니다. + +
+ ![The feed page of Conduit, including the header, the feed, and the tags. The tabs are still missing.](/img/tutorial/realworld-feed-without-tabs.jpg) + +
헤더, 피드, 태그를 포함한 Conduit의 피드 페이지. 탭은 아직 없습니다.
+
+ +### 탭 + +이제 인증 상태를 감지할 수 있으므로 탭과 글 좋아요를 빠르게 구현하여 피드 페이지를 완성해 봅시다. 또 다른 폼이 필요하지만 이 페이지 파일이 꽤 커지고 있으므로 이러한 폼을 인접한 파일로 옮기겠습니다. `Tabs.tsx`, `PopularTags.tsx`, `Pagination.tsx`를 다음 내용으로 만들 것입니다. + + +```tsx title="pages/feed/ui/Tabs.tsx" +import { useContext } from "react"; +import { Form, useSearchParams } from "@remix-run/react"; + +import { CurrentUser } from "shared/api"; + +export function Tabs() { + const [searchParams] = useSearchParams(); + const currentUser = useContext(CurrentUser); + + return ( +
+
+
    + {currentUser !== null && ( +
  • + +
  • + )} +
  • + +
  • + {searchParams.has("tag") && ( +
  • + + {searchParams.get("tag")} + +
  • + )} +
+
+
+ ); +} +``` + +```tsx title="pages/feed/ui/PopularTags.tsx" +import { Form, useLoaderData } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import type { loader } from "../api/loader"; + +export function PopularTags() { + const { tags } = useLoaderData(); + + return ( +
+

Popular Tags

+ +
+ +
+ {tags.tags.map((tag) => ( + + ))} +
+ +
+ ); +} +``` + +```tsx title="pages/feed/ui/Pagination.tsx" +import { Form, useLoaderData, useSearchParams } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import { LIMIT, type loader } from "../api/loader"; + +export function Pagination() { + const [searchParams] = useSearchParams(); + const { articles } = useLoaderData(); + const pageAmount = Math.ceil(articles.articlesCount / LIMIT); + const currentPage = parseInt(searchParams.get("page") ?? "1", 10); + + return ( +
+ +
    + {Array(pageAmount) + .fill(null) + .map((_, index) => + index + 1 === currentPage ? ( +
  • + {index + 1} +
  • + ) : ( +
  • + +
  • + ), + )} +
+ + ); +} +``` + +이제 `FeedPage`를 다음과 같이 업데이트하세요. + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { useLoaderData } from "@remix-run/react"; + +import type { loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; +import { Tabs } from "./Tabs"; +import { PopularTags } from "./PopularTags"; +import { Pagination } from "./Pagination"; + +export function FeedPage() { + const { articles } = useLoaderData(); + + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ + + {articles.articles.map((article) => ( + + ))} + + +
+ +
+ +
+
+
+
+ ); +} +``` + +마지막으로 로더를 업데이트하여 새로운 필터를 처리하세요. + +```tsx title="pages/feed/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET, requireUser } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + /* unchanged */ +} + +/** Amount of articles on one page. */ +export const LIMIT = 20; + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const selectedTag = url.searchParams.get("tag") ?? undefined; + const page = parseInt(url.searchParams.get("page") ?? "", 10); + + if (url.searchParams.get("source") === "my-feed") { + const userSession = await requireUser(request); + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles/feed", { + params: { + query: { + limit: LIMIT, + offset: !Number.isNaN(page) ? page * LIMIT : undefined, + }, + }, + headers: { Authorization: `Token ${userSession.token}` }, + }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); + } + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles", { + params: { + query: { + tag: selectedTag, + limit: LIMIT, + offset: !Number.isNaN(page) ? page * LIMIT : undefined, + }, + }, + }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); +}; +``` + +피드 페이지를 떠나기 전에, 글에 대한 좋아요를 처리하는 코드를 추가해 봅시다. `ArticlePreview.tsx`를 다음과 같이 변경하세요. + +```tsx title="pages/feed/ui/ArticlePreview.tsx" +import { Form, Link } from "@remix-run/react"; +import type { Article } from "shared/api"; + +interface ArticlePreviewProps { + article: Article; +} + +export function ArticlePreview({ article }: ArticlePreviewProps) { + return ( +
+
+ + + +
+ + {article.author.username} + + + {new Date(article.createdAt).toLocaleDateString(undefined, { + dateStyle: "long", + })} + +
+
+ +
+
+ +

{article.title}

+

{article.description}

+ Read more... +
    + {article.tagList.map((tag) => ( +
  • + {tag} +
  • + ))} +
+ +
+ ); +} +``` + +이 코드는 글에 좋아요를 표시하기 위해 `/article/:slug`로 `_action=favorite`과 함께 POST 요청을 보냅니다. 아직 작동하지 않겠지만, 글 읽기 페이지 작업을 시작하면서 이것도 구현할 것입니다. + +이것으로 피드가 공식적으로 완성되었습니다! 야호! + +### 글 읽기 페이지 + +먼저 데이터가 필요합니다. 로더를 만들어 봅시다. + +```bash +npx fsd pages article-read -s api +``` + +```tsx title="pages/article-read/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import invariant from "tiny-invariant"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET, getUserFromSession } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +export const loader = async ({ request, params }: LoaderFunctionArgs) => { + invariant(params.slug, "Expected a slug parameter"); + const currentUser = await getUserFromSession(request); + const authorization = currentUser + ? { Authorization: `Token ${currentUser.token}` } + : undefined; + + return json( + await promiseHash({ + article: throwAnyErrors( + GET("/articles/{slug}", { + params: { + path: { slug: params.slug }, + }, + headers: authorization, + }), + ), + comments: throwAnyErrors( + GET("/articles/{slug}/comments", { + params: { + path: { slug: params.slug }, + }, + headers: authorization, + }), + ), + }), + ); +}; +``` + +```tsx title="pages/article-read/index.ts" +export { loader } from "./api/loader"; +``` + + +이제 `/article/:slug` 라우트에 연결할 수 있습니다. `article.$slug.tsx`라는 라우트 파일을 만드세요. + +```tsx title="app/routes/article.$slug.tsx" +export { loader } from "pages/article-read"; +``` + +페이지 자체는 세 가지 주요 블록으로 구성됩니다 - 글 헤더와 액션(두 번 반복), 글 본문, 댓글 섹션입니다. 다음은 페이지의 마크업입니다. 특별히 흥미로운 내용은 없습니다: + +```tsx title="pages/article-read/ui/ArticleReadPage.tsx" +import { useLoaderData } from "@remix-run/react"; + +import type { loader } from "../api/loader"; +import { ArticleMeta } from "./ArticleMeta"; +import { Comments } from "./Comments"; + +export function ArticleReadPage() { + const { article } = useLoaderData(); + + return ( +
+
+
+

{article.article.title}

+ + +
+
+ +
+
+
+

{article.article.body}

+
    + {article.article.tagList.map((tag) => ( +
  • + {tag} +
  • + ))} +
+
+
+ +
+ +
+ +
+ +
+ +
+
+
+ ); +} +``` + +더 흥미로운 것은 `ArticleMeta`와 `Comments`입니다. 이들은 글 좋아요, 댓글 작성 등과 같은 쓰기 작업을 포함합니다. 이들을 작동시키려면 먼저 백엔드 부분을 구현해야 합니다. 페이지의 `api` 세그먼트에 `action.ts`를 만드세요: + +```tsx title="pages/article-read/api/action.ts" +import { redirect, type ActionFunctionArgs } from "@remix-run/node"; +import { namedAction } from "remix-utils/named-action"; +import { redirectBack } from "remix-utils/redirect-back"; +import invariant from "tiny-invariant"; + +import { DELETE, POST, requireUser } from "shared/api"; + +export const action = async ({ request, params }: ActionFunctionArgs) => { + const currentUser = await requireUser(request); + + const authorization = { Authorization: `Token ${currentUser.token}` }; + + const formData = await request.formData(); + + return namedAction(formData, { + async delete() { + invariant(params.slug, "Expected a slug parameter"); + await DELETE("/articles/{slug}", { + params: { path: { slug: params.slug } }, + headers: authorization, + }); + return redirect("/"); + }, + async favorite() { + invariant(params.slug, "Expected a slug parameter"); + await POST("/articles/{slug}/favorite", { + params: { path: { slug: params.slug } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async unfavorite() { + invariant(params.slug, "Expected a slug parameter"); + await DELETE("/articles/{slug}/favorite", { + params: { path: { slug: params.slug } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async createComment() { + invariant(params.slug, "Expected a slug parameter"); + const comment = formData.get("comment"); + invariant(typeof comment === "string", "Expected a comment parameter"); + await POST("/articles/{slug}/comments", { + params: { path: { slug: params.slug } }, + headers: { ...authorization, "Content-Type": "application/json" }, + body: { comment: { body: comment } }, + }); + return redirectBack(request, { fallback: "/" }); + }, + async deleteComment() { + invariant(params.slug, "Expected a slug parameter"); + const commentId = formData.get("id"); + invariant(typeof commentId === "string", "Expected an id parameter"); + const commentIdNumeric = parseInt(commentId, 10); + invariant( + !Number.isNaN(commentIdNumeric), + "Expected a numeric id parameter", + ); + await DELETE("/articles/{slug}/comments/{id}", { + params: { path: { slug: params.slug, id: commentIdNumeric } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async followAuthor() { + const authorUsername = formData.get("username"); + invariant( + typeof authorUsername === "string", + "Expected a username parameter", + ); + await POST("/profiles/{username}/follow", { + params: { path: { username: authorUsername } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async unfollowAuthor() { + const authorUsername = formData.get("username"); + invariant( + typeof authorUsername === "string", + "Expected a username parameter", + ); + await DELETE("/profiles/{username}/follow", { + params: { path: { username: authorUsername } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + }); +}; +``` + +그 슬라이스에서 이를 내보내고 라우트에서도 내보내세요. 그리고 페이지 자체도 연결하겠습니다. + +```tsx title="pages/article-read/index.ts" +export { ArticleReadPage } from "./ui/ArticleReadPage"; +export { loader } from "./api/loader"; +export { action } from "./api/action"; +``` + +```tsx title="app/routes/article.$slug.tsx" +import { ArticleReadPage } from "pages/article-read"; + +export { loader, action } from "pages/article-read"; + +export default ArticleReadPage; +``` + +이제 독자 페이지에서 좋아요 버튼을 아직 구현하지 않았지만, 피드의 좋아요 버튼이 작동하기 시작할 것입니다! 이 라우트로 "좋아요" 요청을 보내고 있었기 때문입니다. 한번 시도해 보세요. + +`ArticleMeta`와 `Comments`는 다시 한번 폼들의 모음입니다. 이전에 이미 해봤으니, 코드를 가져와서 넘어가겠습니다. + +```tsx title="pages/article-read/ui/ArticleMeta.tsx" +import { Form, Link, useLoaderData } from "@remix-run/react"; +import { useContext } from "react"; + +import { CurrentUser } from "shared/api"; +import type { loader } from "../api/loader"; + +export function ArticleMeta() { + const currentUser = useContext(CurrentUser); + const { article } = useLoaderData(); + + return ( +
+
+ + + + +
+ + {article.article.author.username} + + {article.article.createdAt} +
+ + {article.article.author.username == currentUser?.username ? ( + <> + + Edit Article + +    + + + ) : ( + <> + + +    + + + )} +
+
+ ); +} +``` + +```tsx title="pages/article-read/ui/Comments.tsx" +import { useContext } from "react"; +import { Form, Link, useLoaderData } from "@remix-run/react"; + +import { CurrentUser } from "shared/api"; +import type { loader } from "../api/loader"; + +export function Comments() { + const { comments } = useLoaderData(); + const currentUser = useContext(CurrentUser); + + return ( +
+ {currentUser !== null ? ( +
+
+ +
+
+ + +
+
+ ) : ( +
+
+

+ Sign in +   or   + Sign up +   to add comments on this article. +

+
+
+ )} + + {comments.comments.map((comment) => ( +
+
+

{comment.body}

+
+ +
+ + + +   + + {comment.author.username} + + {comment.createdAt} + {comment.author.username === currentUser?.username && ( + +
+ + +
+
+ )} +
+
+ ))} +
+ ); +} +``` + +이것으로 우리의 글 읽기 페이지도 완성되었습니다! 이제 작성자를 팔로우하고, 글에 좋아요를 누르고, 댓글을 남기는 버튼들이 예상대로 작동해야 합니다. + +
+ ![Article reader with functioning buttons to like and follow](/img/tutorial/realworld-article-reader.jpg) + +
기능하는 좋아요와 팔로우 버튼이 있는 글 읽기 페이지
+
+ +### 글 작성 페이지 + +이것은 이 튜토리얼에서 다룰 마지막 페이지이며, 여기서 가장 흥미로운 부분은 폼 데이터를 어떻게 검증할 것인가 입니다. + +페이지 자체인 `article-edit/ui/ArticleEditPage.tsx`는 꽤 간단할 것이며, 추가적인 복잡성은 다른 두 개의 컴포넌트로 숨겨질 것입니다. + +```tsx title="pages/article-edit/ui/ArticleEditPage.tsx" +import { Form, useLoaderData } from "@remix-run/react"; + +import type { loader } from "../api/loader"; +import { TagsInput } from "./TagsInput"; +import { FormErrors } from "./FormErrors"; + +export function ArticleEditPage() { + const article = useLoaderData(); + + return ( +
+
+
+
+ + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+
+
+
+
+
+ ); +} +``` + +이 페이지는 현재 글(새로 작성하는 경우가 아니라면)을 가져와서 해당하는 폼 필드를 채웁니다. 이전에 본 적이 있습니다. 흥미로운 부분은 `FormErrors`인데, 이는 검증 결과를 받아 사용자에게 표시할 것입니다. 한번 살펴보겠습니다. + +```tsx title="pages/article-edit/ui/FormErrors.tsx" +import { useActionData } from "@remix-run/react"; +import type { action } from "../api/action"; + +export function FormErrors() { + const actionData = useActionData(); + + return actionData?.errors != null ? ( +
    + {actionData.errors.map((error) => ( +
  • {error}
  • + ))} +
+ ) : null; +} +``` + +여기서는 우리의 액션이 `errors` 필드, 즉 사람이 읽을 수 있는 오류 메시지 배열을 반환할 것이라고 가정하고 있습니다. 곧 액션에 대해 다루겠습니다. + +또 다른 컴포넌트는 태그 입력입니다. 이는 단순한 입력 필드에 선택된 태그의 추가적인 미리보기가 있는 것입니다. 여기에는 특별한 것이 없습니다: + +```tsx title="pages/article-edit/ui/TagsInput.tsx" +import { useEffect, useRef, useState } from "react"; + +export function TagsInput({ + name, + defaultValue, +}: { + name: string; + defaultValue?: Array; +}) { + const [tagListState, setTagListState] = useState(defaultValue ?? []); + + function removeTag(tag: string): void { + const newTagList = tagListState.filter((t) => t !== tag); + setTagListState(newTagList); + } + + const tagsInput = useRef(null); + useEffect(() => { + tagsInput.current && (tagsInput.current.value = tagListState.join(",")); + }, [tagListState]); + + return ( + <> + + setTagListState(e.target.value.split(",").filter(Boolean)) + } + /> +
+ {tagListState.map((tag) => ( + + + [" ", "Enter"].includes(e.key) && removeTag(tag) + } + onClick={() => removeTag(tag)} + >{" "} + {tag} + + ))} +
+ + ); +} +``` + +이제 API 부분입니다. 로더는 URL을 살펴보고, 글 슬러그가 포함되어 있다면 기존 글을 수정하는 것이므로 해당 데이터를 로드해야 합니다. 그렇지 않으면 아무것도 반환하지 않습니다. 그 로더를 만들어 봅시다. + +```ts title="pages/article-edit/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; + +import { GET, requireUser } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +export const loader = async ({ params, request }: LoaderFunctionArgs) => { + const currentUser = await requireUser(request); + + if (!params.slug) { + return { article: null }; + } + + return throwAnyErrors( + GET("/articles/{slug}", { + params: { path: { slug: params.slug } }, + headers: { Authorization: `Token ${currentUser.token}` }, + }), + ); +}; +``` + +액션은 새로운 필드 값들을 받아 우리의 데이터 스키마를 통해 실행하고, 모든 것이 올바르다면 이러한 변경사항을 백엔드에 커밋합니다. 이는 기존 글을 업데이트하거나 새 글을 생성하는 방식으로 이루어집니다. + +```tsx title="pages/article-edit/api/action.ts" +import { json, redirect, type ActionFunctionArgs } from "@remix-run/node"; + +import { POST, PUT, requireUser } from "shared/api"; +import { parseAsArticle } from "../model/parseAsArticle"; + +export const action = async ({ request, params }: ActionFunctionArgs) => { + try { + const { body, description, title, tags } = parseAsArticle( + await request.formData(), + ); + const tagList = tags?.split(",") ?? []; + + const currentUser = await requireUser(request); + const payload = { + body: { + article: { + title, + description, + body, + tagList, + }, + }, + headers: { Authorization: `Token ${currentUser.token}` }, + }; + + const { data, error } = await (params.slug + ? PUT("/articles/{slug}", { + params: { path: { slug: params.slug } }, + ...payload, + }) + : POST("/articles", payload)); + + if (error) { + return json({ errors: error }, { status: 422 }); + } + + return redirect(`/article/${data.article.slug ?? ""}`); + } catch (errors) { + return json({ errors }, { status: 400 }); + } +}; +``` + +스키마는 `FormData`를 위한 파싱 함수로도 작동하여, 깨끗한 필드를 편리하게 얻거나 마지막에 처리할 오류를 던질 수 있게 해줍니다. 그 파싱 함수는 다음과 같이 보일 수 있습니다. + +```tsx title="pages/article-edit/model/parseAsArticle.ts" +export function parseAsArticle(data: FormData) { + const errors = []; + + const title = data.get("title"); + if (typeof title !== "string" || title === "") { + errors.push("Give this article a title"); + } + + const description = data.get("description"); + if (typeof description !== "string" || description === "") { + errors.push("Describe what this article is about"); + } + + const body = data.get("body"); + if (typeof body !== "string" || body === "") { + errors.push("Write the article itself"); + } + + const tags = data.get("tags"); + if (typeof tags !== "string") { + errors.push("The tags must be a string"); + } + + if (errors.length > 0) { + throw errors; + } + + return { title, description, body, tags: data.get("tags") ?? "" } as { + title: string; + description: string; + body: string; + tags: string; + }; +} +``` + +물론 이는 다소 길고 반복적이지만, 사람이 읽을 수 있는 오류 메시지를 위해 우리가 지불해야 하는 대가입니다. 이것은 Zod 스키마일 수도 있지만, 그렇게 하면 프론트엔드에서 오류 메시지를 렌더링해야 하고, 이 폼은 그런 복잡성을 감당할 만한 가치가 없습니다. + +마지막 단계로 - 페이지, 로더, 그리고 액션을 라우트에 연결합니다. 우리는 생성과 편집을 모두 깔끔하게 지원하므로 `editor._index.tsx`와 `editor.$slug.tsx` 모두에서 동일한 것을 내보낼 수 있습니다. + +```tsx title="pages/article-edit/index.ts" +export { ArticleEditPage } from "./ui/ArticleEditPage"; +export { loader } from "./api/loader"; +export { action } from "./api/action"; +``` + +```tsx title="app/routes/editor._index.tsx, app/routes/editor.$slug.tsx (same content)" +import { ArticleEditPage } from "pages/article-edit"; + +export { loader, action } from "pages/article-edit"; + +export default ArticleEditPage; +``` + +이제 완료되었습니다! 로그인하고 새 글을 작성해보세요. 또는 글을 "잊어버리고" 검증이 작동하는 것을 확인해보세요. + +
+ ![The Conduit article editor, with the title field saying “New article” and the rest of the fields empty. Above the form there are two errors: “**Describe what this article is about” and “Write the article itself”.**](/img/tutorial/realworld-article-editor.jpg) + +
제목 필드에 "새 글"이라고 쓰여 있고 나머지 필드는 비어 있는 Conduit 글 편집기. 폼 위에 두 개의 오류가 있습니다. **"이 글이 무엇에 관한 것인지 설명해주세요"**, **"글 본문을 작성해주세요"**.
+
+ +프로필과 설정 페이지는 글 읽기와 편집기 페이지와 매우 유사하므로, 독자인 여러분의 연습 과제로 남겨두겠습니다 :) diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/auth.md b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/auth.md new file mode 100644 index 0000000000..1bbe99af28 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/auth.md @@ -0,0 +1,226 @@ +--- +sidebar_position: 1 +sidebar_label: 인증 +--- + +# 인증 + +보통 인증은 세 가지 주요 단계로 이루어집니다: + +1. 사용자로부터 로그인 정보(아이디, 비밀번호 등)을 수집합니다. +2. 백엔드 서버로 해당 로그인 정보을 전송합니다. +3. 인증 후 발급받은 토큰을 저장하여 이후 요청에 사용합니다. + +## 사용자 로그인 정보 수집 방법 + +앱에서 사용자로부터 로그인 정보를 수집하는 방법을 알아보겠습니다. 만약에 OAuth를 사용하는 경우, OAuth 제공자의 로그인 페이지를 사용하여 [3단계](#how-to-store-the-token-for-authenticated-requests)로 바로 넘어갈 수 있습니다. + +### 전용 로그인 페이지 만들기 + +웹사이트에서 사용자 이름과 비밀번호를 입력하는 로그인 페이지를 제공하는 것이 일반적입니다. 이러한 페이지들은 구조가 단순하여 별도의 복잡한 분해 작업이 필요하지 않습니다. 다만, 로그인과 회원가입 양식은 외형이 비슷하기 때문에, 경우에 따라 두 양식을 하나의 페이지에서 통합하여 제공하기도 합니다. + +- 📂 pages + - 📂 login + - 📂 ui + - 📄 LoginPage.tsx (or your framework's component file format) + - 📄 RegisterPage.tsx + - 📄 index.ts + - other pages… + +로그인과 회원가입 컴포넌트를 별도로 만들고, 필요에 따라 index 파일에서 export 할 수 있습니다. 이 컴포넌트들은 사용자로부터 로그인 정보을 입력받는 폼을 포함합니다. + +### 로그인 다이얼로그 만들기 + +앱의 어디서나 사용할 수 있는 로그인 다이얼로그가 필요하다면, 이 다이얼로그를 재사용 가능한 위젯으로 만드는 것이 좋습니다. 이렇게 하면 불필요한 세분화를 피하면서도 어떤 페이지에서나 쉽게 로그인 다이얼로그를 띄울 수 있습니다. + +- 📂 widgets + - 📂 login-dialog + - 📂 ui + - 📄 LoginDialog.tsx + - 📄 index.ts + - other widgets… + +가이드 나머지 부분은 전용 페이지 방식에 대해 설명하고 있지만, 동일한 원칙을 로그인 다이얼로그에도 적용할 수 있습니다. + +### 클라이언트 측 검증 + +특히 회원가입의 경우, 사용자가 입력한 내용에 문제가 있을 때 빠르게 피드백을 제공하기 위해 클라이언트 측 검증을 수행하는 것이 좋습니다. 이를 위해 로그인 페이지의 `model` 세그먼트에서 검증 로직을 구현할 수 있습니다. 예를 들어 JS/TS에서는 [Zod][ext-zod]와 같은 스키마 검증 라이브러리를 사용할 수 있습니다: + +```ts title="pages/login/model/registration-schema.ts" +import { z } from "zod"; + +export const registrationData = z.object({ + email: z.string().email(), + password: z.string().min(6), + confirmPassword: z.string(), +}).refine((data) => data.password === data.confirmPassword, { + message: "비밀번호가 일치하지 않습니다", + path: ["confirmPassword"], +}); +``` + +그런 다음, ui 세그먼트에서 이 스키마를 사용하여 사용자 입력을 검증할 수 있습니다: + +```tsx title="pages/login/ui/RegisterPage.tsx" +import { registrationData } from "../model/registration-schema"; + +function validate(formData: FormData) { + const data = Object.fromEntries(formData.entries()); + try { + registrationData.parse(data); + } catch (error) { + // TODO: Show error message to the user + } +} + +export function RegisterPage() { + return ( +
validate(new FormData(e.target))}> + + + + + + + + +
+ ) +} +``` + +## 로그인 정보 전송 방법 + +로그인 정보를 백엔드 서버로 전송하기 위한 요청 함수를 작성하세요. 이 함수는 상태 관리 라이브러리나 뮤테이션 라이브러리(예: TanStack Query)를 사용하여 호출할 수 있습니다. + +### 요청 함수 저장 위치 + +이 요청 함수를 저장할 수 있는 위치는 크게 두 가지입니다: `shared/api` 또는 페이지의 `api` 세그먼트입니다. + +#### `shared/api`에 저장하기 + +모든 API 요청을 `shared/api`에 모아서 관리하고, 엔드포인트별로 그룹화하는 접근 방식입니다. 파일 구조는 다음과 같습니다: + +- 📂 shared + - 📂 api + - 📂 endpoints + - 📄 login.ts + - other endpoint functions… + - 📄 client.ts + - 📄 index.ts + +`📄 client.ts` 파일은 요청을 수행하는 원시 함수(예: `fetch()`)에 대한 래퍼를 포함합니다. 이 래퍼는 백엔드의 기본 URL 설정, 헤더 설정, 데이터 직렬화 등을 처리합니다. + +```ts title="shared/api/endpoints/login.ts" +import { POST } from "../client"; + +export function login({ email, password }: { email: string, password: string }) { + return POST("/login", { email, password }); +} +``` + +```ts title="shared/api/index.ts" +export { login } from "./endpoints/login"; +``` + +#### 페이지의 `api` 세그먼트에 저장하기 + +로그인 요청이 특정 페이지에만 필요한 경우, 로그인 페이지의 `api` 세그먼트에 함수를 저장할 수 있습니다: + +- 📂 pages + - 📂 login + - 📂 api + - 📄 login.ts + - 📂 ui + - 📄 LoginPage.tsx + - 📄 index.ts + - other pages… + +```ts title="pages/login/api/login.ts" +import { POST } from "shared/api"; + +export function login({ email, password }: { email: string, password: string }) { + return POST("/login", { email, password }); +} +``` + +이 함수는 페이지의 공개 API에서 내보낼 필요가 없습니다. 로그인 요청이 다른 곳에서 필요할 가능성이 낮기 때문입니다. + +### 이중 인증(2FA) + +앱이 이중 인증(2FA)을 지원하는 경우, 사용자가 일회용 비밀번호(OTP)를 입력할 수 있는 별도의 페이지로 이동해야 할 수 있습니다. 일반적으로 `POST /login` 요청은 사용자가 2FA를 활성화했음을 나타내는 플래그가 포함된 사용자 객체를 반환합니다. 이 플래그가 설정되면 사용자를 2FA 페이지로 리디렉션해야 합니다. + +2FA 페이지는 로그인과 밀접하게 연관되어 있으므로 Pages 레이어의 `login` 슬라이스에 함께 저장하는 것이 좋습니다.
+ +이중 인증을 처리하기 위해서는 `login()` 함수와 유사한 또 다른 요청 함수가 필요할 것입니다. 이러한 함수들은 `Shared`나 로그인 페이지의 `api` 세그먼트에 함께 배치할 수 있습니다. + +## 인증된 요청의 토큰 저장 방법 {#how-to-store-the-token-for-authenticated-requests} + +인증 방식이 로그인/비밀번호, OAuth, 2단계 인증 등 어떤 것이든, 결국 토큰이 발급됩니다. 이 토큰은 이후 요청에서 사용자 식별을 위해 저장되어야 합니다. + +웹 애플리케이션에서는 **쿠키**를 사용해 토큰을 저장하는 것이 가장 일반적이고 이상적인 방법입니다. 쿠키를 사용하면 토큰을 수동으로 관리할 필요가 없으며, 복잡한 처리를 줄일 수 있습니다. 만약 서버 사이드 렌더링을 지원하는 프레임워크(예: [Remix][ext-remix])를 사용 중이라면, 서버 사이드 쿠키 인프라를 `shared/api`에 저장하는 것이 좋습니다. Remix를 사용하는 예시는 튜토리얼의 [인증 섹션][tutorial-authentication]에서 확인할 수 있습니다. + +그러나 쿠키를 사용할 수 없는 상황에서는, 토큰을 직접 관리해야 합니다. 이 경우, 토큰 만료 시 갱신 로직을 함께 구현해야 할 수도 있습니다. 이 경우, 토큰 만료 시 갱신 로직을 함께 구현해야 합니다. FSD에서는 토큰을 저장할 수 있는 다양한 방법이 있습니다. + +### Shared에 저장하기 + +`shared/api`에 저장하는 접근 방식은 API 클라이언트와 잘 맞아떨어집니다. 인증이 필요한 다른 요청 함수에서 이 토큰을 쉽게 사용할 수 있기 때문입니다. API 클라이언트에서 반응형 스토어나 모듈 수준 변수를 사용해 토큰을 저장하고, `login()/logout()` 함수에서 해당 상태를 업데이트할 수 있습니다. + +토큰 자동 갱신은 API 클라이언트에서 미들웨어 형태로 구현할 수 있습니다. 모든 요청마다 실행되며, 아래와 같은 방식으로 동작합니다: + +- 사용자가 로그인하면 액세스 토큰과 갱신 토큰을 저장합니다. +- 인증이 필요한 요청을 수행합니다. +- 토큰이 만료되어 요청이 실패하면, 갱신 토큰을 사용해 새로운 토큰을 요청하고 저장한 후, 원래 요청을 다시 시도합니다. + +이 방법의 단점 중 하나는 토큰 관리 로직이 요청 로직과 같은 위치에 있어, 복잡해질 수 있다는 점입니다. 간단한 경우에는 문제가 없겠지만, 토큰 관리 로직이 복잡한 경우에는 요청과 관리 로직을 분리하는 것이 좋습니다. 요청 및 API 클라이언트는 `shared/api`에 두고, 토큰 관리 로직은 `shared/auth`에 두는 방식으로 나눌 수 있습니다. + +또 다른 단점은 백엔드가 토큰과 함께 현재 사용자 정보를 반환하는 경우, 이 정보를 별도로 저장하거나 `/me` 또는 `/users/current`와 같은 엔드포인트에서 다시 요청해야 한다는 점입니다. + +### Entities에 저장하기 + +FSD 프로젝트에서는 사용자 엔티티 또는 현재 사용자 엔티티를 사용하는 것이 일반적입니다. 두 엔티티는 같은 것을 가리킬 수도 있습니다. + +:::note + +**현재 사용자**는 "viewer" 또는 "me"라고도 합니다. 이는 권한과 개인 정보를 가진 단일 인증 사용자와 공개적으로 접근 가능한 정보로 구성된 모든 사용자 목록을 구별하기 위해 사용됩니다. + +::: + +User 엔티티에 토큰을 저장하려면 `model` 세그먼트에 반응형 스토어를 생성해야 합니다. 이 스토어는 토큰과 사용자 객체를 모두 포함할 수 있습니다. + +API 클라이언트는 일반적으로 `shared/api` 정의되거나 엔티티 전체에 분산되어 있습니다. 따라서 주요 과제는 레이어의 임포트 규칙([import rule on layers][import-rule-on-layers])을 위반하지 않으면서 다른 요청에서도 토큰을 사용할 수 있도록 하는 것입니다. + +> 레이어 규칙: 슬라이스의 모듈은 자기보다 낮은 레이어에 위치한 다른 슬라이스만 임포트할 수 있습니다. + +이 문제를 해결하기 위한 몇 가지 방법은 다음과 같습니다: + +1. **요청 시마다 토큰 수동 전달** + 이 방법은 가장 간단하지만, 번거롭고 타입 안전성이 보장되지 않으면 실수가 발생할 가능성이 큽니다. 또한 Shared의 API 클라이언트에 미들웨어 패턴을 적용하기 어렵습니다. +2. **앱 전역에서 글로벌 스토어로 토큰 관리** + 토큰을 context나 `localStorage`에 저장하고, `shared/api`에 토큰 접근 키를 보관합니다. 토큰의 반응형 저장소는 User 엔터티에서 내보내며, 필요한 경우 context Provider는 App 레이어에서 설정합니다. 이 방법은 API 클라이언트 설계를 유연하게 만들지만, 상위 레이어에 context 제공이 필요하다는 암묵적인 의존성을 발생시킵니다. 따라서 context나 `localStorage`가 제대로 설정되지 않았을 경우, 유용한 오류 메시지를 제공하는 것이 좋습니다. +3. **토큰 변경 시 API 클라이언트 업데이트** + 반응형 스토어를 활용해 엔티티의 스토어가 변경될 때마다 API 클라이언트의 토큰 스토어를 업데이트하는 구독(subscribe)을 생성할 수 있습니다. 이 방법은 상위 계층에 암묵적인 의존성을 만든다는 점에서는 이전 해결책과 비슷하지만, 이 방법은 더 "명령형(push)" 접근이고, 이전 방법은 더 "선언형(pull)" 접근입니다. + +엔티티의 `model`에 토큰을 저장하여 문제를 해결하면, 토큰 관리와 관련된 더 많은 비즈니스 로직을 추가할 수 있습니다. 예를 들어, `model` 세그먼트에 토큰 만료 시 갱신하는 로직을 추가하거나, 일정 시간이 지나면 토큰을 무효화하는 로직을 포함할 수 있습니다. +백엔드에 요청을 보내야 하는 경우에는 User 엔티티의 api 세그먼트나 `shared/api`를 사용할 수 있습니다. + +### Pages/Widgets에 저장하기 (권장하지 않음) + +애플리케이션 전역에 적용되는 상태(예: 액세스 토큰)를 페이지나 위젯에 저장하는 것은 권장되지 않습니다. 예를 들어, 로그인 페이지의 `model` 세그먼트에 토큰 스토어를 배치하는 대신, 이 아티클에서 제시한 처음 두 해결책인 Shared나 Entities를 사용하는 것이 권장됩니다. + +## 로그아웃 및 토큰 무효화 + +로그아웃 기능은 애플리케이션에서 중요한 기능이지만, 이를 위한 별도의 페이지는 없는 경우가 많습니다. 이 기능은 백엔드에 인증된 요청을 보내고, 토큰 스토어를 업데이트하는 작업으로 구성됩니다. + +모든 요청을 `shared/api`에 보관했다면, 로그인 함수 근처에 로그아웃 요청 함수를 두는 것이 좋습니다. 그렇지 않은 경우, 로그아웃 버튼이 있는 위치 근처에 로그아웃 요청 함수를 배치할 수 있습니다. 예를 들어, 모든 페이지에 나타나는 헤더 위젯에 로그아웃 링크가 있다면, 해당 요청을 그 위젯의 `api` 세그먼트에 배치하는 것이 좋습니다. + +토큰 스토어에 대한 업데이트는 로그아웃 버튼이 위치한 곳(예: 헤더 위젯)에서 트리거되어야 합니다. 이 요청과 스토어 업데이트를 해당 위젯의 `model` 세그먼트에서 결합할 수 있습니다. + +### 자동 로그아웃 + +로그아웃 요청 실패나 로그인 토큰 갱신 실패 시를 대비해 안전장치를 마련하는 것도 중요합니다. 이 두 경우 모두 토큰 스토어를 비워야 합니다. 토큰을 Entities에 저장하는 경우, 이 로직은 `model` 세그먼트에 배치할 수 있습니다. 토큰을 Shared에 저장하는 경우, 이 로직을 `shared/api`에 포함하면 세그먼트가 너무 복잡해질 수 있습니다. 따라서 토큰 관리 로직을 별도의 세그먼트(예: `shared/auth`)로 분리하는 것도 고려해볼 만합니다. + +[tutorial-authentication]: /docs/get-started/tutorial#authentication +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-remix]: https://remix.run +[ext-zod]: https://zod.dev diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/index.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/index.mdx new file mode 100644 index 0000000000..1714b089c7 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/index.mdx @@ -0,0 +1,36 @@ +--- +hide_table_of_contents: true +--- + +# Examples + +

+방법론 적용에 대한 예시들 +

+ +## Main + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { UserSwitchOutlined, LayoutOutlined, FontSizeOutlined } from "@ant-design/icons"; + + + + diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md new file mode 100644 index 0000000000..43e2c2b09c --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md @@ -0,0 +1,104 @@ +--- +sidebar_position: 3 +--- + +# 페이지 레이아웃 + +이 가이드는 여러 페이지가 같은 기본 구조를 공유하고, 주요 내용만 다른 경우 사용할 수 있는 _페이지 레이아웃_ 에 대해 설명합니다. + +:::info + +이 가이드에서 다루지 않는 질문이 있으신가요? 오른쪽 파란색 버튼을 눌러 피드백을 남겨주세요. 여러분의 의견을 반영해 가이드를 확장해 나가겠습니다! + +::: + +## 간단한 레이아웃 + +간단한 레이아웃 예시로 설명 해 보겠습니다. 이 페이지는 사이트 내비게이션이 포함된 헤더, 두 개의 사이드바, 외부 링크가 포함된 푸터로 구성되어 있습니다. 복잡한 비즈니스 로직은 없으며, 동적인 부분은 사이드바와 헤더 오른쪽에 있는 테마 전환 버튼뿐입니다. 이러한 레이아웃은 shared/ui 또는 app/layouts에 포함시킬 수 있으며, props를 통해 전달받은 사이드바 콘텐츠를 표시합니다. + +```tsx title="shared/ui/layout/Layout.tsx" +import { Link, Outlet } from "react-router-dom"; +import { useThemeSwitcher } from "./useThemeSwitcher"; + +export function Layout({ siblingPages, headings }) { + const [theme, toggleTheme] = useThemeSwitcher(); + + return ( +
+
+ + +
+
+ + {/* 여기에 주요 콘텐츠가 들어갑니다 */} + +
+
+
    +
  • GitHub
  • +
  • Twitter
  • +
+
+
+ ); +} +``` + +```ts title="shared/ui/layout/useThemeSwitcher.ts" +export function useThemeSwitcher() { + const [theme, setTheme] = useState("light"); + + function toggleTheme() { + setTheme(theme === "light" ? "dark" : "light"); + } + + useEffect(() => { + document.body.classList.remove("light", "dark"); + document.body.classList.add(theme); + }, [theme]); + + return [theme, toggleTheme] as const; +} +``` + +사이드바의 구체적인 코드는 여러분 상상에 맡기겠습니다 😉. + +## layout에 widget 사용하기 + +상황에 따라 layout에 특정 비즈니스 로직을 추가하고 싶을 때가 있습니다. 특히 [React Router][ext-react-router]와 같은 라우터를 사용해 깊이 중첩된 경로를 다룰 때 이러한 요구가 발생합니다. 이러한 경우 layout을 shared나 widgets 폴더에 두는 것이 어려울 수 있습니다. 이는 [layer에 대한 import 규칙][import-rule-on-layers] 때문입니다: + +> slice의 module은 자신보다 하위 layers에 위치한 다른 slice만 import할 수 있습니다. + +이 문제가 정말 중요한지 먼저 고려해 봐야 합니다. 레이아웃이 _정말로 필요한가요?_ 그리고 그 레이아웃이 _정말로 widget이어야 할까요?_ 만약 해당 비즈니스 로직이 2-3개의 페이지에서만 사용되고, 레이아웃이 그 widget을 감싸는 역할이라면, 다음 두 가지 방법을 고려해 보세요: + +1. **App 레이어에서 인라인으로 레이아웃 작성하기** + App 레이어에서 직접 레이아웃을 정의하는 것이 좋습니다. 이렇게 하면 중첩된 라우터를 사용할 때 특정 경로 그룹에만 해당 레이아웃을 적용할 수 있어 유연하게 사용할 수 있습니다. + +2. **복사하여 붙여넣기** + 코드 추상화는 항상 좋은 선택은 아닙니다. 특히 레이아웃은 자주 변경되지 않기 때문에, 필요한 경우 해당 페이지만 수정하는 것이 더 효율적일 수 있습니다. 이렇게 하면 다른 페이지에 영향을 주지 않고 수정할 수 있습니다. 팀원들이 다른 페이지를 수정하는 걸 잊을까 봐 걱정된다면, 페이지 간의 관계를 주석으로 남겨보세요. 큰 프로젝트에서도 협업이 더 편해질 거예요. + +위의 내용이 적절하지 않은 경우, 레이아웃에 widget을 포함하는 두 가지 해결책이 있습니다: + +1. **render props나 slots 사용하기** + 대부분의 프레임워크에서는 컴포넌트 내부에 표시될 UI 요소를 외부에서 전달할 수 있는 기능을 제공합니다. React에서는 [render props][ext-render-props]라고 하며, Vue에서는 [slots][ext-vue-slots]이라고 부릅니다. +2. **레이아웃을 App 레이어로 이동하기** + 레이아웃을 `app/layouts` 등 App 레이어에 저장하고 원하는 widget을 구성할 수도 있습니다. + +## 추가 자료 + +React 및 Remix(React Router와 유사)의 인증 레이아웃 구축에 대한 예시는 [튜토리얼][tutorial]에서 확인하실 수 있습니다. + + +[tutorial]: /docs/get-started/tutorial +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-react-router]: https://reactrouter.com/ +[ext-render-props]: https://www.patterns.dev/react/render-props-pattern/ +[ext-vue-slots]: https://vuejs.org/guide/components/slots + diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/types.md b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/types.md new file mode 100644 index 0000000000..0c8a49279a --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/types.md @@ -0,0 +1,442 @@ +--- +sidebar_position: 2 +--- + +# 타입 + +이 가이드는 Typescript와 같은 정적 타입 언어의 데이터 타입을 다루는 방법과 FSD 구조 내에서 타입이 어떻게 활용되는지 설명합니다. + +:::info + +이 가이드에서 다루지 않는 질문이 있으신가요? 오른쪽 파란색 버튼을 눌러 피드백을 남겨주세요. 여러분의 의견을 반영해 가이드를 확장해 나가겠습니다! + +::: + +## 유틸리티 타입 + +유틸리티 타입은 자체로 큰 의미를 가지지는 않지만, 다른 타입과 자주 사용되는 경우가 많은 타입입니다. 예를 들어, 배열의 값을 나타내는 ArrayValues 타입을 정의할 수 있습니다. + +
+ +```ts +type ArrayValues = T[number]; +``` + +
+ Source: https://github.com/sindresorhus/type-fest/blob/main/source/array-values.d.ts +
+ +
+ +프로젝트에서 이러한 유틸리티 타입을 활용하려면, [`type-fest`][ext-type-fest] 같은 라이브러리를 설치하거나, 직접 `shared/lib`에 유틸리티 타입을 모아 라이브러리를 구축할 수 있습니다. 새로 추가할 타입과 이 라이브러리에 속하지 않는 타입을 명확하게 구분하는 것이 중요합니다. 예를 들어, 이를 `shared/lib/utility-types`로 수정하고 유틸리티 타입들에 대한 설명을 포함한 README 파일을 추가하는 것도 좋은 방법입니다. + +하지만 유틸리티 타입을 너무 많이 재사용하려고 하지 않는 것도 중요합니다. 재사용할 수 있다고 해서 꼭 모든 곳에서 사용할 필요는 없습니다. 모든 유틸리티 타입을 공유 폴더에 넣기보다는, 상황에 따라 필요한 파일 가까에에 두는 것이 더 좋을 떄도 있습니다. + +- 📂 pages + - 📂 home + - 📂 api + - 📄 ArrayValues.ts (유틸리티 타입) + - 📄 getMemoryUsageMetrics.ts (유틸리티 타입을 사용하는 코드) + +:::warning + +`shared/types` 폴더를 생성하거나 각 슬라이스에 `types`라는 세그먼트를 추가하고 싶은 마음이 들 수 있지만, 그렇게 하지 않는 것이 좋습니다.
+`types`라는 카테고리는 `components`나 `hooks`와 마찬가지로 내용이 무엇인지를 설명할 뿐, 코드의 목적을 명확히 설명하지 않습니다. 슬라이스는 해당 코드의 목적을 정확히 설명할 수 있어야 합니다. + +::: + +## 비즈니스 엔티티 및 상호 참조 관계 + +앱에서 가장 중요한 타입 중 하나는 비즈니스 엔티티, 즉 앱에서 다루는 객체들 입니다. +예를 들어, 음악 스트리밍 앱에서는 _Song_, _Album_ 등이 비즈니스 엔티티가 될 수 있습니다. + +비즈니스 엔티티는 주로 백엔드 바탕이기 떄문에, 백엔드 응답을 타입으로 정의하는 것이 첫 번째 단계입니다. +각 엔드포인트에 대한 요청 함수와 그 응답을 타입으로 지정하는 것이 좋습니다, 추가적인 타입 안정성을 위해 [Zod][ext-zod]와 같은 스키마 검증 라이브러리를 사용해 응답을 검증할 수도 있습니다. + +예를 들어, 모든 요청을 Shared에 보관하는 경우 이렇게 작성할 수 있습니다. + +```ts title="shared/api/songs.ts" +import type { Artist } from "./artists"; + +interface Song { + id: number; + title: string; + artists: Array; +} + +export function listSongs() { + return fetch('/api/songs').then((res) => res.json() as Promise>); +} +``` + +`Song` 타입은 다른 엔티티인 `Artist`를 참조합니다. 이와 같이 요청 관련 코드들을 Shared에 관리하면, 타입들의 서로 얽혀 있을 떄 관리가 용이해집니다. 만약 이 함수를 `entities/song/api`에 보관했다면, `entities/artist`에서 간단히 가져오는 것이 어려웠을 것 입니다. FSD 구조에서는 [레이어별 import 규칙][import-rule-on-layers]을 통해 슬라이스 간의 교차 import를 제한하고 있기 떄문입니다: + +> 슬라이스 안에 있는 모듈은 계층적으로 더 낮은 레이어에 위치한 슬라이스만 가져올 수 있습니다. + +이 문제를 해결하기 위한 두 가지 방법은 다음과 같습니다: + +1. **타입 매개변수화** + 타입이 다른 엔티티와 연결될 때, 타입 매개변수를 통해 처리할 수 있습니다. 예를 들어, Song 타입에 ArtistType이라는 제약 조건을 설정할 수 있습니다. + + ```ts title="entities/song/model/song.ts" + interface Song { + id: number; + title: string; + artists: Array; + } + ``` + + 이 방법은 일부 타입에 더 적합합니다. 예를 들어, `Cart = { items: Array }`처럼 간단한 타입은 다양한 제품 타입을 지원하기 쉽게 할 수 있습니다. 하지만 `Country`와 `City`처럼 더 밀접하게 연결된 타입은 분리하기 어렵습니다. + +2. **Cross-import (공개 API를 사용해 관리하기)** + FSD에서 엔티티 간 cross-imports를 허용하기 위해서는 공개 API를 사용할 수 있습니다. 예를 들어, `song`, `artist`, `playlist`라는 엔티티가 있고, 후자의 두 엔티티가 `song`을 참조해야 한다고 가정합니다. 이 경우, `song` 엔티티 내에 `artist`와 `playlist`용 공개 API를 따로 `@x` 표기를 만들어 사용할 수 있습니다. + + - 📂 entities + - 📂 song + - 📂 @x + - 📄 artist.ts (artist entities를 가져오기 위한 public API) + - 📄 playlist.ts (playlist.ts (playlist entities를 가져오기 위한 public API)) + - 📄 index.ts (일반적인 public API) + + 파일 `📄 entities/song/@x/artist.ts`의 내용은 `📄 entities/song/index.ts`와 유사합니다: + + ```ts title="entities/song/@x/artist.ts" + export type { Song } from "../model/song.ts"; + ``` + + 따라서 `📄 entities/artist/model/artist.ts` 파일은 다음과 같이 `Song`을 가져올 수 있습니다: + + ```ts title="entities/artist/model/artist.ts" + import type { Song } from "entities/song/@x/artist"; + + export interface Artist { + name: string; + songs: Array; + } + ``` + + 이렇게 엔티티 간 명시적으로 연결을 해두면 의존 관계를 파악하고 도메인 분리 수준을 유지하기 쉬워집니다. + +## 데이터 전송 객체와 mappers {#data-transfer-objects-and-mappers} + +데이터 전송 객체(Data Transfer Object, DTO)는 백엔드에서 오는 데이터의 구조를 나타내는 용어입니다. 떄로는 DTO를 그대로 사용하는 것이 편리할 수 있지만, 경우에 따라 프론트엔드에서는 불편할 수 있습니다. 이때 매퍼를 사용해 DTO를 더 편리한 형태로 변환합니다. + +### DTO의 위치 + +백엔드 타입이 별도의 패키지에 있는 경우(예: 프론트엔드와 백엔드에서 코드를 공유하는 경우) DTO를 해당 패키지에서 가져와 사용하면 됩니다. 백엔드와 프론트엔드 간 코드 공유가 없다면, 프론트엔드 코드베이스 어딘가에 DTO를 보관해야 하는데, 이를 아래에서 다루어 보겠습니다. + +`shared/api`에 요청 함수가 있다면, DTO 역시 해당 함수 바로 옆에 두는 것이 좋습니다: + +```ts title="shared/api/songs.ts" +import type { ArtistDTO } from "./artists"; + +interface SongDTO { + id: number; + title: string; + artist_ids: Array; +} + +export function listSongs() { + return fetch('/api/songs').then((res) => res.json() as Promise>); +} +``` + +앞에서 언급한 것처럼, 요청과 DTO를 shared에 두면 다른 DTO를 참조하기가 용이합니다. + +### Mappers의 위치 + +Mappers는 DTO를 받아 변환하는 역할을 하므로, DTO 정의와 가까운 위치에 두는 것이 좋습니다. 만약 요청과 DTO가 `shared/api`에 정의되어 있다면, mappers도 그곳에 위치하는 것이 적절합니다. + +```ts title="shared/api/songs.ts" +import type { ArtistDTO } from "./artists"; + +interface SongDTO { + id: number; + title: string; + disc_no: number; + artist_ids: Array; +} + +interface Song { + id: string; + title: string; + /** 노래의 전체 제목, 디스크 번호까지 포함된 제목입니다. */ + fullTitle: string; + artistIds: Array; +} + +function adaptSongDTO(dto: SongDTO): Song { + return { + id: String(dto.id), + title: dto.title, + fullTitle: `${dto.disc_no} / ${dto.title}`, + artistIds: dto.artist_ids.map(String), + }; +} + +export function listSongs() { + return fetch('/api/songs').then(async (res) => (await res.json()).map(adaptSongDTO)); +} +``` + +요청과 상태 관리 코드가 엔티티 슬라이스에 정의되어 있는 경우, mappers 역시 해당 슬라이스 내에 두는 것이 좋습니다. 이때 슬라이스 간 교차 참조가 발생하지 않도록 주의해야 합니다. + +```ts title="entities/song/api/dto.ts" +import type { ArtistDTO } from "entities/artist/@x/song"; + +export interface SongDTO { + id: number; + title: string; + disc_no: number; + artist_ids: Array; +} +``` + +```ts title="entities/song/api/mapper.ts" +import type { SongDTO } from "./dto"; + +export interface Song { + id: string; + title: string; + /** 노래의 전체 제목, 디스크 번호까지 포함된 제목입니다. */ + fullTitle: string; + artistIds: Array; +} + +export function adaptSongDTO(dto: SongDTO): Song { + return { + id: String(dto.id), + title: dto.title, + fullTitle: `${dto.disc_no} / ${dto.title}`, + artistIds: dto.artist_ids.map(String), + }; +} +``` + +```ts title="entities/song/api/listSongs.ts" +import { adaptSongDTO } from "./mapper"; + +export function listSongs() { + return fetch('/api/songs').then(async (res) => (await res.json()).map(adaptSongDTO)); +} +``` + +```ts title="entities/song/model/songs.ts" +import { createSlice, createEntityAdapter } from "@reduxjs/toolkit"; + +import { listSongs } from "../api/listSongs"; + +export const fetchSongs = createAsyncThunk('songs/fetchSongs', listSongs); + +const songAdapter = createEntityAdapter(); +const songsSlice = createSlice({ + name: "songs", + initialState: songAdapter.getInitialState(), + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchSongs.fulfilled, (state, action) => { + songAdapter.upsertMany(state, action.payload); + }) + }, +}); +``` + +### 중첩된 DTO 처리 방법 + +백엔드 응답에 여러 엔티티가 포함된 경우 문제가 될 수 있습니다. 예를 들어, 곡 정보에 저자의 ID뿐만 아니라 저자 객체 전체가 포함된 경우가 있을 수 있습니다. 이런 상황에서는 엔티티 간의 상호 참조를 피하기 어렵습니다. 데이터를 지우거나 백엔드 팀과 협의하지 않는 한, 이러한 경우에는 슬라이스 간 간접적인 연결 대신 명시적인 교차 참조를 사용하는 것이 좋습니다. 이를 위해 `@x` 표기법을 활용할 수 있으며, 다음은 Redux Toolkit을 사용한 예시입니다: + +```ts title="entities/song/model/songs.ts" +import { + createSlice, + createEntityAdapter, + createAsyncThunk, + createSelector, +} from '@reduxjs/toolkit' +import { normalize, schema } from 'normalizr' + +import { getSong } from "../api/getSong"; + +// Normalizr의 entities 스키마 정의 +export const artistEntity = new schema.Entity('artists') +export const songEntity = new schema.Entity('songs', { + artists: [artistEntity], +}) + +const songAdapter = createEntityAdapter() + +export const fetchSong = createAsyncThunk( + 'songs/fetchSong', + async (id: string) => { + const data = await getSong(id) + // 데이터를 정규화하여 리듀서가 예측 가능한 payload를 로드할 수 있도록 합니다: + // `action.payload = { songs: {}, artists: {} }` + const normalized = normalize(data, songEntity) + return normalized.entities + } +) + +export const slice = createSlice({ + name: 'songs', + initialState: songAdapter.getInitialState(), + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchSong.fulfilled, (state, action) => { + songAdapter.upsertMany(state, action.payload.songs) + }) + }, +}) + +const reducer = slice.reducer +export default reducer +``` + +```ts title="entities/song/@x/artist.ts" +export { fetchSong } from "../model/songs"; +``` + +```ts title="entities/artist/model/artists.ts" +import { createSlice, createEntityAdapter } from '@reduxjs/toolkit' + +import { fetchSong } from 'entities/song/@x/artist' + +const artistAdapter = createEntityAdapter() + +export const slice = createSlice({ + name: 'users', + initialState: artistAdapter.getInitialState(), + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchSong.fulfilled, (state, action) => { + // 같은 fetch 결과를 처리하며, 여기서 artists를 삽입합니다. + artistAdapter.upsertMany(state, action.payload.artists) + }) + }, +}) + +const reducer = slice.reducer +export default reducer +``` + +이 방법은 슬라이스 분리의 이점을 다소 제한할 수 있지만, 우리가 제어할 수 없는 두 엔티티 간의 관계를 명확하게 나타냅니다. 만약 이러한 엔티티가 리팩토링되어야 한다면, 함께 리팩토링해야 할 것입니다. + +## 전역 타입과 Redux + +전역 타입은 애플리케이션 전반에서 사용되는 타입을 의미하며, 크게 두 가지로 나눌 수 있습니다:
+1. 애플리케이션 특성이 없는 제너릭 타입 +2. 애플리케이션 전체에 알고 있어야 하는 타입 + +첫 번째 경우에는 관련 타입을 Shared 폴더 안에 적절한 세그먼트로 배치하면 됩니다. 예를 들어, 분석 전역 변수를 위한 인터페이스가 있다면 `shared/analytics`에 두는 것이 좋습니다. + +:::warning + +경고: `shared/types` 폴더를 생성하지 않는 것이 좋습니다. "타입"이라는 공통된 속성으로 관련 없는 항목들을 그룹화하면, 프로젝트에서 코드를 검색할 때 효율성이 떨어질 수 있습니다. + +::: + +두 번째 경우는 Redux를 사용하지만 RTK가 없는 프로젝트에서 자주 발생합니다. 최종 스토어 타입은 모든 리듀서를 추가한 후에만 사용 가능하지만, 이 스토어 타입은 앱 전체에서 사용하는 셀렉터에 필요합니다. 예를 들어, 일반적인 스토어 정의는 다음과 같습니다: + +```ts title="app/store/index.ts" +import { combineReducers, rootReducer } from "redux"; + +import { songReducer } from "entities/song"; +import { artistReducer } from "entities/artist"; + +const rootReducer = combineReducers(songReducer, artistReducer); + +const store = createStore(rootReducer); + +type RootState = ReturnType; +type AppDispatch = typeof store.dispatch; +``` + +`shared/store`에서 `useAppDispatch`와 `useAppSelector`와 같은 타입이 지정된 Redux 훅을 사용하는 것이 좋지만, [레이어에 대한 import 규칙][import-rule-on-layers] 떄문에 App 레이어에서 `RootState`와 `AppDispatch`를 import 할 수 없습니다. + +> 슬라이스의 모듈은 더 낮은 레이어에 위치한 다른 슬라이스만 import 할 수 있습니다. + +이 경우 권장되는 해결책은 Shared와 App 레이어 간에 암묵적인 의존성을 만드는 것입니다. `RootState`와 `AppDispatch` 두 타입은 유지보수 필요성이 적고 Redux를 사용하는 개발자들에게 익숙하므로 큰 문제 없이 사용할 수 있습니다. + +TypeScript에서는 다음과 같이 타입을 전역으로 선언할 수 있습니다: + +```ts title="app/store/index.ts" +/* 이전 코드 블록과 동일한 내용입니다… */ + +declare type RootState = ReturnType; +declare type AppDispatch = typeof store.dispatch; +``` + +```ts title="shared/store/index.ts" +import { useDispatch, useSelector, type TypedUseSelectorHook } from "react-redux"; + +export const useAppDispatch = useDispatch.withTypes() +export const useAppSelector: TypedUseSelectorHook = useSelector; +``` + +## 열거형 + +**일반적으로 열거형(enum)은 사용되는 위치와 최대한 가까운 곳에 정의하는 것이 좋습니다**. 열거형이 특정 기능과 관련된 값을 나타낸다면, 해당 기능 내에 정의해야 합니다. + +세그먼트 선택도 사용 위치에 따라 달라져야 합니다. 예를 들어, 화면에서 토스트 위치를 나타내는 열거형이라면 ui 세그먼트에 두는 것이 좋고, 백엔드 응답 상태 등을 나타낸다면 api 세그먼트에 두는 것이 적합합니다. + +프로젝트 전반에서 공통으로 사용되는 열거형도 있습니다. 예를 들어, 일반적인 백엔드 응답 상태나 디자인 시스템 토큰 등이 있습니다. 이 경우 Shared에 두되, 열거형이 나타내는 것을 기준으로 세그먼트를 선택하면 됩니다 (`api`는 응답 상태, `ui`는 디자인 토큰 등). + +## 타입 검증 스키마와 Zod + +데이터가 특정 형태나 제약 조건을 충족하는지 검증하려면 검증 스키마를 정의할 수 있습니다. TypeScript에서는 [Zod][ext-zod]와 같은 라이브러리를 많이 사용합니다. 검증 스키마는 가능하면 사용하는 코드와 같은 위치에 두는 것이 좋습니다. + +검증 스키마는 데이터를 파싱하며, 파싱에 실패하면 오류를 발생시킵니다.([Data transfoer objects and mappers](#data-transfer-objects-and-mappers) 토론을 참조하세요.) 가장 일반적인 검증 사례 중 하나는 백엔드에서 오는 데이터에 대한 것입니다. 데이터가 스키마와 일치하지 않는 경우 요청을 실패시키기를 원하기 때문에, 보통 `api` 세그먼트에 스키마를 두는 것이 좋습니다. + +사용자 입력(예: 폼)으로 데이터를 받을 경우, 입력된 데이터에 대해 바로 검증이 이루어져야 합니다. 이 경우 스키마를 `ui` 세그먼트 내 폼 컴포넌트 옆에 두거나, `ui` 세그먼트가 너무 복잡하다면 `model` 세그먼트에 둘 수 있습니다. + +## 컴포넌트 props와 context의 타입 정의 + +보통 props나 context 인터페이스는 이를 사용하는 컴포넌트나 컨텍스트와 같은 파일에 두는 것이 가장 좋습니다. 만약 Vue나 Svelte처럼 단일 파일 컴포넌트를 사용하는 프레임워크에서 여러 컴포넌트 간에 해당 인터페이스를 공유해야 한다면, `ui` 세그먼트 내 동일 폴더에 별도의 파일을 만들어 정의할 수 있습니다. + +예를 들어, React의 JSX에서는 다음과 같이 정의합니다: + +```ts title="pages/home/ui/RecentActions.tsx" +interface RecentActionsProps { + actions: Array<{ id: string; text: string }>; +} + +export function RecentActions({ actions }: RecentActionsProps) { + /* … */ +} +``` + +Vue에서 인터페이스를 별도 파일에 저장한 예는 다음과 같습니다: + +```ts title="pages/home/ui/RecentActionsProps.ts" +export interface RecentActionsProps { + actions: Array<{ id: string; text: string }>; +} +``` + +```html title="pages/home/ui/RecentActions.vue" + +``` + +## Ambient 선언 파일(*.d.ts) + +[Vite][ext-vite]나 [ts-reset][ext-ts-reset] 같은 일부 패키지는 앱 전반에서 작동하기 위해 Ambient 선언 파일을 필요로 합니다. 이러한 파일들은 보통 크거나 복잡하지 않기 때문에 `src/` 폴더에 두어도 괜찮습니다. 더 정리된 구조를 위해 `app/ambient/` 폴더에 두는 것도 좋은 방법입니다. + +타이핑이 없는 패키지인 경우, 해당 패키지를 미타입으로 선언하거나 직접 타이핑을 작성할 수 있습니다. 이러한 타이핑을 위한 좋은 위치는 `shared/lib` 폴더 내의 `shared/lib/untyped-packages` 폴더입니다. 이 폴더에 `%LIBRARY_NAME%.d.ts` 파일을 생성하고 필요한 타입을 선언합니다 + +```ts title="shared/lib/untyped-packages/use-react-screenshot.d.ts" +// 이 라이브러리는 타입 정의가 없으며 작성하는 것을 생략했습니다. +declare module "use-react-screenshot"; +``` + +## 타입 자동 생성 + +외부 소스로부터 타입을 생성하는 일은 흔히 발생합니다. 예를 들어, OpenAPI 스키마로부터 백엔드 타입을 생성하는 경우가 있습니다.
+이러한 타입을 위한 전용 위치를 코드베이스에 만드는 것이 좋습니다. 예를 들어 `shared/api/openapi`와 같은 위치가 적합합니다. 이상적으로는 이러한 파일이 무엇인지, 어떻게 재생성하는지 등을 설명하는 README 파일도 포함하는 것이 좋습니다. + +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-type-fest]: https://github.com/sindresorhus/type-fest +[ext-zod]: https://zod.dev +[ext-vite]: https://vitejs.dev +[ext-ts-reset]: https://www.totaltypescript.com/ts-reset diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/index.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/guides/index.mdx new file mode 100644 index 0000000000..6d7a02ccda --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/index.mdx @@ -0,0 +1,50 @@ +--- +hide_table_of_contents: true +pagination_prev: get-started/index +--- + +# 🎯 Guides + +PRACTICE-ORIENTED + +

+Feature-Sliced Design(FSD)의 적용을 위한 종합 가이드입니다. 구체적인 예시, 마이그레이션 전략, 그리고 FSD 코드에서 발견할 수 있는 흔한 설계상의 문제들을 다룹니다. FSD를 프로젝트에 도입하거나 기존 구조를 개선하고자 할 때 참고하기 좋은 리소스입니다. +

+ +## Main + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { ToolOutlined, ImportOutlined, BugOutlined, FunctionOutlined } from "@ant-design/icons"; + + + + + + + + + diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/issues/_category_.yaml b/i18n/kr/docusaurus-plugin-content-docs/current/guides/issues/_category_.yaml new file mode 100644 index 0000000000..19189179c1 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/issues/_category_.yaml @@ -0,0 +1,2 @@ +label: Code smells & Issues +position: 4 diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx new file mode 100644 index 0000000000..79608c4ed1 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx @@ -0,0 +1,21 @@ +--- +sidebar_position: 4 +sidebar_class_name: sidebar-item--wip +pagination_next: reference/index +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Cross-import + + + +> Cross-import는 Layer나 추상화가 원래의 책임 범위를 넘어설 때 발생합니다. 방법론에서는 이러한 Cross-import를 해결하기 위한 별도의 Layer를 정의합니다. + +## 참고 자료 + +- [(스레드) Cross-import가 불가피한 상황 논의](https://t.me/feature_sliced/4515) +- [(스레드) Entity에서 Cross-import 해결 방법](https://t.me/feature_sliced/3678) +- [(스레드) Cross-import와 책임 범위 관계](https://t.me/feature_sliced/3287) +- [(스레드) Segment 간 import 이슈 해결](https://t.me/feature_sliced/4021) +- [(스레드) Shared 내부 구조의 Cross-import 해결](https://t.me/feature_sliced/3618) diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/issues/desegmented.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/guides/issues/desegmented.mdx new file mode 100644 index 0000000000..e0cc2da21f --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/issues/desegmented.mdx @@ -0,0 +1,97 @@ +--- +sidebar_position: 2 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Desegmentation + + + +## 상황 + +프로젝트에서 동일한 도메인의 모듈들이 서로 연관되어 있음에도 불구하고, 프로젝트 전체에 불필요하게 분산되어 있는 경우가 많습니다. + +```sh +├── components/ +| ├── DeliveryCard +| ├── DeliveryChoice +| ├── RegionSelect +| ├── UserAvatar +├── actions/ +| ├── delivery.js +| ├── region.js +| ├── user.js +├── epics/ +| ├── delivery.js +| ├── region.js +| ├── user.js +├── constants/ +| ├── delivery.js +| ├── region.js +| ├── user.js +├── helpers/ +| ├── delivery.js +| ├── region.js +| ├── user.js +├── entities/ +| ├── delivery/ +| | ├── getters.js +| | ├── selectors.js +| ├── region/ +| ├── user/ +``` + +## 문제점 + +이는 높은 응집도 원칙을 위반하며, **Changes Axis의 과도한 확장**을 초래합니다. + +## 무시했을 때의 결과 + +- delivery 관련 로직 수정 시 여러 위치의 코드를 찾아 수정해야 하며, 이는 **Changes Axis를 불필요하게 확장**합니다 +- user 관련 로직을 이해하려면 프로젝트 전반의 **actions, epics, constants, entities, components**를 모두 찾아봐야 합니다 +- 암묵적 연결로 인해 도메인 영역이 비대해지고 관리가 어려워집니다 +- 불필요한 파일들이 쌓여 문제 인식이 어려워집니다 + +## 해결 방안 + +도메인이나 use case와 관련된 모듈들을 한 곳에 모아 배치합니다. + +이를 통해 모듈 학습이나 수정 시 필요한 모든 요소를 쉽게 찾을 수 있습니다. + +> 이 접근은 코드베이스의 탐색성과 가독성을 높이고, 모듈 간 관계를 더 명확하게 보여줍니다. + +```diff +- ├── components/ +- | ├── DeliveryCard +- | ├── DeliveryChoice +- | ├── RegionSelect +- | ├── UserAvatar +- ├── actions/ +- | ├── delivery.js +- | ├── region.js +- | ├── user.js +- ├── epics/{...} +- ├── constants/{...} +- ├── helpers/{...} + ├── entities/ + | ├── delivery/ ++ | | ├── ui/ # ~ components/ ++ | | | ├── card.js ++ | | | ├── choice.js ++ | | ├── model/ ++ | | | ├── actions.js ++ | | | ├── constants.js ++ | | | ├── epics.js ++ | | | ├── getters.js ++ | | | ├── selectors.js ++ | | ├── lib/ # ~ helpers + | ├── region/ + | ├── user/ +``` + +## 참고 자료 + +* [(아티클) Coupling과 Cohesion의 명확한 이해](https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/) +* [(아티클) Coupling, Cohesion과 Law of Demeter](https://medium.com/german-gorelkin/low-coupling-high-cohesion-d36369fb1be9) \ No newline at end of file diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/issues/routes.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/guides/issues/routes.mdx new file mode 100644 index 0000000000..146fdcc183 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/issues/routes.mdx @@ -0,0 +1,46 @@ +--- +sidebar_position: 3 +sidebar_class_name: sidebar-item--wip +sidebar_label: Routing +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Routing + + + +## 상황 + +Page의 URL이 하위 Layer에 하드코딩되어 있는 경우가 있습니다. + +```tsx title="entities/post/card" + + + ... + +``` + +## 문제점 + +URL이 Page Layer에 집중되지 않고, 하위 Layer에 분산되어 관리됩니다. + +## 무시했을 때의 결과 + +URL 변경 시 Page Layer 외의 여러 하위 Layer에 있는 URL과 redirect 로직을 모두 고려해야 합니다. + +결과적으로 단순한 Product Card 같은 Component도 Page의 책임을 가지게 되어, 프로젝트 구조가 불필요하게 복잡해집니다. + +## 해결 방안 + +URL과 redirect 로직은 Page Layer와 그 상위 Layer에서만 다루도록 합니다. + +이를 위해 composition, props 전달, Factory 패턴 등을 활용해 URL 정보를 하위 Layer에 전달합니다. + +## 참고 자료 + +- [(스레드) Entity/Feature/Widget에서 Routing 처리의 영향](https://t.me/feature_sliced/4389) +- [(스레드) Page에서만 Route 로직을 다뤄야 하는 이유](https://t.me/feature_sliced/3756) diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md b/i18n/kr/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md new file mode 100644 index 0000000000..0d0d2e67e0 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md @@ -0,0 +1,313 @@ +--- +sidebar_position: 3 +sidebar_label: 기존 아키텍처에서 전환하기 +--- + +# 기존 아키텍처에서 FSD로의 마이그레이션 + +이 가이드는 기존 아키텍처에서 Feature-Sliced Design(FSD)으로 전환할 때 도움이 되는 접근 방식을 설명합니다. + +아래는 일반적인 기존 아키텍처의 폴더 구조입니다. 이 가이드에서는 이를 예시로 사용하여 전환 과정을 설명합니다. +파란색 화살표를 클릭하여 폴더를 열어보세요. + +
+ 📁 src +
    +
  • +
    + 📁 actions +
      +
    • 📁 product
    • +
    • 📁 order
    • +
    +
    +
  • +
  • 📁 api
  • +
  • 📁 components
  • +
  • 📁 containers
  • +
  • 📁 constants
  • +
  • 📁 i18n
  • +
  • 📁 modules
  • +
  • 📁 helpers
  • +
  • +
    + 📁 routes +
      +
    • 📁 products.jsx
    • +
    • 📄 products.[id].jsx
    • +
    +
    +
  • +
  • 📁 utils
  • +
  • 📁 reducers
  • +
  • 📁 selectors
  • +
  • 📁 styles
  • +
  • 📄 App.jsx
  • +
  • 📄 index.js
  • +
+
+ +## 시작하기 전에 {#before-you-start} + +FSD로 전환을 고려할 때 팀에게 가장 중요한 질문은 _정말로 FSD가 필요한가?_ 라는 점입니다. FSD는 훌륭한 방법론이지만, 어떤 프로젝트에서는 굳이 필요하지 않을 수 있습니다. + +다음과 같은 이유가 있다면 전환을 고려해 볼 수 있습니다: + +1. 신규 팀원이 프로젝트에 적응하기 어려워하는 경우 +2. 코드 일부를 수정할 때, 관련 없는 다른 코드에 오류가 발생하는 경우가 **잦은** 경우 +3. 새 기능을 추가할 때 고려해야 할 사항이 너무 많아 어려움을 겪는 경우 + +**팀원들의 동의 없이 FSD로 전환을 시도하지 마십시오.** 팀 리더라고 해도 전환의 이점이 비용보다 크다는 점을 먼저 설득하는 것이 중요합니다. 새로운 아키텍처를 도입하는 데는 학습과 전환 비용이 따르므로 충분한 논의가 필요합니다. + +또한, 아키텍처 변경은 즉시 관리층에 가시적으로 드러나지 않을 수 있습니다. 프로젝트 전환을 시작하기 전에 관리자의 동의를 얻고, 전환이 프로젝트에 어떤 이점을 줄 수 있는지 설명하는 것이 중요합니다. + +:::tip + +프로젝트 매니저를 설득해야 한다면, 다음과 같은 점을 고려해 보세요: +1. FSD로의 전환은 점진적으로 진행될 수 있어 새로운 기능 개발을 중단할 필요가 없습니다. +2. 체계적인 아키텍처는 새로운 개발자가 업무에 익숙해지는 시간을 크게 줄여줄 수 있습니다. +3. FSD는 문서화된 아키텍처이기 때문에, 팀이 자체 문서화를 지속적으로 관리할 필요성이 줄어듭니다. + +::: + +--- + +마이그레이션을 시작하기로 결정했다면, `📁 src` 폴더에 대한 별칭(alias)을 설정하는 것이 첫 단계입니다. 이후 설명에서는 `@`를 `./src`의 별칭으로 사용하겠습니다. + +## 1단계: 페이지별로 코드 분리하기 {#divide-code-by-pages} + +대부분의 커스텀(custom) 아키텍처는 어느 정도 페이지별로 코드가 나뉘어 있습니다. 만약 `📁 pages` 폴더가 이미 있다면 이 단계를 건너뛸 수 있습니다. + +`📁 routes` 폴더만 있다면, `📁 pages` 폴더를 새로 만들고 `📁 routes`에서 가능한 많은 컴포넌트 코드를 pages로 이동하세요. 이상적인 구조는 작은 라우트 파일과 큰 페이지 파일을 갖추는 것입니다. 코드를 이동할 때는 페이지별로 폴더를 만들고, index 파일을 추가합니다. + +:::note + +현재는 페이지들이 서로를 참조해도 괜찮습니다. 지금은 페이지별로 구조를 명확하게 구분하는 데 집중하세요. 참조 문제는 나중에 해결할 수 있습니다. + +::: + +라우트 파일: + +```js title="src/routes/products.[id].js" +export { ProductPage as default } from "@/pages/product" +``` + +페이지 index 파일: + +```js title="src/pages/product/index.js" +export { ProductPage } from "./ProductPage.jsx" +``` + +페이지 컴포넌트 파일: + +```jsx title="src/pages/product/ProductPage.jsx" +export function ProductPage(props) { + return
; +} +``` + +## 2단계: 페이지에서 나머지 코드 분리하기 {#separate-everything-else-from-pages} + +`📁 src/shared` 폴더를 만들고 `📁 pages`나 `📁 routes`에 의존하지 않는 코드를 모두 이곳으로 이동합니다. 그다음 `📁 src/app` 폴더를 만들어 pages 또는 routes에 의존하는 코드를 이곳으로 옮깁니다. 라우트 파일도 포함됩니다. + +Shared layer 에는 slices가 없으므로, segments끼리 서로 import해도 괜찮습니다. + +이제 폴더 구조는 다음과 같아야 합니다: + +
+ 📁 src +
    +
  • +
    + 📁 app +
      +
    • +
      + 📁 routes +
        +
      • 📄 products.jsx
      • +
      • 📄 products.[id].jsx
      • +
      +
      +
    • +
    • 📄 App.jsx
    • +
    • 📄 index.js
    • +
    +
    +
  • +
  • +
    + 📁 pages +
      +
    • +
      + 📁 product +
        +
      • +
        + 📁 ui +
          +
        • 📄 ProductPage.jsx
        • +
        +
        +
      • +
      • 📄 index.js
      • +
      +
      +
    • +
    • 📁 catalog
    • +
    +
    +
  • +
  • +
    + 📁 shared +
      +
    • 📁 actions
    • +
    • 📁 api
    • +
    • 📁 components
    • +
    • 📁 containers
    • +
    • 📁 constants
    • +
    • 📁 i18n
    • +
    • 📁 modules
    • +
    • 📁 helpers
    • +
    • 📁 utils
    • +
    • 📁 reducers
    • +
    • 📁 selectors
    • +
    • 📁 styles
    • +
    +
    +
  • +
+
+ +## 3단계: 페이지 간의 cross-imports 해결하기 {#tackle-cross-imports-between-pages} + + + + +각 페이지가 다른 페이지를 import하고 있는 경우, 다음 두 가지 방법 중 하나를 선택하여 해결할 수 있습니다: + +1. 필요한 코드만 복사하여 붙여넣어 참조를 제거합니다. +2. 코드를 Shared layer의 적절한 segment로 이동합니다: + - UI 키트의 일부라면 `📁 shared/ui`로 이동 + - 설정 상수라면 `📁 shared/config`로 이동 + - 백엔드와의 상호작용 코드라면 `📁 shared/api`로 이동 + +:::note + +**코드를 복사해서 붙여넣는 것이 항상 잘못된 것은 아닙니다.** 경우에 따라, 모듈로 재사용하는 것보다 복사하여 독립적으로 사용하는 것이 더 나을 수 있습니다. 특히, 각 페이지에서 코드가 조금씩 달라질 가능성이 있는 경우에는 불필요한 의존성을 만들지 않도록 하는 것이 중요합니다. + +다만, 비즈니스 로직을 반복하지 않도록 주의하세요. 비즈니스 로직을 여러 곳에 복사해 둘 경우, 오류 수정 시 여러 위치에서 코드를 수정해야 할 수도 있습니다. + +::: + +## 4단계: Shared 레이어 정리하기 {#unpack-shared-layer} + +이 단계에서는 Shared 레이어에 많은 코드가 남아 있을 수 있지만, 가능하면 이를 피하는 것이 좋습니다. Shared 레이어는 코드베이스 내 다른 모든 레이어에 의존할 수 있으므로, 여기에 변경이 발생하면 예기치 않은 결과를 초래할 가능성이 높아집니다 + +한 페이지에서만 사용하는 코드가 있다면 해당 페이지의 slice로 이동하세요. _이는 actions, reducers, selectors에도 적용됩니다_. 모든 action을 한 곳에 모을 필요는 없으며, 관련된 action을 사용하는 위치에 가까이 배치하는 것이 더 좋습니다. + +최종 폴더 구조는 다음과 같아야 합니다: + +
+ 📁 src +
    +
  • 📁 app (unchanged)
  • +
  • +
    + 📁 pages +
      +
    • +
      + 📁 product +
        +
      • 📁 actions
      • +
      • 📁 reducers
      • +
      • 📁 selectors
      • +
      • +
        + 📁 ui +
          +
        • 📄 Component.jsx
        • +
        • 📄 Container.jsx
        • +
        • 📄 ProductPage.jsx
        • +
        +
        +
      • +
      • 📄 index.js
      • +
      +
      +
    • +
    • 📁 catalog
    • +
    +
    +
  • +
  • +
    + 📁 shared (only objects that are reused) +
      +
    • 📁 actions
    • +
    • 📁 api
    • +
    • 📁 components
    • +
    • 📁 containers
    • +
    • 📁 constants
    • +
    • 📁 i18n
    • +
    • 📁 modules
    • +
    • 📁 helpers
    • +
    • 📁 utils
    • +
    • 📁 reducers
    • +
    • 📁 selectors
    • +
    • 📁 styles
    • +
    +
    +
  • +
+
+ +## 5단계: 기술적 목적에 따른 코드 정리 {#organize-by-technical-purpose} + +FSD에서는 코드를 _segments_ 단위로 나누어, 기술적 목적에 따라 구분합니다. 다음은 일반적인 세그먼트 예시입니다: + +- `ui` — UI 표시와 관련된 요소들: UI 컴포넌트, 날짜 형식 지정, 스타일 등 +- `api` — 백엔드와의 상호작용: 요청 함수, 데이터 타입, 매퍼 등 +- `model` — 데이터 모델: 스키마, 인터페이스, 스토어 및 비즈니스 로직 +- `lib` — 이 슬라이스의 다른 모듈에서 필요한 라이브러리 코드 +- `config` — 설정 파일과 기능 플래그 + +필요에 따라 자체적인 segments를 추가할 수도 있습니다. 단, `components`, `actions`, `types`, `utils`처럼 코드의 성격에 따라 그룹화하기보다는, 코드의 목적에 따라 구분하는 것이 좋습니다. + +각 페이지에 segments를 나누어 코드를 정리하세요. 이미 `ui` 세그먼트가 있다면, 이제는 actions, reducers, selectors를 위한 `model` 세그먼트를 만들고, thunks와 mutations를 위한 `api` segments도 추가할 차례입니다. + +또한, Shared 레이어에 있는 폴더를 다음과 같이 재배치하세요: +- `📁 components`, `📁 containers` — 대부분은 `📁 shared/ui`로 이동 +- `📁 helpers`, `📁 utils` — 재사용되는 헬퍼들이 남아 있다면 기능에 따라 그룹화하여 `📁 shared/lib`로 이동 +- `📁 constants` — 기능에 따라 그룹화하여 `📁 shared/config`로 이동 + + +## 선택 단계 {#optional-steps} + +### 6단계: 여러 페이지에서 사용하는 Redux slices를 entities/features으로 정리하기 {#form-entities-features-from-redux} + +일반적으로 여러 페이지에서 재사용되는 Redux slices는 제품이나 사용자와 같이 비즈니스와 관련이 깊습니다. 이러한 경우, slice를 Entities layer로 이동하고, 하나의 폴더에 하나의 entity가 있도록 구성합니다. 만약 Redux slice가 사용자의 특정 액션과 관련이 있다면, 이를 Features layer로 이동할 수 있습니다. + +Entities와 기능은 서로 독립적으로 사용될 수 있도록 설계됩니다. business domain에 따라 entities 간에 본질적인 연결이 필요한 경우, [business entities 연결 가이드][business-entities-cross-relations]를 참고하여 이러한 연결을 효율적으로 조직하는 방법을 확인하세요. + +이 slices와 관련된 API 함수는 `📁 shared/api`에 그대로 두어도 괜찮습니다. + +### 7단계: modules 리팩터링 {#refactor-your-modules} + +`📁 modules` 폴더는 일반적으로 비즈니스 로직을 담고 있어 FSD의 Features layer와 성격이 비슷합니다. 일부 모듈은 앱 헤더와 같이 큰 UI 요소를 설명하는 역할을 할 수도 있습니다. 이 경우 해당 modules을 Widgets layer로 이동하는 것이 좋습니다. + +### 8단계: `shared/ui`에서 UI 기본 요소 정리하기 {#form-clean-ui-foundation} + +`📁 shared/ui`는 비즈니스 로직이 포함되지 않고 재사용 가능한 UI 요소들로만 구성되어야 합니다. + +이전 `📁 components`와 `📁 containers`에 있던 UI 컴포넌트를 리팩터링하여 비즈니스 로직을 분리하고, 해당 로직을 상위 layers로 이동시키세요. 많은 곳에서 사용되지 않는다면, 코드 복사를 통해 필요한 부분에만 사용해도 좋습니다. + +## 추가 참고 자료 {#see-also} + +- [(러시아어 영상) Ilya Klimov — "끝없는 리팩터링의 악순환에서 벗어나기: 기술 부채가 동기와 제품에 미치는 영향](https://youtu.be/aOiJ3k2UvO4) + +[ext-steiger]: https://github.com/feature-sliced/steiger +[business-entities-cross-relations]: /docs/guides/examples/types#business-entities-and-their-cross-references diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md b/i18n/kr/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md new file mode 100644 index 0000000000..a5b32f934c --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md @@ -0,0 +1,171 @@ +--- +sidebar_position: 4 +--- + +# v1에서 v2로의 마이그레이션 + +## v2로의 전환 이유 + +**feature-slices**의 원래 개념은 [2018년에 발표되었습니다][ext-tg-spb]. + +그 이후로 여러 방법론이 발전하면서 개념이 확장되었지만, 기본 원칙은 유지되었습니다 **[기본 원칙 참고][ext-v1]**: + +- 표준화된 프론트엔드 프로젝트 구조 사용 +- *비즈니스 로직*에 따라 애플리케이션을 우선적으로 분리 +- 의도하지 않은 부작용과 순환 의존성 방지하기 위해 *독립적인 기능* 단위 사용 +- 모듈 내부로 직접 접근하지 않도록 *공용 API*를 사용하여 구조를 안정화 + +그러나 이전 버전의 방법론에는 여전히 몇 가지 **개선점**이 필요했습니다. + +- 반복적인 코드(보일러플레이트)를 유발하는 경우가 있음 +- 코드베이스가 복잡해지고, 추상화 간의 규칙이 모호해지는 경우가 있음 +- 암묵적인 아키텍처 설계 방식이 프로젝트 확장과 신규 입사자의 온보딩을 어렵게 함 + +새로운 버전([v2][ext-v2])은 기존 장점을 유지하면서도 **이러한 단점을 해결**하도록 설계되었습니다. + +또한 2018년 이후, [또 다른 유사한 방법론][ext-fdd-issues]인 [**feature-driven**][ext-fdd]이 개발되었으며, 이는 [Oleg Isonen][ext-kof]에 의해 처음 발표되었습니다. + +두 가지 접근 방식을 통합한 후, **기존의 실무를 개선**하고 발전시켜 애플리케이션을 더 유연하고 명확하며 효율적으로 만들었습니다. + +> 이러한 변경 사항은 방법론의 이름에도 영향을 주어, 이제는 "feature-sliced"라고 불리게 되었습니다. + +## 프로젝트를 v2로 마이그레이션하는 이유 + +> `WIP:` 현재 방법론 버전은 개발 중이며 일부 세부 사항이 변경될 수 있습니다. + +#### 🔍 더 직관적이고 간단한 아키텍처 + +v2 방법론은 **더 직관적이고 일반적인 추상화를 제공하여 개발자가 로직 분리를 쉽게 할 수 있게 합니다.** + +이를 통해 신규 인력의 온보딩이 수월해지며, 프로젝트 상태를 빠르게 이해하고 애플리케이션의 비즈니스 로직을 효과적으로 분배하는 데 도움이 됩니다. + +#### 📦 더 유연하고 실용적인 모듈화 + +v2 방법론은 **로직을 더 유연하게 배치할 수 있습니다:** + +- 독립적인 부분을 처음부터 새로 리팩터링할 수 있는 유연성을 제공합니다. +- 불필요하게 의존성이 얽히지 않도록 동일한 추상화를 사용할 수 있습니다. +- 새로운 모듈을 추가할 때 *(layer => slice => segment)* 구조가 더 간단해집니다. + +#### 🚀 더 풍부한 명세, 계획, 커뮤니티 지원 + +현재 `코어 팀`은 최신(v2) 버전의 방법론 개발에 활발히 참여하고 있습니다. + +따라서 v2에는 다음과 같은 요소들이 추가로 제공됩니다: + +- 더 다양한 사례와 문제 설명 +- 적용에 관한 가이드 +- 실전 예제 추가 +- 신규 인력의 온보딩과 방법론 개념 학습을 위한 풍부한 문서 제공 +- 아키텍처 개념과 규칙을 준수할 수 있도록 지원하는 툴킷 + +> 물론, 첫 번째 버전에 대한 사용자 지원도 제공될 것입니다. 하지만 최신 버전이 최우선 과제입니다. +> +> 향후 주요 업데이트가 있을 때, **팀과 프로젝트에 대한 부담 없이** 최신 버전(v2) 방법론에 접근할 수 있습니다. + +## 변경 사항 + +### `주요 변경` Layers + +이제 방법론은 상위 레벨에서 layers를 명확히 구분합니다. + +- `/app` > `/processes` > **`/pages`** > **`/features`** > `/entities` > `/shared` +- *이제 모든 것이 기능/페이지로 취급되지는 않습니다.* +- 이 접근 방식은 [layers에 대한 명시적인 규칙 설정][ext-tg-v2-draft]을 가능하게 합니다. +- 모듈의 위치한 **layer가 높을수록** 더 많은 **컨텍스트**를 가집니다. + + *(즉, 각 layer의 모듈은 하위 layer의 모듈만 가져올 수 있으며 상위 layer는 가져올 수 없습니다.)* + +- 모듈의 위치한 **layer가 낮을수록** 변경에 따른 **위험과 책임이** 커집니다. + + *(보통 하위 layer가 더 자주 사용되기 때문입니다)* + +### `주요 변경` Shared + +프로젝트의 src 루트에 있던 `/ui`, `/lib`, `/api`와 같은 인프라 추상화는 이제 `/src/shared`라는 별도 디렉토리에 분리됩니다. + +- `shared/ui` - 여전히 애플리케이션의 공통 UI 컴포넌트 모음(선택 사항) + - *기존의 `Atomic Design` 사용도 가능합니다.* +- `shared/lib` - 로직 구현을 위한 보조 라이브러리 모음 + - *헬퍼 코드가 모여있는 덤프는 지양* +- `shared/api` - API에 접근하는 공용 진입점 + - *각 기능/페이지별로 로컬에 등록할 수 있지만, 권장하지 않음* +- 이전과 마찬가지로 `shared`에 비즈니스 로직에 대한 명시적 결합이 없어야 합니다. + - *필요하다면 해당 관계를 `entities` 레벨이나 상위 레벨로 올려야 합니다.* + +### `새로 추가된` Entities, Processes + +v2에서는 로직 복잡성과 높은 결합을 줄이기 위한 **새로운 추상화**가 추가되었습니다. + +- `/entities` - **business entities** layer, 프론트엔드에서 필요한 비즈니스 모델이나 인위적인 entities를 포함하는 slices + - *예시: `user`, `i18n`, `order`, `blog`* +- `/processes` - 애플리케이션 전반에 걸친 **비즈니스 프로세스** layer + - **선택적 layer**, 보통 *로직이 확장되어 여러 페이지에 걸쳐 분산되기 시작할 때* 사용을 권장 + - *예시: `payment`, `auth`, `quick-tour`* + +### `주요 변경` 추상화 및 네이밍 + +이제 특정 추상화 및 [명확한 네이밍 권장사항][refs-adaptability]이 정의되었습니다. + +[disc-process]: https://github.com/feature-sliced/documentation/discussions/20 +[disc-features]: https://github.com/feature-sliced/documentation/discussions/23 +[disc-entities]: https://github.com/feature-sliced/documentation/discussions/18#discussioncomment-422649 +[disc-shared]: https://github.com/feature-sliced/documentation/discussions/31#discussioncomment-453020 + +[disc-ui]: https://github.com/feature-sliced/documentation/discussions/31#discussioncomment-453132 +[disc-model]: https://github.com/feature-sliced/documentation/discussions/31#discussioncomment-472645 +[disc-api]: https://github.com/feature-sliced/documentation/discussions/66 + +#### 레이어 + +- `/app` — **애플리케이션 초기화 layer** + - *이전 명칭: `app`, `core`,`init`, `src/index` (가끔 사용됨)* +- `/processes` — [**비즈니스 process layer**][disc-process] + - *이전 명칭: `processes`, `flows`, `workflows`* +- `/pages` — **애플리케이션 페이지 layer** + - *이전 명칭: `pages`, `screens`, `views`, `layouts`, `components`, `containers`* +- `/features` — [**기능 모듈 layer**][disc-features] + - *이전 명칭: `features`, `components`, `containers`* +- `/entities` — [**비즈니스 entity layer**][disc-entities] + - *이전 명칭: `entities`, `models`, `shared`* +- `/shared` — [**재사용 가능한 인프라 코드 layer**][disc-shared] 🔥 + - *이전 명칭: `shared`, `common`, `lib`* + +#### 세그먼트 + +- `/ui` — [**UI segment**][disc-ui] 🔥 + - *이전 명칭: `ui`, `components`, `view`* +- `/model` — [**비즈니스 로직 segment**][disc-model] 🔥 + - *이전 명칭: `model`, `store`, `state`, `services`, `controller`* +- `/lib` — **보조 코드 segment** + - *이전 명칭: `lib`, `libs`, `utils`, `helpers`* +- `/api` — [**API segment**][disc-api] + - *이전 명칭: `api`, `service`, `requests`, `queries`* +- `/config` — **애플리케이션 설정 segment** + - *이전 명칭: `config`, `env`, `get-env`* + +### `개선된` 낮은 결합 + +이제 새로운 layers 덕분에 모듈 간 [낮은 결합 원칙을 준수][refs-low-coupling]하기가 훨씬 쉬워졌습니다. + +*동시에, 모듈 간 "결합을 해제"하기 어려운 상황은 가능한 한 피하는 것이 여전히 권장됩니다.* + +## 참고 자료 + +- [React SPB Meetup #1 발표 노트"][ext-tg-spb] +- [React Berlin Talk - Oleg Isonen "Feature Driven Architecture"][ext-kof-fdd] +- [v1과의 비교 (커뮤니티 채팅)](https://t.me/feature_sliced/493) +- [v2에 대한 새로운 아이디어와 설명 (atomicdesign 채팅)][ext-tg-v2-draft] +- [새로운 버전 방법론 (v2)에 대한 추상화 및 네이밍 논의](https://github.com/feature-sliced/documentation/discussions/31) + +[refs-low-coupling]: /docs/reference/slices-segments#zero-coupling-high-cohesion +[refs-adaptability]: /docs/about/understanding/naming + +[ext-v1]: https://feature-sliced.github.io/featureslices.dev/v1.0.html +[ext-tg-spb]: https://t.me/feature_slices +[ext-fdd]: https://github.com/feature-sliced/documentation/tree/rc/feature-driven +[ext-fdd-issues]: https://github.com/kof/feature-driven-architecture/issues +[ext-v2]: https://github.com/feature-sliced/documentation +[ext-kof]: https://github.com/kof +[ext-kof-fdd]: https://www.youtube.com/watch?v=BWAeYuWFHhs +[ext-tg-v2-draft]: https://t.me/atomicdesign/18708 diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md b/i18n/kr/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md new file mode 100644 index 0000000000..64f0fe16a7 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md @@ -0,0 +1,70 @@ +--- +sidebar_position: 3 +--- + +# v2.0에서 v2.1로의 마이그레이션 + +v2.1의 핵심 변화는 페이지 중심(Page-First) 접근 방식을 통한 인터페이스 구조화입니다. + +### v2.0 방식 +이전 버전에서는: +- Entity와 Feature을 최소 단위로 분해 +- 이를 기반으로 Widget과 Page를 구성 +- 대부분의 로직이 Entity와 Feature 계층에 집중 +- Page는 단순 조합 계층으로 취급 + +### v2.1 방식 +새로운 버전에서는: +- Page를 시작점으로 설정 +- UI와 비즈니스 로직을 우선적으로 Page 내에 구현 +- Shared 계층은 순수하게 재사용 가능한 기반 코드만 관리 +- 여러 페이지에서 공통으로 사용되는 로직만 하위 계층으로 분리 + +Feature-Sliced Design(v2.1)에서는 Entity 간 Cross Import를 표준화하기 위해 `@x` 표기법이 도입되었습니다. + +## 마이그레이션 프로세스 {#how-to-migrate} + +v2.1은 하위 호환성을 보장하므로, v2.0 프로젝트는 수정 없이도 정상 동작합니다. 다만, 새로운 아키텍처 모델의 이점을 활용하기 위해 다음과 같은 단계적 개선을 권장합니다. + +### 1. 슬라이스(Slice) 병합 + +[Steiger][steiger] 린터(Linter)를 활용하여 코드베이스를 분석할 수 있습니다: + +주요 린트 규칙: +- [`insignificant-slice`][insignificant-slice]: 단일 Page에서만 사용되는 Slice 감지 + - 해당 Slice의 코드를 관련 Page로 이동 권장 +- [`excessive-slicing`][excessive-slicing]: 과도한 Slices 분할 감지 + - 유지보수성 향상을 위한 Slices 통합 또는 그룹화 제안 + +```bash +npx steiger src +``` + +Steiger를 사용하면 프로젝트에서 한 번만 사용되는 Slice들을 찾아낼 수 있습니다. 이러한 Slice들이 정말 독립적인 Slice로 존재할 필요가 있는지 검토해야 합니다. + +:::tip Slice 관리 +각 계층은 해당 계층에 속한 모든 Slices의 네임스페이스를 관리합니다. 이는 전역 변수를 관리하는 것과 비슷한 개념입니다: +- 전역 변수는 꼭 필요한 경우에만 사용하듯이 +- Slice도 실제로 재사용되는 경우에만 독립적으로 분리하세요 +- 한 곳에서만 사용되는 코드는 해당 Page나 Feature 내부로 이동하는 것이 좋습니다 +::: + +### 2. Cross Import 표준화 + +새로운 `@x-` 표기법을 사용하여 Entity 간 참조를 표준화합니다: + +```ts title="entities/B/some/file.ts" +// 표준화된 Cross Import 방식 +import type { EntityA } from "entities/A/@x/B"; +``` + +자세한 내용은 [Cross Import를 위한 Public API][public-api-for-cross-imports] 문서를 참조하세요. + +[insignificant-slice]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice +[steiger]: https://github.com/feature-sliced/steiger +[excessive-slicing]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/excessive-slicing +[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports + + + + diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx new file mode 100644 index 0000000000..1a31af2b7e --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx @@ -0,0 +1,117 @@ +--- +sidebar_position: 10 +--- +# NextJS와 함께 사용하기 + +NextJS에서도 FSD(Feature-Sliced Design) 아키텍처를 구현할 수 있지만, 두 가지 점에서 NextJS의 프로젝트 구조 요구사항과 FSD 구조 간에 충돌이 발생합니다: + +- `pages` 폴더와의 라우팅 방식 차이 +- NextJS에서 `app` 폴더의 충돌 문제 또는 부재 + +## FSD와 NextJS의 `페이지` 레이어 간 충돌 {#pages-conflict} + +NextJS는 애플리케이션 라우트를 정의하기 위해 `pages` 폴더를 사용하며, `pages` 폴더 내의 파일이 URL과 매핑되도록 설정합니다. +하지만 이 방식은 FSD(Folder Slice Design) `개념에 맞지는 않습니다`. 특히, NextJS의 라우팅 방식으로는 FSD의 슬라이스 구조를 평평하게 유지하기 어려운 점이 있습니다. + +### NextJS의 `pages` 폴더를 프로젝트 루트 폴더로 이동하기 (권장) + +프로젝트 루트에 `pages` 폴더를 배치하고, FSD 구조에 맞춘 페이지들을 NextJS의 `pages` 폴더로 옮깁니다. +이렇게 하면 `src` 폴더 내에서 FSD 구조를 유지할 수 있습니다. + +```sh +├── pages # NextJS 페이지 폴더 +├── src +│ ├── app +│ ├── entities +│ ├── features +│ ├── pages # FSD 페이지 폴더 +│ ├── shared +│ ├── widgets +``` + +### FSD 구조 내 `pages` 폴더 이름 변경하기 + +다른 방법으로는 FSD 구조 내에서 `pages` 폴더의 이름을 변경하여 NextJS의 `pages` 폴더와 충돌을 피할 수도 있습니다. +예를 들어, `pages` 폴더를 `views`로 이름을 변경하면 `src` 폴더 내의 FSD 구조를 유지하면서도 NextJS의 요구 사항과 충돌하지 않게 됩니다. + +```sh +├── app +├── entities +├── features +├── pages # NextJS 페이지 폴더 +├── views # 이름이 변경된 FSD 페이지 폴더 +├── shared +├── widgets +``` + +이름을 변경하는 경우, 이를 프로젝트의 README나 내부 문서에 명확히 기록하여 변경 사항이 잘 전달되도록 하는 것이 좋습니다. 이러한 변경은 ["프로젝트 지식"][project-knowledge]의 일부로 문서화하는 것이 중요합니다. + +## NextJS에서 `app` 폴더 부재 문제 {#app-absence} + +NextJS 13버전 이하에서는 명시적인 `app` 폴더가 없으며, +대신 `_app.tsx` 파일이 모든 페이지를 감싸는 컴포넌트 역할을 합니다. + +### `pages/_app.tsx` 파일에 app 기능 가져오기 + +NextJS 구조에서 `app` 폴더가 없는 문제를 해결하려면, `app` 폴더 내에 `App` 컴포넌트를 생성하고, 이를 `pages/_app.tsx`에 가져와 NextJS가 사용할 수 있도록 설정하면 됩니다. 예를 들어: + +```tsx +// app/providers/index.tsx + +const App = ({ Component, pageProps }: AppProps) => { + return ( + + + + + + + + ); +}; + +export default App; +``` + +그 다음 `pages/_app.tsx` 파일에서 `App` 컴포넌트와 프로젝트 전역 스타일을 다음과 같이 가져올 수 있습니다: + +```tsx +// pages/_app.tsx + +import 'app/styles/index.scss' + +export { default } from 'app/providers'; +``` + +## App Router 사용하기 {#app-router} + +NextJS 13.4 버전에서는 App Router가 안정화되었습니다. App Router를 사용하면 `pages` 폴더 대신 `app` 폴더를 통해 라우팅을 처리할 수 있습니다. +FSD 원칙을 준수하기 위해, NextJS의 `app` 폴더도 `pages` 폴더와의 충돌 문제를 해결한 것과 동일한 방식으로 다루어야 합니다. + +이를 위해 NextJS의 `app` 폴더를 프로젝트 루트로 이동하고, FSD 페이지들을 `app` 폴더로 옮기는 방식을 사용합니다. +이렇게 하면 `src` 폴더 내에서 FSD 프로젝트 구조를 유지할 수 있습니다. +또한, App Router와 Pages Router가 호환되므로 `pages` 폴더를 프로젝트 루트에 추가하는 것이 필요합니다. + +``` +├── app # NextJS app 폴더 +├── pages # NextJS pages 폴더 +│ ├── README.md # 해당 폴더의 목적과 역할에 대한 설명 +├── src +│ ├── app # FSD app 폴더 +│ ├── entities +│ ├── features +│ ├── pages # FSD pages 폴더 +│ ├── shared +│ ├── widgets +``` + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)][ext-app-router-stackblitz] + +## 관련 항목 {#see-also} + +- [(스레드) NextJS의 pages 디렉토리에 대한 토론](https://t.me/feature_sliced/3623) + +[project-knowledge]: /docs/about/understanding/knowledge-types +[ext-app-router-stackblitz]: https://stackblitz.com/edit/stackblitz-starters-aiez55?file=README.md + + diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx new file mode 100644 index 0000000000..0c3e01e3cf --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx @@ -0,0 +1,425 @@ +--- +sidebar_position: 10 +--- +# React Query와 함께 사용하기 + +## “키를 어디에 두어야 하는가” 문제 + +### 해결책 — 엔티티별로 분리하기 + +프로젝트가 이미 엔티티 단위로 구성되어 있으며, 각 요청이 단일 엔티티에 해당한다면, 엔티티별로 코드를 구성하는 것이 좋습니다. 예를 들어, 다음과 같은 디렉토리 구조를 사용할 수 있습니다: + +```sh +└── src/ # + ├── app/ # + | ... # + ├── pages/ # + | ... # + ├── entities/ # + | ├── {entity}/ # + | ... └── api/ # + | ├── `{entity}.query` # 쿼리 키와 함수 + | ├── `get-{entity}` # 엔티티 조회 함수 + | ├── `create-{entity}` # 엔티티 생성 함수 + | ├── `update-{entity}` # 엔티티 업데이트 함수 + | ├── `delete-{entity}` # 엔티티 삭제 함수 + | ... # + | # + ├── features/ # + | ... # + ├── widgets/ # + | ... # + └── shared/ # + ... # +``` + +만약 엔티티 간에 연결이 필요한 경우 (예: Country 엔티티에 City 엔티티 필드가 포함되는 경우), [교차 가져오기를 위한 공개 API][public-api-for-cross-imports]을 사용하거나 대안으로 아래의 구조를 고려할 수 있습니다. + +### 대안 방안 — shared에 유지하기 + +엔티티별 분리가 적절하지 않은 경우, 다음과 같은 구조를 사용할 수 있습니다: + +```sh +└── src/ # + ... # + └── shared/ # + ├── api/ # + ... ├── `queries` # 쿼리 팩토리들 + | ├── `document.ts` # + | ├── `background-jobs.ts` # + | ... # + └── index.ts # +``` + +이후 `@/shared/api/index.ts`에서 다음과 같이 사용합니다: + +```ts title="@/shared/api/index.ts" +export { documentQueries } from "./queries/document"; +``` + +## "mutation 위치 설정" 문제 + +쿼리와 mutation을 같은 위치에 두는 것은 권장되지 않습니다. 다음 두 가지 옵션이 있습니다: + +### 1. 사용 위치 근처의 `api` 디렉토리에서 커스텀 훅 정의하기 + +```tsx title="@/features/update-post/api/use-update-title.ts" +export const useUpdateTitle = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ id, newTitle }) => + apiClient + .patch(`/posts/${id}`, { title: newTitle }) + .then((data) => console.log(data)), + + onSuccess: (newPost) => { + queryClient.setQueryData(postsQueries.ids(id), newPost); + }, + }); +}; +``` + +### 2. 공용 또는 엔티티에서 mutation 함수를 정의하고, 컴포넌트에서 `useMutation`을 직접 사용하기 + +```tsx +const { mutateAsync, isPending } = useMutation({ + mutationFn: postApi.createPost, +}); +``` + +```tsx title="@/pages/post-create/ui/post-create-page.tsx" +export const CreatePost = () => { + const { classes } = useStyles(); + const [title, setTitle] = useState(""); + + const { mutate, isPending } = useMutation({ + mutationFn: postApi.createPost, + }); + + const handleChange = (e: ChangeEvent) => + setTitle(e.target.value); + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + mutate({ title, userId: DEFAULT_USER_ID }); + }; + + return ( +
+ + + Create + + + ); +}; +``` + +## 요청의 조직화 + +### 쿼리 팩토리 + +쿼리 팩토리는 쿼리 키 목록을 반환하는 함수를 포함한 객체입니다. 사용 방법은 다음과 같습니다: + +```ts +const keyFactory = { + all: () => ["entity"], + lists: () => [...postQueries.all(), "list"], +}; +``` + +:::info +`queryOptions`는 react-query@v5의 내장 유틸리티입니다 (선택 사항) + +```ts +queryOptions({ + queryKey, + ...options, +}); +``` + +더 큰 타입 안정성, react-query의 향후 버전과의 호환성, 함수 및 쿼리 키에 대한 쉬운 액세스를 위해, "@tanstack/react-query"의 내장 queryOptions 함수를 사용할 수 있습니다 [(자세한 내용은 여기)](https://tkdodo.eu/blog/the-query-options-api#queryoptions). + +::: + +### 1. 쿼리 팩토리 생성 예시 + +```tsx title="@/entities/post/api/post.queries.ts" +import { keepPreviousData, queryOptions } from "@tanstack/react-query"; +import { getPosts } from "./get-posts"; +import { getDetailPost } from "./get-detail-post"; +import { PostDetailQuery } from "./query/post.query"; + +export const postQueries = { + all: () => ["posts"], + + lists: () => [...postQueries.all(), "list"], + list: (page: number, limit: number) => + queryOptions({ + queryKey: [...postQueries.lists(), page, limit], + queryFn: () => getPosts(page, limit), + placeholderData: keepPreviousData, + }), + + details: () => [...postQueries.all(), "detail"], + detail: (query?: PostDetailQuery) => + queryOptions({ + queryKey: [...postQueries.details(), query?.id], + queryFn: () => getDetailPost({ id: query?.id }), + staleTime: 5000, + }), +}; +``` + +### 2. 애플리케이션 코드에서의 쿼리 팩토리 사용 예시 +```tsx +import { useParams } from "react-router-dom"; +import { postApi } from "@/entities/post"; +import { useQuery } from "@tanstack/react-query"; + +type Params = { + postId: string; +}; + +export const PostPage = () => { + const { postId } = useParams(); + const id = parseInt(postId || ""); + const { + data: post, + error, + isLoading, + isError, + } = useQuery(postApi.postQueries.detail({ id })); + + if (isLoading) { + return
Loading...
; + } + + if (isError || !post) { + return <>{error?.message}; + } + + return ( +
+

Post id: {post.id}

+
+

{post.title}

+
+

{post.body}

+
+
+
Owner: {post.userId}
+
+ ); +}; +``` + +### 쿼리 팩토리 사용의 장점 +- **요청 구조화**: 팩토리를 통해 모든 API 요청을 한 곳에 조직화하여 코드의 가독성과 유지보수성을 높입니다. +- **쿼리 및 키에 대한 편리한 접근**: 다양한 유형의 쿼리와 해당 키에 쉽게 접근할 수 있는 메서드를 제공합니다. +- **쿼리 재호출 용이성**: 애플리케이션의 여러 부분에서 쿼리 키를 변경할 필요 없이 쉽게 재호출할 수 있습니다. + +## 페이지네이션 +이 섹션에서는 페이지네이션을 사용하여 게시물 엔티티를 가져오는 API 요청을 수행하는 `getPosts` 함수의 예를 소개합니다. + +### 1. `getPosts` 함수 생성하기 +getPosts 함수는 `api` 세그먼트의 `get-posts.ts` 파일에 있습니다. + +```tsx title="@/pages/post-feed/api/get-posts.ts" +import { apiClient } from "@/shared/api/base"; + +import { PostWithPaginationDto } from "./dto/post-with-pagination.dto"; +import { PostQuery } from "./query/post.query"; +import { mapPost } from "./mapper/map-post"; +import { PostWithPagination } from "../model/post-with-pagination"; + +const calculatePostPage = (totalCount: number, limit: number) => + Math.floor(totalCount / limit); + +export const getPosts = async ( + page: number, + limit: number, +): Promise => { + const skip = page * limit; + const query: PostQuery = { skip, limit }; + const result = await apiClient.get("/posts", query); + + return { + posts: result.posts.map((post) => mapPost(post)), + limit: result.limit, + skip: result.skip, + total: result.total, + totalPages: calculatePostPage(result.total, limit), + }; +}; +``` + +### 2. 페이지네이션을 위한 쿼리 팩토리 +`postQueries` 쿼리 팩토리는 특정 페이지와 제한에 맞춰 게시물 목록을 요청하는 등 게시물 관련 다양한 쿼리 옵션을 정의합니다. + +```tsx +import { keepPreviousData, queryOptions } from "@tanstack/react-query"; +import { getPosts } from "./get-posts"; + +export const postQueries = { + all: () => ["posts"], + lists: () => [...postQueries.all(), "list"], + list: (page: number, limit: number) => + queryOptions({ + queryKey: [...postQueries.lists(), page, limit], + queryFn: () => getPosts(page, limit), + placeholderData: keepPreviousData, + }), +}; +``` + + +### 3. 애플리케이션 코드에서의 사용 + +```tsx title="@/pages/home/ui/index.tsx" +export const HomePage = () => { + const itemsOnScreen = DEFAULT_ITEMS_ON_SCREEN; + const [page, setPage] = usePageParam(DEFAULT_PAGE); + const { data, isFetching, isLoading } = useQuery( + postApi.postQueries.list(page, itemsOnScreen), + ); + return ( + <> + setPage(page)} + page={page} + count={data?.totalPages} + variant="outlined" + color="primary" + /> + + + ); +}; +``` +:::note +예시는 단순화된 버전이며, 전체 코드는 [GitHub](https://github.com/ruslan4432013/fsd-react-query-example)에서 확인할 수 있습니다. +::: + +## 쿼리 관리를 위한 `QueryProvider` +이 가이드에서는 `QueryProvider`를 어떻게 구성하는지 살펴봅니다. + +### 1. `QueryProvider` 생성하기 +`query-provider.tsx` 파일은 `@/app/providers/query-provider.tsx` 경로에 있습니다. + +```tsx title="@/app/providers/query-provider.tsx" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { ReactNode } from "react"; + +type Props = { + children: ReactNode; + client: QueryClient; +}; + +export const QueryProvider = ({ client, children }: Props) => { + return ( + + {children} + + + ); +}; +``` + +### 2. `QueryClient` 생성하기 +`QueryClient`는 API 요청을 관리하는 인스턴스입니다. `query-client.ts` 파일은 `@/shared/api/query-client.ts`에 속해 있으며, 쿼리 캐싱을 위해 특정 설정으로 `QueryClient`를 생성합니다. + +```tsx title="@/shared/api/query-client.ts" +import { QueryClient } from "@tanstack/react-query"; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, + gcTime: 5 * 60 * 1000, + }, + }, +}); +``` + +## 코드 생성 + +API 코드를 생성해주는 도구들이 있지만, 이러한 방식은 위의 예제 처럼 직접 코드를 작성하는 방법보다 유연성이 부족할 수 있습니다. 그러나 Swagger 파일이 잘 구성되어 있고 이러한 자동 생성 도구를 사용하는 경우, 생성된 코드를 `@/shared/api` 디렉토리에 두어 관리하는 것이 효율적일 수 있습니다. + + +## React Query를 조직화하기 위한 추가 조언 +### API 클라이언트 + +공유 레이어에서 커스텀 API 클라이언트 클래스를 사용하면, 프로젝트 내 API 작업을 일관성 있게 관리할 수 있습니다. 이를 통해 로깅, 헤더 설정, 데이터 전송 형식(JSON 또는 XML 등)을 한 곳에서 관리할 수 있게 됩니다. 또한 이 접근 방식은 API와의 상호작용에 대한 변경 사항을 쉽게 반영할 수 있게 하여, 프로젝트의 유지보수성과 개발 편의성을 크게 향상시킵니다. + + +```tsx title="@/shared/api/api-client.ts" +import { API_URL } from "@/shared/config"; + +export class ApiClient { + private baseUrl: string; + + constructor(url: string) { + this.baseUrl = url; + } + + async handleResponse(response: Response): Promise { + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + try { + return await response.json(); + } catch (error) { + throw new Error("Error parsing JSON response"); + } + } + + public async get( + endpoint: string, + queryParams?: Record, + ): Promise { + const url = new URL(endpoint, this.baseUrl); + + if (queryParams) { + Object.entries(queryParams).forEach(([key, value]) => { + url.searchParams.append(key, value.toString()); + }); + } + const response = await fetch(url.toString(), { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + return this.handleResponse(response); + } + + public async post>( + endpoint: string, + body: TData, + ): Promise { + const response = await fetch(`${this.baseUrl}${endpoint}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + return this.handleResponse(response); + } +} + +export const apiClient = new ApiClient(API_URL); +``` + +## 참고 자료 {#see-also} + +- [(GitHub) 샘플 프로젝트](https://github.com/ruslan4432013/fsd-react-query-example) +- [(CodeSandbox) 샘플 프로젝트](https://codesandbox.io/p/github/ruslan4432013/fsd-react-query-example/main) +- [쿼리 팩토리에 대하여](https://tkdodo.eu/blog/the-query-options-api) + +[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx new file mode 100644 index 0000000000..ec1aea8498 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx @@ -0,0 +1,95 @@ +# SvelteKit와 함께 사용하기 + +SvelteKit에서 FSD(Feature-Sliced Design) 아키텍처를 구현할 수 있지만, 몇 가지 구조적 차이점이 있습니다: + +- SvelteKit은 routing 파일을 `src/routes`에 배치하지만, FSD에서는 app Layer에 포함됩니다 +- SvelteKit은 routing 외 파일을 src/lib에 배치하도록 권장합니다 + +## 구성 설정 + +```ts title="svelte.config.ts" +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config}*/ +const config = { + preprocess: [vitePreprocess()], + kit: { + adapter: adapter(), + files: { + routes: 'src/app/routes', // routing을 app Layer로 이동 + lib: 'src', + appTemplate: 'src/app/index.html', // application entry point를 app Layer로 이동 + assets: 'public' + }, + alias: { + '@/*': 'src/*' // src directory alias 설정 + } + } +}; + +export default config; +``` + +## File Routing을 `src/app`으로 이동 + +Configuration 변경 후 구조: + +```sh +├── src +│ ├── app +│ │ ├── index.html +│ │ ├── routes +│ ├── pages # FSD pages Layer +``` + +이제 route 파일을 `app` Layer의 routes 폴더에 배치하고, `pages` Layer와 연결할 수 있습니다. + +예시) Home Page 추가 과정: + +1. `pages` Layer에 새 Page Slice 추가 +2. `app` Layer의 `routes`에 route 생성 +3. Slice의 Page를 route와 연결 + +[CLI tool](https://github.com/feature-sliced/cli)을 사용한 Page Slice 생성: + +```shell +fsd pages home +``` + +`home-page.vue` 파일을 UI Segment에 생성하고 public API로 노출: + + +```ts title="src/pages/home/index.ts" +export { default as HomePage } from './ui/home-page'; +``` + +`app` Layer의 routes에 route 추가: + +```sh +├── src +│ ├── app +│ │ ├── routes +│ │ │ ├── +page.svelte +│ │ ├── index.html +│ ├── pages +│ │ ├── home +│ │ │ ├── ui +│ │ │ │ ├── home-page.svelte +│ │ │ ├── index.ts +``` + +Page Component 연결: + +```html title="src/app/routes/+page.svelte" + + + +``` + +## 참고 자료 + +- [SvelteKit Directory Structure 문서](https://kit.svelte.dev/docs/configuration#files) + diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/intro.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/intro.mdx new file mode 100644 index 0000000000..43ab1397e3 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/intro.mdx @@ -0,0 +1,70 @@ +--- +sidebar_position: 1 +slug: / +pagination_next: get-started/index +--- + +# 문서 + +![feature-sliced-banner](/img/banner.jpg) + +**Feature-Sliced Design**(FSD)는 프론트엔드 애플리케이션을 구조화하기 위한 아키텍처 방법론입니다. 간단히 말해, 코드를 조직하는 규칙과 컨벤션 모음입니다. 이 방법론의 주요 목적은 끊임없이 변화하는 비즈니스 요구사항 속에서도 프로젝트를 더 이해하기 쉽고 체계적으로 만드는 것입니다. + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { RocketOutlined, ThunderboltOutlined, FundViewOutlined } from "@ant-design/icons"; +import Link from "@docusaurus/Link"; + + + + + +
+ + + + + + + diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/reference/public-api.md b/i18n/kr/docusaurus-plugin-content-docs/current/reference/public-api.md new file mode 100644 index 0000000000..c7fb604031 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/reference/public-api.md @@ -0,0 +1,166 @@ +--- +sidebar_position: 3 +--- + +import useBaseUrl from "@docusaurus/useBaseUrl"; + +# Public API + +Public API는 Slice와 같은 모듈 그룹과 이를 사용하는 코드 사이의 Contract 역할을 합니다. 또한 Gate 역할을 하여 특정 Object에 접근할 수 있는 유일한 경로를 제공합니다. + +일반적으로 Public API는 Re-export가 포함된 Index File로 구현됩니다: + +```js title="pages/auth/index.js" +export { LoginPage } from "./ui/LoginPage"; +export { RegisterPage } from "./ui/RegisterPage"; +``` + +## 좋은 Public API의 조건 + +좋은 Public API는 Slice 사용과 통합을 용이하게 합니다. 세 가지 주요 목표: + +1. Application은 Slice 내부 구조의 Refactoring에 영향받지 않아야 함 +2. Slice 동작의 중요한 변경은 Public API 변경으로 이어져야 함 +3. Slice의 필요한 부분만 외부에 노출되어야 함 + +마지막 목표는 실용적 고려사항을 포함합니다. 초기 개발 시 모든 Export를 자동으로 노출하고 싶은 유혹이 있어 Wildcard(*) Re-export를 사용하려 할 수 있습니다: + +```js title="Bad practice, features/comments/index.js" +// ❌ 잘못된 예시 +export * from "./ui/Comment"; // 👎 사용하지 마세요 +export * from "./model/comments"; // 💩 나쁜 관행 +``` + +이는 Slice Interface를 모호하게 만들어 발견성과 이해도를 낮춥니다. Interface가 불명확하면 Application 통합을 위해 코드를 깊이 분석해야 합니다. + +또한 모듈 내부 구현이 의도치 않게 노출될 수 있어, 외부 코드가 이에 의존하게 되면 Refactoring이 어려워집니다. + +## Cross-Import를 위한 Public API {#public-api-for-cross-imports} + +Cross-import는 같은 Layer의 한 Slice가 다른 Slice를 Import하는 것입니다. [Layer Import Rule][import-rule-on-layers]으로 금지되지만, 때로는 필요한 경우가 있습니다. + +예를 들어, Business Entity들은 실제로 서로 참조하는 경우가 많습니다. 이런 관계를 우회하기보다 코드에 자연스럽게 반영하는 것이 더 적절할 수 있습니다. + +이를 위해 `@x-` 표기법의 특별한 Public API를 사용할 수 있습니다. Entity A와 B가 있고 B가 A의 일부를 Import해야 한다면, A는 B를 위한 전용 Public API를 선언할 수 있습니다: + +- `📂 entities` + - `📂 A` + - `📂 @x` + - `📄 B.ts` — `entities/B/` 전용 Public API + - `📄 index.ts` — 일반 Public API + +이제 `entities/B/` 코드는 `entities/A/@x/B`에서 필요한 부분을 Import할 수 있습니다: + +```ts +import type { EntityA } from "entities/A/@x/B"; +``` + +`A/@x/B`는 'A와 B의 교차'를 의미합니다. + +:::note + +Cross-import는 최소화해야 하며, **이 표기법은 Entity Layer에서만 사용**하세요. Cross-import 제거가 비효율적이거나 비현실적일 수 있기 때문입니다. + +::: + +## Index File의 문제점 + +Index File(Barrel File)은 Public API 정의의 일반적 방법이지만, 특정 Bundler나 Framework에서 문제를 일으킬 수 있습니다. + +### Circular Import + +Circular Import는 파일들이 서로를 순환적으로 Import하는 경우입니다. + + + +
+ 세 파일이 서로 원형으로 import하는 모습 + 세 파일이 서로를 원형으로 import하고 있는 예시입니다. +
+ 위 그림: `fileA.js`, `fileB.js`, `fileC.js` 파일의 Circular Import 예시 +
+
+ +이는 Bundler가 처리하기 어렵고 Runtime Error의 원인이 될 수 있습니다. + +Index File 사용 시 Circular Import가 발생하기 쉽습니다. 특히 Slice의 Public API에서 여러 Object를 노출할 때 자주 발생합니다. + +예시) `HomePage`와 `loadUserStatistics`가 Public API로 노출되고 `HomePage`가 `loadUserStatistics`에 접근해야 할 때: + +```jsx title="pages/home/ui/HomePage.jsx" +import { loadUserStatistics } from "../"; // pages/home/index.js에서 import + +export function HomePage() { /* … */ } +``` + +```js title="pages/home/index.js" +export { HomePage } from "./ui/HomePage"; +export { loadUserStatistics } from "./api/loadUserStatistics"; +``` + +이는 Circular Import를 생성합니다: `index.js`가 `ui/HomePage.jsx`를 Import하고, `ui/HomePage.jsx`가 다시 `index.js`를 Import합니다. + +해결을 위한 두 가지 원칙: +- 같은 Slice 내: 항상 Relative Path Import 사용, 전체 경로 명시 +- 다른 Slice Import: 항상 Alias 등의 Absolute Import 사용 + +### Shared의 Large Bundle과 Tree-shaking 문제 {#large-bundles} + +일부 Bundler는 모든 것을 Re-export하는 Index File이 있을 때 Tree-shaking(미사용 코드 제거)을 제대로 수행하지 못할 수 있습니다. + +일반적으로 Public API에서는 큰 문제가 되지 않습니다. Module 내용이 밀접하게 연관되어 있어 하나를 Import하면 다른 것들도 필요한 경우가 많기 때문입니다. 하지만 FSD의 Public API Rule은 `shared/ui`와 `shared/lib`에서 문제가 될 수 있습니다. + +이 두 폴더는 보통 연관성이 적은 Component들의 집합입니다. 예를 들어, `shared/ui`는 UI Library의 모든 Component를 포함할 수 있습니다: + + +- `📂 shared/ui/` + - `📁 button` + - `📁 text-field` + - `📁 carousel` + - `📁 accordion` + +Syntax Highlighter나 Drag-and-Drop Library 같은 Heavy Dependency가 있을 때 문제가 더 심각해집니다. `shared/ui`에서 Button 같은 간단한 Component를 사용하는 모든 Page에 이런 Heavy Dependency가 포함되는 것은 피해야 합니다. + +`shared/ui`나 `shared/lib`의 단일 Public API로 인해 Bundle Size가 커진다면, 각 Component나 Library에 대해 별도의 Index File을 만드는 것이 좋습니다: + +- `📂 shared/ui/` + - `📂 button` + - `📄 index.js` + - `📂 text-field` + - `📄 index.js` + +이렇게 하면 다음과 같이 직접 Import가 가능합니다: + +```js title="pages/sign-in/ui/SignInPage.jsx" +import { Button } from '@/shared/ui/button'; +import { TextField } from '@/shared/ui/text-field'; +``` + +### Public API 우회 방지의 한계 + +Slice에 Index File을 추가해도 직접 Import를 막을 수는 없습니다. 특히 IDE의 Auto Import 기능에서 문제가 됩니다. Import 가능한 여러 경로 중 IDE가 직접 Import를 선택하여 Slice의 Public API Rule을 위반할 수 있습니다. + +이 문제를 자동으로 감지하고 방지하려면 FSD용 Architecture Linter인 [Steiger][ext-steiger]를 사용하세요. + +### Large Project에서의 Bundler 성능 문제 + +TkDodo의 ["Please Stop Using Barrel Files"][ext-please-stop-using-barrel-files] 글처럼, 많은 Index File은 Development Server 속도를 저하시킬 수 있습니다. + +해결 방안: +1. ["Shared의 Large Bundle 문제"](#large-bundles) 조언을 따르세요. `shared/ui`와 `shared/lib`에 하나의 큰 Index File 대신 각 Component/Library별 Index File을 사용하세요. +2. Slice Layer의 Segment에서 Index File 생성을 피하세요. + 예) "comments" Feature의 `📄 features/comments/index.js`가 있다면, `📄 features/comments/ui/index.js` 같은 추가 Index File은 불필요 + +3. 대규모 프로젝트는 여러 큰 Chunk로 분할을 고려하세요. + 예) Google Docs처럼 Document Editor와 File Browser를 분리. Monorepo로 각 Package가 독립적 Layer 구조를 가진 FSD Root가 되도록 구성: + - 일부 Package는 Shared와 Entity Layer만 포함 + - 다른 Package는 Page와 App Layer만 포함 + - 또 다른 Package는 자체 작은 Shared와 다른 Package의 큰 Shared 활용 가능 + + + + + +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-please-stop-using-barrel-files]: https://tkdodo.eu/blog/please-stop-using-barrel-files diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx new file mode 100644 index 0000000000..aad6af4814 --- /dev/null +++ b/i18n/kr/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx @@ -0,0 +1,77 @@ +--- +title: Slice와 Segment +sidebar_position: 2 +pagination_next: reference/public-api +--- + +import useBaseUrl from "@docusaurus/useBaseUrl"; + +# Slice와 Segment + +## Slice + +Slice는 Feature-Sliced Design의 두 번째 구성 단위입니다. Slice의 주 목적은 제품, 비즈니스, Application 관점에서 코드를 체계적으로 그룹화하는 것입니다. + +Slice 이름은 표준화되어 있지 않고 Application의 비즈니스 도메인에 따라 결정됩니다. 예시: +- 사진 갤러리: `photo`, `effects`, `gallery-page` +- 소셜 네트워크: `post`, `comments`, `news-feed` + +Shared와 App Layer는 Slice를 포함하지 않습니다. Shared는 Business Logic을 포함하지 않고, App은 Application 전체 관련 코드만 다루기 때문입니다. + +### Zero 결합도과 High 응집도 {#zero-coupling-high-cohesion} + +Slice는 독립적이고 응집도 높은 코드 그룹이어야 합니다. 아래 그래픽은 _응집도_과 _결합도_ 개념을 시각화합니다: + +
+ + +
+ Image inspired by https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/ +
+
+ +이상적인 Slice 특징: + 1. 독립적 — 같은 Layer의 다른 Slice와 Zero 결합도 + 2. 높은 응집도 — 핵심 목적 관련 코드를 포함 + +Slice의 독립성은 [Layer Import Rule][layers--import-rule]로 강제됩니다: + +> _Slice의 모듈은 하위 Layer의 다른 Slice만 Import 가능_ + +### Slice의 Public API Rule + +Slice 내부 구조는 자유롭지만, 다른 Slice가 사용할 좋은 Public API를 제공해야 합니다. 이는 **Slice Public API Rule**로 강제됩니다: + +> _모든 Slice(와 Slice가 없는 Layer의 Segment)는 Public API를 정의해야 합니다._ +> +> _외부 모듈은 Slice/Segment의 내부 구조가 아닌 Public API만 참조 가능_ + +자세한 내용은 [Public API Reference][ref-public-api]를 참고하세요. + +### Slice Group + +연관된 Slice들은 폴더로 그룹화할 수 있습니다. 단, 다른 Slice와 동일한 격리 규칙을 따라야 하며, 그룹 내 **코드 공유는 불가능**합니다. + +![Features "compose", "like" 그리고 "delete"가 "post" 폴더에 그룹화되어 있습니다. 해당 폴더에는 허용되지 않음을 나타내기 위해 취소선이 그어진 "some-shared-code.ts" 파일도 있습니다.](/img/graphic-nested-slices.svg) + +## Segment + +Segment는 마지막 구성 단위로, 기술적 특성에 따라 코드를 그룹화합니다. + +표준 Segment: + +- `ui` — UI 관련: Component, Date Formatter, Style 등 +- `api` — Backend 통신: Request Function, Data Type, Mapper 등 +- `model` — Data Model: Schema, Interface, Store, Business Logic +- `lib` — Slice 내부 Library 코드 +- `config` — Configuration과 Feature Flag + +각 Layer의 Segment 사용법은 [Layer Page][layers--layer-definitions]를 참조하세요. + +Custom Segment도 생성 가능하며, App과 Shared Layer에서 주로 사용됩니다. + +Segment 이름은 코드 목적을 명확히 해야 합니다. `components`, `hooks`, `types`같은 모호한 이름은 피하세요. + +[layers--layer-definitions]: /docs/reference/layers#layer-definitions +[layers--import-rule]: /docs/reference/layers#import-rule-on-layers +[ref-public-api]: /docs/reference/public-api diff --git a/i18n/ru/code.json b/i18n/ru/code.json index 838135aafa..44ec1891f6 100644 --- a/i18n/ru/code.json +++ b/i18n/ru/code.json @@ -127,18 +127,6 @@ "message": "Но для совместимости есть редиректы со старых ссылок", "description": "NavPage section=legacy subdetails" }, - "features.cookie-consent.alert": { - "message": "🍰 Мы используем куки для аналитики", - "description": "Cookie Consent alert" - }, - "features.cookie-consent.accept": { - "message": "Хорошо", - "description": "Cookie Consent accept label" - }, - "features.cookie-consent.reason": { - "message": "зачем?", - "description": "Cookie Consent reason label" - }, "features.feedback-badge.label": { "message": "Поделись фидбеком по документации 🤙", "description": "Feedback share button label" @@ -159,6 +147,62 @@ "message": "Ваш фидбек помогает нам улучшать документацию", "description": "DocFeedback block=Subtitle" }, + "features.feedback-doc.button-text": { + "message": "Оставить отзыв", + "description": "The text on a floating button to leave feedback about the docs" + }, + "features.feedback-doc.email-placeholder": { + "message": "Оставьте ваш e-mail (по желанию)", + "description": "The placeholder for email input" + }, + "features.feedback-doc.error-message": { + "message": "Пожалуйста, попробуйте позже.", + "description": "The error message displayed when feedback form submission fails" + }, + "features.feedback-doc.modal-title-error-403": { + "message": "URL запроса не совпадает с URL, указанным в PushFeedback для этого проекта.", + "description": "The title of the modal displayed when the feedback form submission fails with 403 error" + }, + "features.feedback-doc.modal-title-error-404": { + "message": "Мы не смогли найти предоставленный идентификатор проекта в PushFeedback.", + "description": "The title of the modal displayed when the feedback form submission fails with 404 error" + }, + "features.feedback-doc.message-placeholder": { + "message": "Напишите ваш отзыв здесь…", + "description": "The placeholder for message input" + }, + "features.feedback-doc.modal-title": { + "message": "Поделитесь вашим мнением", + "description": "The title of the modal displayed when the feedback form is opened" + }, + "features.feedback-doc.modal-title-error": { + "message": "Упс!", + "description": "The title of the modal displayed when the feedback form submission fails" + }, + "features.feedback-doc.modal-title-success": { + "message": "Спасибо за ваш отзыв!", + "description": "The title of the modal displayed when the feedback form submission is successful" + }, + "features.feedback-doc.rating-placeholder": { + "message": "Была ли эта страница полезной?", + "description": "The placeholder for rating input" + }, + "features.feedback-doc.rating-stars-placeholder": { + "message": "Как бы вы оценили эту страницу", + "description": "The placeholder for rating stars input" + }, + "features.feedback-doc.screenshot-button-text": { + "message": "Сделать скриншот", + "description": "The text on a button to take a screenshot" + }, + "features.feedback-doc.screenshot-topbar-text": { + "message": "Выберите элемент на странице", + "description": "The text displayed in the top bar of the screenshot tool" + }, + "features.feedback-doc.send-button-text": { + "message": "Отправить", + "description": "The text on a button to send feedback" + }, "features.hero.tagline": { "message": "Архитектурная методология для фронтенд проектов", "description": "Architectural methodology for frontend projects" diff --git a/i18n/ru/docusaurus-plugin-content-docs/current.json b/i18n/ru/docusaurus-plugin-content-docs/current.json index 6487d3a7d4..0ecd0d7164 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current.json +++ b/i18n/ru/docusaurus-plugin-content-docs/current.json @@ -1,6 +1,6 @@ { "version.label": { - "message": "v2.0.0 🍰", + "message": "v2.1", "description": "The label for version current" }, "sidebar.getstartedSidebar.category.🚀 Get Started": { diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/about/alternatives.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/about/alternatives.mdx index bc8c88d4f7..018bc847b4 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/about/alternatives.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/about/alternatives.mdx @@ -92,11 +92,51 @@ Framework-agnostic, conventional-подход ## Atomic Design - - -> О подходе; О применимости в фронтенде; позиция FSD - -Про совместимость, применимость в методологии и скоуп ответственности, сопоставление слоев +### Что это? + +В Atomic Design сфера ответственности разделена на стандартизированные слои.  + +Atomic Design разбивается на **5 слоев** (сверху вниз): + +1. `pages` - Назначение аналогично слою `pages` в FSD. +2. `templates` - Компоненты задающие структуру страницы, без привязки к контенту. +3. `organisms` - Модули состоящие из молекул, обладающие бизнес логикой. +4. `moleculs` - Более сложные компоненты, в которых, как правило, нет бизнес логики. +5. `atoms` - UI компоненты без бизнес логики. + +Модули на одном слое взаимодействуют только с модулями, находящимися на слоях ниже, как в FSD. +То есть, молекулы строятся из атомов, организмы из молекул, шаблоны из организмов, страницы из шаблонов. +Также Atomic Design подразумевает использование **Public API** внутри модулей для изоляции. + +### Применимость во фронтенде +Atomic Design относительно часто встречается в проектах. Atomic Design популярнее среди веб-дизайнеров,  +нежели в разработке. Веб-дизайнеры часто используют Atomic Design для создания масштабируемых и легко поддерживаемых дизайнов.  +В разработке Atomic Design часто смешивается с другими архитектурными методологиями. + +Однако, так как Atomic Design концентрирует внимание на UI компонентах и их композицию, возникает проблема реализации +бизнес логики в рамках архитектуры. + +Проблема заключается в том, что Atomic Design не предусматривает четкого уровня ответственности для бизнес-логики,  +что приводит к распределению по различным компонентам и уровням, усложняя поддержку и тестирование.  +Бизнес-логика становится размыта, что затрудняет четкое разделение ответственности и делает код менее +модульным и переиспользуемым. + +### Как оно сочетается с FSD? +В контексте FSD некоторые элементы Atomic Design могут быть применены для  +создания гибких и масштабируемых UI компонентов. Слои `atoms` и `molecules` можно реализовать в  +`shared/ui` в FSD, что упрощает переиспользование и поддержку базовых UI элементов.  + +```sh +├── shared +│   ├── ui  +│   │   ├── atoms +│   │   ├── molecules +│   ... +``` + +Сравнение FSD и Atomic Design показывает, что обе методологии стремятся к модульности и переиспользованию,  +но акцентируют внимание на разных аспектах. Atomic Design ориентирован на визуальные компоненты  +и их композицию. FSD фокусируется на разбиении функциональности приложения на независимые модули и их взаимосвязи. - [Методология Atomic Design](https://atomicdesign.bradfrost.com/table-of-contents/) - [(Тред) Про применимость в shared/ui](https://t.me/feature_sliced/1653) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/about/mission.md b/i18n/ru/docusaurus-plugin-content-docs/current/about/mission.md index 2375dbb9cc..239786bf2d 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/about/mission.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/about/mission.md @@ -4,7 +4,7 @@ sidebar_position: 1 # Миссия -Здесь описаны цели и ограничения применимости методологии - которыми мы руководствуемся при разработке методологии +Здесь описаны цели и ограничения применимости методологии, которыми мы руководствуемся при разработке методологии - Мы видим нашу цель, как баланс между идеологией и простотой - Мы не сможем сделать серебряную пулю, которая подходит всем @@ -26,7 +26,7 @@ sidebar_position: 1 **А также - приложить ко всему этому инструментарий (cli, линтеры)** Чтобы разработчики могли использовать *выверенный опытом* подход, позволяющий обходить давние проблемы архитектуры и разработки -> *@sergeysova: Представьте - разработчик пишет код в рамках методологии и у него проблемы возникают раз в 10 реже, просто потому, что другие люди продумали решение многих проблем.* +> *@sergeysova: Представьте: разработчик пишет код в рамках методологии, и у него проблемы возникают раз в 10 реже, просто потому что другие люди продумали решение многих проблем.* ## Ограничения {#limitations} diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/about/motivation.md b/i18n/ru/docusaurus-plugin-content-docs/current/about/motivation.md index 3c9c3231ad..dc5e32a367 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/about/motivation.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/about/motivation.md @@ -24,7 +24,7 @@ sidebar_position: 2 > - *"Все проблемы проекта решаются хорошей документацией, тестами и выстроенными процессами"* > - *"Проблем бы и не было - если бы все разработчики следовали всему выше перечисленному"* > - *"Все придумано уже до вас, вы просто не можете этим пользоваться"* -> - *"Возьмите {FRAMEWORK_NAME} - там решено уже все за вас"* +> - *"Возьмите \{FRAMEWORK_NAME\} - там решено уже все за вас"* ### Одних принципов недостаточно {#principles-alone-are-not-enough} diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/about/understanding/naming.md b/i18n/ru/docusaurus-plugin-content-docs/current/about/understanding/naming.md index 884636f009..ebfca8eb46 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/about/understanding/naming.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/about/understanding/naming.md @@ -4,7 +4,7 @@ sidebar_position: 4 # Нейминг -У разных разработчиков разный опыт и контекст, что может привести к недопониманию в команде, когда одни и те же сущности называются по-разному. Например: +У разных разработчиков различный опыт и контекст, что может привести к недопониманию в команде, когда одни и те же сущности называются по-разному. Например: - Компоненты для отображения могут называться "ui", "components", "ui-kit", "views", … - Код, который повторно используется во всем приложении, может называться "core", "shared", "app", … @@ -21,7 +21,7 @@ sidebar_position: 4 ## Конфликты нейминга {#when-can-naming-interfere} -Конфликты нейминга могут возникать, когда термины, используемые в методологии FSD, пересекаются с терминами, используемыми в бизнесе: +Конфликты нейминга могут возникать, когда термины, которые используются в методологии FSD, пересекаются с терминами, используемыми в бизнесе: - `FSD#process` vs моделируемый процесс в приложении, - `FSD#page` vs страница журнала, @@ -31,9 +31,9 @@ sidebar_position: 4 Когда глоссарий проекта содержит терминологию, характерную для FSD, крайне важно проявлять осторожность при обсуждении этих терминов с командой и техническими незаинтересованными сторонами. -Чтобы эффективно общаться с командой, рекомендуется использовать аббревиатуру "FSD" для префиксации терминов методологии. Например, когда речь идет о процессе, можно сказать: "Мы можем поместить этот процесс на на слой FSD features". +Чтобы эффективно общаться с командой, рекомендуется использовать аббревиатуру "FSD" для префиксации терминов методологии. Например, когда речь идет о процессе, можно сказать: "Мы можем поместить этот процесс на слой FSD features". -И наоборот, при общении с нетехническими заинтересованными сторонами лучше ограничить использование терминологии FSD и воздержаться от упоминания внутренней структуры кодовой базы. +И наоборот, при общении с нетехническими заинтересованными сторонами лучше ограничивать использование терминологии FSD, а также воздержаться от упоминания внутренней структуры кодовой базы. ## См. также {#see-also} diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/branding.md b/i18n/ru/docusaurus-plugin-content-docs/current/branding.md index badd5b43a8..274648dbf9 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/branding.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/branding.md @@ -1,3 +1,5 @@ +import useBaseUrl from "@docusaurus/useBaseUrl"; + # Рекомендации по брендингу Визуальная айдентика FSD основана на его ключевых концепциях: `Layered`, `Sliced self-contained parts`, `Parts & Compose`, `Segmented`. @@ -41,30 +43,30 @@ FSD имеет несколько вариаций логотипа для ра primary
(#29BEDC, #517AED) - logo-primary + logo-primary Предпочтительно в большинстве случаев flat
(#3193FF) - logo-flat + logo-flat Для одноцветного контекста monochrome
(#FFF) - logo-monocrhome + logo-monocrhome Для черно-белого контекста square
(#3193FF) - logo-square + logo-square Для квадратных размеров ## Баннеры & Схемы {#banners--schemes} -banner-primary -banner-monochrome +banner-primary +banner-monochrome ## Social Preview @@ -78,4 +80,3 @@ FSD имеет несколько вариаций логотипа для ра - [Обсуждение (github)](https://github.com/feature-sliced/documentation/discussions/399) - [История ребрендинга с референсами (figma)](https://www.figma.com/file/RPphccpoeasVB0lMpZwPVR/FSD-Brand?node-id=0%3A1) -- [Демо ребрендинга](https://rebrand-sliced.netlify.app/en/) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/cheatsheet.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/cheatsheet.mdx index 2281454f01..73f21947b1 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/cheatsheet.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/cheatsheet.mdx @@ -1,5 +1,6 @@ --- -sidebar_position: 3 +# sidebar_position: 3 +unlisted: true --- # Памятка по декомпозиции diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/faq.md b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/faq.md index 3a8e25ae44..1a51f0c1d8 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/faq.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/faq.md @@ -13,7 +13,7 @@ pagination_next: guides/index ### Существует ли тулкит или линтер? {#is-there-a-toolkit-or-a-linter} -Есть официальный конфиг для ESLint — [@feature-sliced/eslint-config][eslint-config-official], и плагин для ESLint — [@conarti/eslint-plugin-feature-sliced][eslint-plugin-conarti], созданный участником сообщества Александром Белоусом. Мы будем рады вашим вкладам в эти проекты или созданию своих! +Да! У нас есть линтер [Steiger][ext-steiger] для проверки архитектуры вашего проекта и [генераторы папок][ext-tools] через CLI или IDE. ### Где хранить layout/template страниц? {#where-to-store-the-layouttemplate-of-pages} @@ -42,7 +42,7 @@ _Entity_ — это понятие из реальной жизни, с кото ### Есть ли какие-нибудь полезные ресурсы/статьи/т.д. по FSD? {#are-there-any-useful-resourcesarticlesetc-about-fsd} -Да! +Да! https://github.com/feature-sliced/awesome ### Зачем мне нужен Feature-Sliced Design? {#why-do-i-need-feature-sliced-design} @@ -58,10 +58,10 @@ _Entity_ — это понятие из реальной жизни, с кото Ответили [здесь](/docs/guides/examples/auth) +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools [import-rule-layers]: /docs/reference/layers#import-rule-on-layers [reference-entities]: /docs/reference/layers#entities -[eslint-config-official]: https://github.com/feature-sliced/eslint-config -[eslint-plugin-conarti]: https://github.com/conarti/eslint-plugin-feature-sliced [motivation]: /docs/about/motivation [telegram]: https://t.me/feature_sliced [discord]: https://discord.gg/S8MzWTUsmp diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/index.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/index.mdx index 81984b74d6..31f9cbe5f0 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/index.mdx @@ -3,19 +3,15 @@ hide_table_of_contents: true pagination_prev: intro --- -# 🚀 Быстрый старт +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { RocketOutlined, PlaySquareOutlined, QuestionCircleOutlined } from "@ant-design/icons"; -LEARNING-ORIENTED +# 🚀 Быстрый старт

Добро пожаловать! Этот раздел помогает бегло познакомиться с применением Feature-Sliced Design и основами методологии. Также вы поймете ключевые преимущества методологии и причины ее создания.

-## Главное {#main} - -import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" -import { RocketOutlined, BuildOutlined, PlaySquareOutlined } from "@ant-design/icons"; - +{/* +/> */} diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/overview.md b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/overview.md deleted file mode 100644 index 858ca42d61..0000000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/overview.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Обзор - -## Подходит ли это мне? {#is-it-right-for-me} - -FSD подходит для проектов и команд любого размера с некоторыми оговорками: - -- Эта методология исключительно для фронтенда. Если вы ищете архитектуру для бэкенда, обратите внимание на [Clean Architecture][refs-clean-architecture]. -- Эта методология исключительно для приложений с пользовательским интерфейсом. В качестве вдохновения для архитектуры крупной библиотеки компонентов рекомендуется [Material UI][ext-material-ui]. -- Если вы разрабатываете очень простое приложение из одной странички на FSD, преимущества методологии вряд ли понадобятся, а вот разработка может замедлиться. Однако, FSD помогает стандартизированно мыслить о фронтенд-приложениях, так что смело используйте даже на маленьких проектах, если знаете, для чего она вам. -- Огромное приложение, соизмеримое с админ-панелью Google Cloud, потребует специализированной архитектуры. FSD в данном случае может выступать в качестве отправной точки. - -Методология не привязана к конкретному языку программирования, UI-фреймворку или менеджеру состояния — подойдет любой (см. [примеры использования][refs-examples]). - -Если у вас уже есть проект, не переживайте — FSD можно внедрять постепенно. Главный вопрос, который стоит задать команде: "**Есть ли боль** при разработке проекта?" Если боли нет, возможно, переход делать не стоит. Руководство по миграции см. в разделе [Миграция][refs-migration]. - - -## Основы {#basics} - -Проект на FSD состоит из слоев (layers), каждый слой состоит из слайсов (slices) и каждый слайс состоит из сегментов (segments). - -![themed--scheme](/img/visual_schema.jpg) - -**Слои** стандартизированы во всех проектах и расположены вертикально. Модули на одном слое могут взаимодействовать лишь с модулями, находящимися на слоях строго ниже. На данный момент слоев семь (снизу вверх): - -1. `shared` — переиспользуемый код, не имеющий отношения к специфике приложения/бизнеса. - (например, UIKit, libs, API) -2. `entities` (сущности) — бизнес-сущности. - (например, User, Product, Order) -3. `features` (фичи) — взаимодействия с пользователем, действия, которые несут бизнес-ценность для пользователя. - (например, SendComment, AddToCart, UsersSearch) -4. `widgets` (виджеты) — композиционный слой для соединения сущностей и фич в самостоятельные блоки - (например, IssuesList, UserProfile). -5. `pages` (страницы) — композиционный слой для сборки полноценных страниц из сущностей, фич и виджетов. -6. `processes` (процессы, устаревший слой) — сложные сценарии, покрывающие несколько страниц. - (например, авторизация) -7. `app` — настройки, стили и провайдеры для всего приложения. - -Затем есть **слайсы**, разделяющие код по предметной области. Они группируют логически связанные модули, что облегчает навигацию по кодовой базе. Слайсы не могут использовать другие слайсы на том же слое, что обеспечивает высокий уровень [_связности_][refs-wiki-cohesion] (cohesion) при низком уровне [_зацепления_][refs-wiki-coupling] (coupling). - -В свою очередь, каждый слайс состоит из **сегментов**. Это маленькие модули, главная задача которых — разделить код внутри слайса по техническому назначению. Самые распространенные сегменты — `ui`, `model` (store, actions), `api` и `lib` (utils/hooks), но в вашем слайсе может не быть каких-то сегментов, могут быть другие, по вашему усмотрению. - -:::note - -В большинстве случаев [рекомендуется][ext-disc-api] располагать `api` и `config` только в shared-слое - -::: - -## Пример {#example} - -Рассмотрим приложение социальной сети. - -* `app/` содержит настройку роутера, глобальные хранилища и стили. -* `pages/` содержит компоненты роутов на каждую страницу в приложении, преимущественно композирующие, по возможности, без собственной логики. - -В рамках этого приложения рассмотрим карточку поста в ленте новостей. - -* `widgets/` содержит "собранную" карточку поста, с содержимым и интерактивными кнопками, в которые вшиты запросы к бэкенду. -* `features/` содержит всю интерактивность карточки (например, кнопку лайка) и логику обработки этой интерактивности. -* `entities/` содержит скелет карточки со слотами под интерактивные элементы. Компонент, демонстрирующий автора поста, также находится в этой папке, но в другом слайсе. - -### Преимущества {#advantages} - -- **Единообразие** - Код распределяется согласно области влияния (слой), предметной области (слайс) и техническому назначению (сегмент). - Благодаря этому архитектура стандартизируется и становится более простой для ознакомления. - -- **Контролируемое переиспользование логики** - Каждый компонент архитектуры имеет свое назначение и предсказуемый список зависимостей. - Благодаря этому сохраняется баланс между соблюдением принципа **DRY** и возможностью адаптировать модуль под разные цели. - -- **Устойчивость к изменениям и рефакторингу** - Один модуль не может использовать другой модуль, расположенный на том же слое или на слоях выше. - Благодаря этому приложение можно изолированно модифицировать под новые требования без непредвиденных последствий. - -- **Ориентированность на потребности бизнеса и пользователей** - Разбиение приложения по бизнес-доменам помогает глубже понимать, структурировать и находить фичи проекта. - -## Постепенное внедрение {#incremental-adoption} - -Сила FSD в _структурированной_ декомпозиции. В лучшей форме, FSD позволяет найти место для любой части кода почти однозначно. Однако, уровень декомпозиции — это параметр, и любая команда может подстроить его для оптимального баланса между легкостью внедрения и преимуществами. - -Предлагаем следующую стратегию для миграции существующей кодовой базы на FSD, проверенную опытом: - -1. Вырезать слои `app` и `shared`, чтобы иметь опору для последующих этапов. Эти слои получатся тонкими и простыми, пусть такими и остаются. - -2. Вынести весь интерфейс, связанный с бизнесом, распределить по виджетам и страницам, даже если в них пока что будут зависимости, нарушающие правила FSD. - -3. Постепенно наращивать степень декомпозиции, выделяя `features` и `entities`. Превращать страницы и виджеты из перегруженных логикой слоёв в чисто композиционные слои. - -Рекомендуется воздержаться от добавления новых крупных сущностей во время рефакторинга, а также рефакторинга по частям. - -[refs-clean-architecture]: https://medium.com/codex/clean-architecture-for-dummies-df6561d42c94 -[ext-disc-api]: https://github.com/feature-sliced/documentation/discussions/66 -[ext-material-ui]: https://github.com/mui/material-ui -[refs-examples]: /examples -[refs-migration]: /docs/guides/migration -[refs-wiki-cohesion]: https://ru.wikipedia.org/wiki/%D0%A1%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D1%8C_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5) -[refs-wiki-coupling]: https://ru.wikipedia.org/wiki/%D0%97%D0%B0%D1%86%D0%B5%D0%BF%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/overview.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/overview.mdx new file mode 100644 index 0000000000..8b0a174551 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/overview.mdx @@ -0,0 +1,144 @@ +--- +sidebar_position: 1 +--- + +# Обзор + +**Feature-Sliced Design** (FSD) — это архитектурная методология для проектирования фронтенд-приложений. Проще говоря, это набор правил и соглашений по организации кода. Главная цель этой методологии — сделать проект понятнее и стабильнее в условиях постоянно меняющихся бизнес-требований. + +Помимо набора правил, FSD — это также целый инструментарий. У нас есть [линтер][ext-steiger] для проверки архитектуры вашего проекта, [генераторы папок][ext-tools] через CLI или IDE, а также богатая библиотека [примеров][examples]. + +## Подходит ли FSD мне? {#is-it-right-for-me} + +FSD можно внедрять в проектах и командах любого размера. Она подходит для вашего проекта, если: + +- Вы занимаетесь **фронтенд**-разработкой (интерфейсы для сайтов, мобильных/десктопных приложений, и т. д.) +- Вы разрабатываете **приложение**, а не библиотеку + +И это все! Нет никаких ограничений на используемый вами язык программирования, фреймворк или стейт-менеджер. Ещё вы можете внедрять FSD постепенно, использовать его в монорепозиториях, и масштабировать его хоть до луны, разделяя ваше приложение на пакеты и внедряя FSD в каждом из них по отдельности. + +Если у вас уже есть архитектура, и вы подумываете перейти на FSD, убедитесь, что текущая архитектура **создает проблемы** в вашей команде. Например, если ваш проект стал слишком большим и переплетённым, чтоб эффективно разрабатывать новые функции, или если вы ожидаете, что в команду придет много новых участников. Если текущая архитектура работает, возможно, ее не стоит менять. Но если вы всё же решите перейти, ознакомьтесь с рекомендациями в разделе [Миграция][migration]. + +## Базовый пример {#basic-example} + +Вот простой проект, реализующий FSD: + +- `📁 app` +- `📁 pages` +- `📁 shared` + +Эти папки верхнего уровня называются _слоями_. Давайте посмотрим глубже: + +- `📂 app` + - `📁 routes` + - `📁 analytics` +- `📂 pages` + - `📁 home` + - `📂 article-reader` + - `📁 ui` + - `📁 api` + - `📁 settings` +- `📂 shared` + - `📁 ui` + - `📁 api` + +Папки внутри `📂 pages` называются _слайсами_. Они делят слой по домену (в данном случае, по страницам). + +Папки внутри `📂 app`, `📂 shared` и `📂 pages/article-reader` называются _сегментами_, и они делят слайсы (или слои) по техническому назначению, то есть по тому, для чего предназначен код. + +## Понятия {#concepts} + +Слои, слайсы и сегменты образуют иерархию, как показано на схеме: + +
+ ![Иерархия концепций FSD, описанная ниже](/img/visual_schema.jpg) + +
+

На картинке выше: три столбика, обозначенные слева направо как "Слои", "Слайсы" и "Сегменты" соответственно.

+

Столбик "Слои" содержит семь делений, расположенных сверху вниз и обозначенных "app", "processes", "pages", "widgets", "features", "entities" и "shared". Деление "processes" зачеркнуто. Деление "entities" соединено со вторым столбиком "Слайсы", показывая, что второй столбик является содержимым "entities".

+

Столбик "Слайсы" содержит три деления, расположенных сверху вниз и обозначенных "user", "post" и "comment". Деление "post" соединено со столбиком "Сегменты" таким же образом, что и содержимое "post".

+

Столбик "Сегменты" содержит три деления, расположенных сверху вниз и обозначенных "ui", "model" и "api".

+
+
+ +### Слои {#layers} + +Слои стандартизированы во всех проектах FSD. Вам не обязательно использовать все слои, но их названия важны. На данный момент их семь (сверху вниз): + +1. **App** — всё, благодаря чему приложение запускается — роутинг, точки входа, глобальные стили, провайдеры и т. д. +2. **Processes** (процессы, устаревший) — сложные межстраничные сценарии. +3. **Pages** (страницы) — полные страницы или большие части страницы при вложенном роутинге. +4. **Widgets** (виджеты) — большие самодостаточные куски функциональности или интерфейса, обычно реализующие целый пользовательский сценарий. +5. **Features** (фичи) — _повторно используемые_ реализации целых фич продукта, то есть действий, приносящих бизнес-ценность пользователю. +6. **Entities** (сущности) — бизнес-сущности, с которыми работает проект, например `user` или `product`. +7. **Shared** — переиспользуемый код, особенно когда он отделён от специфики проекта/бизнеса, хотя это не обязательно. + +:::warning Важно + +Слои **App** и **Shared**, в отличие от других слоев, не имеют слайсов и состоят из сегментов напрямую. + +Однако для всех остальных слоёв — **Entities**, **Features**, **Widgets** и **Pages**, сохраняется структура, в которой необходимо сначала создать слайс, внутри которого создавать сегменты. + +::: + +Фишка слоев в том, что модули на одном слое могут знать только о модулях со слоев строго ниже, и как следствие, импортировать только с них. + +### Слайсы {#slices} + +Дальше идут слайсы, они делят слой по предметной области. Вы можете называть ваши слайсы как угодно, и создавать их сколько угодно. Слайсы помогают не теряться в проекте, потому что группируют тесно связанный по смыслу код. + +Слайсы не могут использовать другие слайсы на том же слое, и это обеспечивает сильную связанность кода внутри слайса и слабую сцепленность между слайсами. + +### Сегменты {#segments} + +Слайсы, а также слои App и Shared, состоят из сегментов, а сегменты группируют код по его назначению. Имена сегментов не зафиксированы стандартом, но существует несколько общепринятых имен для наиболее распространенных целей: + +- `ui` — всё, что связано с отображением: UI-компоненты, форматтеры дат, стили и т.д. +- `api` — взаимодействие с бэкендом: функции запросов, типы данных, мапперы. +- `model` — модель данных: схемы валидации, интерфейсы, хранилища и бизнес-логика. +- `lib` — библиотечный код, который нужен другим модулям этого слайса. +- `config` — файлы конфигурации и фиче-флаги. + +Обычно этих сегментов достаточно для большинства слоев, поэтому свои собственные сегменты обычно создают только в Shared или App, но это не жёсткое правило. + +## Преимущества {#advantages} + +- **Однородность** + Поскольку структура стандартизирована, проекты становятся более единообразными, что облегчает набор новых участников в команду. + +- **Устойчивость к изменениям и рефакторингу** + Модуль на одном слое не может использовать другие модули на том же слое или слоях выше. + Это позволяет вам вносить изолированные правки без непредвиденных последствий для остальной части приложения. + +- **Контролируемое переиспользование логики** + В зависимости от уровня вы можете сделать код либо очень переиспользуемым, либо очень локальным. + Это сохраняет баланс между соблюдением принципа **DRY** и практичностью. + +- **Ориентация на потребности бизнеса и пользователей** + Приложение разделено на бизнес-домены, и при именовании поощряется использование терминологии бизнеса, чтобы вы могли делать полезную работу в продукте, не вникая полностью во все другие несвязанные части проекта. + +## Постепенное внедрение {#incremental-adoption} + +Если у вас есть существующая кодовая база, которую вы хотите перенести на FSD, мы предлагаем следующую стратегию. На нашем собственном опыте миграции она хорошо себя зарекомендовала. + +1. Начните постепенно формировать слои App и Shared, чтобы создать фундамент. + +2. Раскидайте весь существующий интерфейсный код по виджетам и страницам, даже если у них пока что есть зависимости, нарушающие правила FSD. + +3. Постепенно исправляйте нарушения правил на импорты, а по ходу извлекайте сущности и, возможно, фичи. + +Рекомендуется воздержаться от добавления новых крупных сущностей во время рефакторинга, а также рефакторинга по частям. + +## Следующие шаги {#next-steps} + +- **Хотите разобраться в том, как мыслить по-FSD-шному?** Прочтите [Туториал][tutorial]. +- **Предпочитаете учиться на примерах?** У нас их много в разделе [Примеры][examples]. +- **Есть вопросы?** Загляните в наш [чат Telegram][ext-telegram] и спросите у сообщества. + +[tutorial]: /docs/get-started/tutorial +[examples]: /examples +[migration]: /docs/guides/migration/from-custom +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools +[ext-telegram]: https://t.me/feature_sliced + diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/tutorial.md b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/tutorial.md index 5acb0af353..6638f7d837 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/tutorial.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/tutorial.md @@ -4,824 +4,2257 @@ sidebar_position: 2 # Туториал -Рассмотрим применение **Feature-Sliced Design** на примере TodoApp +## Часть 1. На бумаге -- Сначала разберем *подготовительные аспекты создания приложения* -- А затем - как концепции методологии помогают *гибко и эффективно проектировать бизнес-логику* без лишних затрат +В этом руководстве мы рассмотрим приложение Real World App, также известное как Conduit. Conduit является упрощённым клоном [Medium](https://medium.com/) — он позволяет вам читать и писать статьи в блогах, а также комментировать статьи других людей. -> В конце статьи есть [codesandbox-вставка с финальным решением][ext-sandbox], которое может помочь для уточнения деталей реализации +![Главная страница Conduit](/img/tutorial/realworld-feed-anonymous.jpg) -**Стек**: React, Effector, TypeScript, Sass, AntDesign +Это довольно небольшое приложение, поэтому мы не станем сильно усложнять разработку излишней декомпозицией. Вероятнее всего, что всё приложение поместится в три слоя: **App**, **Pages** и **Shared**. Если нет, будем вводить дополнительные слои по ходу. Готовы? -:::note +### Начните с перечисления страниц -Туториал призван **раскрыть практическую идею самой методологии**. Поэтому описанные здесь практики - во многом подойдут и для других технологических стеков фронтенд-проектов +Если мы посмотрим на скриншот выше, мы можем предположить, что по крайней мере, есть следующие страницы: -::: +- Домашняя (лента статей) +- Войти и зарегистрироваться +- Просмотр статей +- Редактор статей +- Просмотр профилей людей +- Редактор профиля (настройки) -## 1. Подготовительные моменты {#1-preparation} +Каждая из этих страниц станет отдельным *слайсом* на *слое* Pages. Вспомните из обзора, что слайсы — это просто папки внутри слоев, а слои — это просто папки с заранее определенными названиями, например, `pages`. -### 1.1 Инициализируем проект {#11-initializing-the-project} +Таким образом, наша папка Pages будет выглядеть так: -На данный момент имеется множество способов сгенерировать и запустить шаблон проекта +``` +📂 pages/ + 📁 feed/ (лента) + 📁 sign-in/ (войти/зарегистрироваться) + 📁 article-read/ (просмотр статей) + 📁 article-edit/ (редактор статей) + 📁 profile/ (профиль) + 📁 settings/ (настройки) +``` -Не будем акцентироваться сильно на этом шаге, но для быстрой инициализации можно воспользоваться [CRA (для React)](https://create-react-app.dev/docs/getting-started): +Ключевое отличие Feature-Sliced Design от произвольной структуры кода заключается в том, что страницы не могут зависеть друг от друга. То есть одна страница не может импортировать код с другой страницы. Это связано с **правилом импорта для слоёв**: -```cmd -$ npx create-react-app todo-app --template typescript -``` +*Модуль (файл) в слайсе может импортировать другие слайсы только в том случае, если они расположены на слоях строго ниже.* -### 1.2 Подготавливаем структуру {#12-preparing-the-structure} +В этом случае страница является слайсом, поэтому модули (файлы) внутри этой страницы могут импортировать код только из слоев ниже, а не из других страниц. -Получили следующую заготовку под проект +### Пристальный взгляд на ленту -```sh -└── src/ - ├── App.css - ├── App.test.tsx - ├── App.tsx - ├── index.css - ├── index.ts - ├── logo.svg - ├── react-app-env.d.ts - ├── reportWebVitals.ts - ├── setupTests.ts - └── index.tsx/ -``` +
+ ![Перспектива анонимного посетителя](/img/tutorial/realworld-feed-anonymous.jpg) +
+ _Перспектива анонимного посетителя_ +
+
-#### Как это обычно происходит {#how-it-usually-happens} +
+ ![Перспектива авторизованного пользователя](/img/tutorial/realworld-feed-authenticated.jpg) +
+ _Перспектива авторизованного пользователя_ +
+
-И обычно большинство проектов на данном этапе [превращаются в примерно такое][ext-pluralsight--flat]: +На странице ленты есть три динамических области: -```sh -└── src/ - ├── api/ - ├── components/ - ├── containers/ - ├── helpers/ - ├── pages/ - ├── routes/ - ├── store/ - ├── App.tsx - └── index.tsx/ -``` +1. Ссылки для логина, показывающие статус авторизации +2. Список тэгов, фильтрующих ленту +3. Одна—две ленты статей, у каждой статьи кнопка лайка + +Ссылки для логина — часть заголовка, общего для всех страниц, так что пока что отложим их. + +#### Список тэгов + +Чтобы создать список тэгов, нам нужно получить все доступные тэги, отобразить каждый тэг как чип ([chip](https://m3.material.io/components/chips/overview)) и сохранить выбранные тэги в хранилище на стороне клиента. Эти операции относятся к категориям «взаимодействие с API», «пользовательский интерфейс» и «хранение данных». В Feature-Sliced Design код делится по назначению с помощью *сегментов*. Сегменты — это папки в слайсах, и они могут иметь произвольные названия, описывающие их цель, но некоторые цели настолько распространены, что существует несколько общепринятых названий: + +- 📂 `api/` для взаимодействия с бэкендом +- 📂 `ui/` для кода, отвечающего за отображение и внешний вид +- 📂 `model/` для хранения данных и бизнес-логики +- 📂 `config/` для фиче-флагов, переменных окружения и других форм конфигурации -*Они могут как сразу стать такими, так и по прошествии долгой разработки* +Мы поместим код, который получает тэги, в `api`, сам компонент тэга в `ui`, а взаимодействие с хранилищем в `model`. -При этом, если мы заглянем внутрь, как правило обнаружим: +#### Статьи -- Сильно ветвистые по вложенности директории -- Сильно связные друг с другом компоненты -- Огромное количество разнородных компонентов/контейнеров в соответствующих папках, связанные "абы как" +Следуя той же логике, мы можем разбить ленту статей на те же три сегмента: -#### Как это можно делать иначе {#how-can-it-be-done-otherwise} +- 📂 `api/`: получить постраничный список статей с количеством лайков, оставить лайк +- 📂 `ui/`: + - список вкладок, который может отображать дополнительную вкладку при выборе тэга + - отдельная статья + - рабочая пагинация +- 📂 `model/`: клиентское хранилище загруженных постов и текущей страницы (при необходимости) -Каждый, кто хоть сколько давно разрабатывал фронтенд-проекты, примерно понимает преимущества и недостатки такого подхода. +### Переиспользование общего кода -Однако все еще большинство фронтенд-проектов представляют из себя нечто такое, поскольку **нет проверенной опытом гибкой и расширяемой альтернативы** + Страницы, как правило, очень отличаются по своей цели, но что-то остается одинаковым по всему приложению — например, UI-кит, соответствующий языку дизайна, или соглашение на бэкенде, что все делается через REST API с конкретным методом аутентификации. Поскольку слайсы должны быть изолированными, переиспользование кода происходит за счёт слоя ниже, **Shared**. -*Помножим это на вольные адаптации структуры под каждый проект, без запрета со стороны фреймворка - и [получим "уникальные как снежинки проекты"][refs-motivation]* +Shared отличается от других слоев тем, что он содержит сегменты, а не слайсы. Таким образом, слой Shared представляет собой гибрид между слоем и слайсом. -**Цель данного туториала** - показать другой взгляд на привычные практики при проектировании +Обычно код в Shared не планируется заранее, а извлекается по ходу разработки, потому что только во время разработки становится ясно, какие части кода действительно переиспользуются. Тем не менее, полезно держать в голове, какой код имеет смысл хранить в Shared: -#### Адаптируем структуру к нужному виду {#adapting-the-structure-to-the-desired-view} +- 📂 `ui/` — UI-кит, только внешний вид, без бизнес-логики. Например, кнопки, диалоги, поля форм. +- 📂 `api/` — удобные обёртки вокруг запросов на бэкенд (например, обёртка над `fetch()` в случае веба) +- 📂 `config/` — обработка переменных окружения +- 📂 `i18n/` — конфигурация поддержки разных языков +- 📂 `router/` — примитивы и константы маршрутизации -```sh -└── src/ - ├── app/ # Инициализирующая логика приложения - | ├── index.tsx # Энтрипоинт для подключения приложения (бывший App.tsx) - | └── index.css # Глобальные стили приложения - ├── pages/ # - ├── widgets/ # - ├── features/ # - ├── entities/ # - ├── shared/ # - └── index.tsx # Подключение и рендеринг приложения +Это лишь примеры сегментов в Shared, вы можете опустить любой из них или создать свой собственный. Единственное, что нужно помнить при создании новых сегментов — названия сегментов должны описывать **цель (почему), а не суть (что)**. Такие названия как `components` , `hooks` или `modals` *не стоит* использовать, потому что они описывают, что содержат эти файлы по сути, а не то, с какой целью писался этот код. Как следствие таких названий, команде приходится копаться в таких папках, чтоб найти нужное. Помимо этого, несвязанный код лежит рядом, из-за чего при рефакторинге затрагивается большая часть приложения, что усложняет ревью и тестирование. + +### Определите строгий публичный API + +В контексте Feature-Sliced Design термин *публичный API* означает, что слайс или сегмент объявляет, что из него могут импортировать другие модули в проекте. Например, в JavaScript это может быть файл `index.js`, который переэкспортирует объекты из других файлов в слайсе. Это обеспечивает свободу рефакторинга внутри слайса до тех пор, пока контракт с внешним миром (т.е. публичный API) остается неизменным. + +Для слоя Shared, на котором нет слайсов, обычно удобнее определить публичный API (он же индекс) на уровне сегментов, а не один индекс на весь слой. В таком случае импорты из Shared естественным образом организуются по назначению. Для других слоев, на которых слайсы есть, верно обратное — обычно практичнее определить один индекс на слайс и позволить слайсу самому контролировать набор сегментов внутри, потому что другие слои обычно имеют гораздо меньше экспортов и чаще рефакторятся. + +Наши слайсы/сегменты будут выглядеть друг для друга следующим образом: + +``` +📂 pages/ + 📂 feed/ + 📄 index + 📂 sign-in/ + 📄 index + 📂 article-read/ + 📄 index + 📁 … +📂 shared/ + 📂 ui/ + 📄 index + 📂 api/ + 📄 index + 📁 … ``` -Возможно, на первый взгляд, такая структура покажется непривычной, но со временем вы сами заметите, что **используете знакомые вам абстракции, но в консистентном и упорядоченном виде.** +Все, что находится внутри папок типа `pages/feed` или `shared/ui` , известно только этим папкам, и нет никаких гарантий по содержанию этих папок. + +### Крупные переиспользуемые блоки интерфейса + +Ранее мы хотели отдельно вернуться к переиспользуемому заголовку приложения. Собирать его заново на каждой странице было бы непрактично, поэтому мы его переиспользуем. У нас уже есть слой Shared для переиспользования кода, однако, в случае крупных блоков интерфейса в Shared есть нюанс — слой Shared не должен знать о слоях выше. + +Между слоями Shared и Pages есть три других слоя: Entities, Features и Widgets. В других проектах на этих слоях может лежать что-то, что хочется использовать в крупном переиспользуемом блоке, и тогда мы не сможем поместить этот блок в Shared, потому что тогда ему придется импортировать со слоёв выше, а это запрещено. Тут приходит на помощь слой Widgets. Он расположен выше Shared, Entities и Features, поэтому он может использовать их всех. + +В нашем случае заголовок очень простой — это статический логотип и навигация верхнего уровня. Навигация должна спросить у API, авторизован ли сейчас пользователь, но это может быть решено простым импортом из сегмента `api`. Поэтому мы оставим наш заголовок в Shared. + +### Пристальный взгляд на страницу с формой + +Давайте также рассмотрим страницу, на которой можно не только читать, но и редактировать. К примеру, редактор статей: + +![Редактор статей в Conduit](/img/tutorial/realworld-editor-authenticated.jpg) + +Она выглядит тривиально, но содержит несколько аспектов разработки приложений, которые мы еще не исследовали — валидацию форм, состояние ошибки и постоянное хранение данных. + +Для создания этой страницы нам нужно несколько полей и кнопок из Shared, которые мы соберём в форму в сегменте `ui` этой страницы. Затем, в сегменте `api` мы определим изменяющий запрос, чтобы создать статью на бэкенде. + +Чтобы проверить запрос перед отправкой, нам нужна схема валидации, и хорошим местом для нее является сегмент `model` , поскольку это модель данных. Там же мы сгенерируем сообщение об ошибке, а отобразим его с помощью ещё одного компонента в сегменте `ui`. + +Чтобы улучшить пользовательский опыт, мы также можем сохранять введённые данные постоянно, чтобы предотвратить случайную потерю при закрытии браузера. Это тоже подходит под сегмент `model`. + +### Итоги + +Мы разобрали несколько страниц и пришли к базовой структуре нашего приложения: + +1. Слой Shared + 1. `ui` будет содержать наш переиспользуемый UI-кит + 2. `api` будет содержать наши примитивы для взаимодействия с бэкендом + 3. Остальное разложим по ходу написания кода +2. Слой Pages — для каждой страницы отдельный слайс + 1. `ui` будет содержать саму страницу и составляющие её блоки + 2. `api` будет содержать более специализированные функции получения данных, использующие `shared/api` + 3. `model` может содержать клиентское хранилище данных, которые мы будем отображать + +Давайте создадим это приложение! + +## Часть 2. В коде + +Теперь, когда у нас есть план, давайте воплотим его в жизнь. Мы будем использовать React и [Remix](https://remix.run/). + +Для этого проекта уже есть готовый шаблон, cклонируйте его с GitHub, чтобы начать работу: [https://github.com/feature-sliced/tutorial-conduit/tree/clean](https://github.com/feature-sliced/tutorial-conduit/tree/clean) + +Установите зависимости с помощью `npm install` и запустите сервер с помощью `npm run dev`. Откройте [http://localhost:3000](http://localhost:3000/), и вы увидите пустое приложение. -**Также, подключаем поддержку абсолютных импортов для удобства** +### Разложим по страницам -```ts title=tsconfig.json -{ - "compilerOptions": { - "baseUrl": "./src", - // Либо же альясы, если так удобнее +Давайте начнем с создания пустых компонентов для всех наших страниц. Выполните следующую команду в своем проекте: + +```bash +npx fsd pages feed sign-in article-read article-edit profile settings --segments ui ``` -Вот, как это поможет нам в будущем +Это создаст папки наподобие `pages/feed/ui/` и индексный файл `pages/feed/index.ts` для каждой страницы. + +### Подключим страницу фида + +Давайте подключим корневой маршрут (`/`) нашего приложения к странице фида. Создайте компонент `FeedPage.tsx` в `pages/feed/ui` и поместите в него следующее: -```diff -- import App from "../app" -- import Button from "../../shared/ui/button"; -+ import App from "app" -+ import Button from "shared/ui/button"; +```tsx title="pages/feed/ui/FeedPage.tsx" +export function FeedPage() { + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+
+ ); +} ``` -#### Layers: app {#layers-app} +Затем ре-экспортируйте этот компонент в публичном API страницы фида, файл `pages/feed/index.ts`: -Как можно заметить - мы перенесли всю базовую логику в директорию `app/` +```tsx title="pages/feed/index.ts" +export { FeedPage } from "./ui/FeedPage"; +``` -Именно там, согласно методологии, стоит располагать всю подготовительную логику: +Теперь подключите его к корневому маршруту. В Remix маршрутизация работает на файлах, и файлы маршрутов находятся в папке `app/routes`, что хорошо сочетается с Feature-Sliced Design. -- подключение глобальных стилей (`/app/styles/**` + `/app/index.css`) -- провайдеры и HOCs с инициализирующей логикой (`/app/providers/**`) +Используйте компонент `FeedPage` в `app/routes/_index.tsx`: -Пока что перенесем туда всю существующую логику, а другие директории оставим пустыми, как на схеме выше. +```tsx title="app/routes/_index.tsx" +import type { MetaFunction } from "@remix-run/node"; +import { FeedPage } from "pages/feed"; -```tsx title=app/index.tsx -import "./index.css"; +export const meta: MetaFunction = () => { + return [{ title: "Conduit" }]; +}; -const App = () => {...} +export default FeedPage; ``` -### 1.3 Подключим глобальные стили {#13-enabling-global-styles} +Затем, если вы запустите dev-сервер и откроете приложение, вы должны увидеть баннер Conduit! + +![Баннер Conduit](/img/tutorial/conduit-banner.jpg) -#### Установим зависимости {#install-dependencies} +### API-клиент -В туториале устанавливаем sass, но можно взять и любой другой препроцессор, поддерживающий импорты +Чтобы общаться с бэкендом RealWorld, давайте создадим удобный API-клиент в Shared. Создайте два сегмента, `api` для клиента и `config` для таких переменных как базовый URL бэкенда: -```cmd -$ npm i sass +```bash +npx fsd shared --segments api config ``` -#### Заводим файлы для стилей {#creating-files-for-styles} +Затем создайте `shared/config/backend.ts`: -##### Для css-переменных {#for-css-variables} +```tsx title="shared/config/backend.ts" +export const backendBaseUrl = "https://api.realworld.io/api"; +``` -```scss title=app/styles/vars.scss -:root { - --color-dark: #242424; - --color-primary: #108ee9; - ... -} +```tsx title="shared/config/index.ts" +export { backendBaseUrl } from "./backend"; ``` -##### Для нормализации стилей {#to-normalize-styles} +Поскольку проект RealWorld предоставляет [спецификацию OpenAPI](https://github.com/gothinkster/realworld/blob/main/api/openapi.yml), мы можем автоматически сгенерировать типы для нашего API-клиента. Мы будем использовать [пакет `openapi-fetch`](https://openapi-ts.pages.dev/openapi-fetch/), в котором дополнительно есть генератор типов. -```scss title=app/styles/normalize.scss -html { - scroll-behavior: smooth; -} -... +Выполните следующую команду, чтобы сгенерировать актуальные типы для API: + +```bash +npm run generate-api-types +``` + +В результате будет создан файл `shared/api/v1.d.ts`. Мы воспользуемся этим файлом в `shared/api/client.ts` для создания типизированного клиента API: + +```tsx title="shared/api/client.ts" +import createClient from "openapi-fetch"; + +import { backendBaseUrl } from "shared/config"; +import type { paths } from "./v1"; + +export const { GET, POST, PUT, DELETE } = createClient({ baseUrl: backendBaseUrl }); +``` + +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; ``` -##### Подключаем все стили {#connecting-all-styles} +### Реальные данные в ленте + +Теперь мы можем перейти к получению статей из бэкенда и добавлению их в ленту. Начнем с реализации компонента предпросмотра статьи. + +Создайте `pages/feed/ui/ArticlePreview.tsx` со следующим содержимым: -```scss title=app/styles/index.scss -@import "./normalize.scss"; -@import "./vars.scss"; -... +```tsx title="pages/feed/ui/ArticlePreview.tsx" +export function ArticlePreview({ article }) { /* TODO */ } ``` -```scss title=app/index.scss -@import "./styles/index.scss"; -... +Поскольку мы пишем на TypeScript, было бы неплохо иметь типизированный объект статьи Article. Если мы изучим сгенерированный `v1.d.ts`, то увидим, что объект Article доступен через `components["schemas"]["Article"]`. Поэтому давайте создадим файл с нашими моделями данных в Shared и экспортируем модели: + +```tsx title="shared/api/models.ts" +import type { components } from "./v1"; + +export type Article = components["schemas"]["Article"]; ``` -```tsx title=app/index.tsx -import "./index.scss" +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; -const App = () => {...} +export type { Article } from "./models"; ``` -### 1.4 Добавим роутинг {#14-adding-routing} +Теперь мы можем вернуться к компоненту предпросмотра статьи и заполнить разметку данными. Обновите компонент, добавив в него следующее содержимое: -#### Установим зависимости {#install-dependencies-1} +```tsx title="pages/feed/ui/ArticlePreview.tsx" +import { Link } from "@remix-run/react"; +import type { Article } from "shared/api"; + +interface ArticlePreviewProps { + article: Article; +} -```cmd -$ npm i react-router react-router-dom compose-function -$ npm i -D @types/react-router @types/react-router-dom @types/compose-function +export function ArticlePreview({ article }: ArticlePreviewProps) { + return ( +
+
+ + + +
+ + {article.author.username} + + + {new Date(article.createdAt).toLocaleDateString(undefined, { + dateStyle: "long", + })} + +
+ +
+ +

{article.title}

+

{article.description}

+ Read more... +
    + {article.tagList.map((tag) => ( +
  • + {tag} +
  • + ))} +
+ +
+ ); +} ``` -#### Добавим HOC для инициализации роутера {#add-hoc-to-initialize-the-router} +Кнопка "Мне нравится" пока ничего не делает, мы исправим это, когда перейдем на страницу чтения статей и реализуем функцию "Мне нравится". -```tsx title=app/providers/with-router.tsx -import { Suspense } from "react"; -import { BrowserRouter } from "react-router-dom"; +Теперь мы можем получить статьи и отобразить кучу этих карточек предпросмотра. Получение данных в Remix осуществляется с помощью *загрузчиков* — серверных функций, которые собирают те данные, которые нужны странице. Загрузчики взаимодействуют с API от имени страницы, поэтому мы поместим их в сегмент `api` страницы: -export const withRouter = (component: () => React.ReactNode) => () => ( - - - {component()} - - -); +```tsx title="pages/feed/api/loader.ts" +import { json } from "@remix-run/node"; + +import { GET } from "shared/api"; + +export const loader = async () => { + const { data: articles, error, response } = await GET("/articles"); + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return json({ articles }); +}; ``` -```ts title=app/providers/index.ts -import compose from "compose-function"; -import { withRouter } from "./with-router"; +Чтобы подключить его к странице, нам нужно экспортировать его с именем `loader` из файла маршрута: -export const withProviders = compose(withRouter); +```tsx title="pages/feed/index.ts" +export { FeedPage } from "./ui/FeedPage"; +export { loader } from "./api/loader"; ``` -```tsx title=app/index.tsx -import { withProviders } from "./providers"; -... +```tsx title="app/routes/_index.tsx" +import type { MetaFunction } from "@remix-run/node"; +import { FeedPage } from "pages/feed"; -const App = () => {...} +export { loader } from "pages/feed"; -export default withProviders(App); +export const meta: MetaFunction = () => { + return [{ title: "Conduit" }]; +}; + +export default FeedPage; ``` -#### Добавим реальные страницы {#lets-add-real-pages} +И последний шаг — отображение этих карточек в ленте. Обновите `FeedPage` следующим кодом: + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { useLoaderData } from "@remix-run/react"; -:::note +import type { loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; -Это лишь одна из реализаций роутинга +export function FeedPage() { + const { articles } = useLoaderData(); -- Можно объявлять его декларативно либо через список роутов (+ react-router-config) -- Можно объявлять его на уровне pages либо app + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ {articles.articles.map((article) => ( + + ))} +
+
+
+
+ ); +} +``` -Методология пока никак не регламентирует реализацию этой логики +### Фильтрация по тегам -::: +Что касается тегов, то наша задача — получить их из бэкенда и запомнить выбранный пользователем тег. Мы уже знаем, как загружать из бэкенда — это еще один запрос от функции-загрузчика. Мы будем использовать удобную функцию `promiseHash` из пакета `remix-utils`, который уже установлен. -##### Временная страница, только для проверки роутинга {#temporary-page-only-for-checking-the-routing} +Обновите файл загрузчика, `pages/feed/api/loader.ts`, следующим кодом: -Ее можно удалить позднее +```tsx title="pages/feed/api/loader.ts" +import { json } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; -```tsx title=pages/test/index.tsx -const TestPage = () => { - return
Test Page
; +import { GET } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +export const loader = async () => { + return json( + await promiseHash({ + articles: throwAnyErrors(GET("/articles")), + tags: throwAnyErrors(GET("/tags")), + }), + ); }; +``` + +Вы можете заметить, что мы вынесли обработку ошибок в общую функцию `throwAnyErrors`. Она выглядит довольно полезной, так что, возможно, мы захотим переиспользовать её позже, а пока давайте просто заметим этот факт. -export default TestPage; +Теперь перейдем к списку тегов. Он должен быть интерактивным - щелчок по тегу должен выбрать этот тег. По традиции Remix, мы будем использовать параметры запроса в URL в качестве хранилища для выбранного тега. Пусть браузер позаботится о хранилище, а мы сосредоточимся на более важных вещах. + +Обновите `pages/feed/ui/FeedPage.tsx` следующим кодом: + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { Form, useLoaderData } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import type { loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; + +export function FeedPage() { + const { articles, tags } = useLoaderData(); + + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ {articles.articles.map((article) => ( + + ))} +
+ +
+
+

Popular Tags

+ +
+ +
+ {tags.tags.map((tag) => ( + + ))} +
+ +
+
+
+
+
+ ); +} ``` -##### Сформируем роуты {#lets-form-the-routes} +Затем нам нужно использовать параметр поиска тегов в нашем загрузчике. Измените функцию `loader` в `pages/feed/api/loader.ts` на следующую: -```tsx title=pages/index.tsx -// Либо использовать @loadable/component, в рамках туториала - некритично -import { lazy } from "react"; -import { Route, Routes, Navigate } from "react-router-dom"; +```tsx title="pages/feed/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; -const TestPage = lazy(() => import("./test")); +import { GET } from "shared/api"; -export const Routing = () => { - return ( - - } /> - } /> - - ); +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} +export const loader = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const selectedTag = url.searchParams.get("tag") ?? undefined; + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles", { params: { query: { tag: selectedTag } } }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); }; ``` -##### Подключаем роутинг к приложению {#connecting-the-routing-to-the-application} +И всё, сегмент `model` нам не понадобился. Remix — клёвая штука. -```tsx title=app/index.tsx -import { Routing } from "pages"; +### Пагинация -const App = () => ( - // Потенциально сюда можно вставить - // Единый на все приложение хедер - // Либо же делать это на отдельных страницах - -) -... -``` +Аналогичным образом мы можем реализовать пагинацию. Не стесняйтесь попробовать реализовать её сами или же просто скопируйте код ниже. В любом случае, осуждать вас некому. + +```tsx title="pages/feed/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } -#### Layers: app, pages {#layers-app-pages} + return data as NonNullable; +} -Здесь мы использовали сразу несколько слоев: +/** Amount of articles on one page. */ +export const LIMIT = 20; + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const selectedTag = url.searchParams.get("tag") ?? undefined; + const page = parseInt(url.searchParams.get("page") ?? "", 10); + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles", { + params: { + query: { + tag: selectedTag, + limit: LIMIT, + offset: !Number.isNaN(page) ? page * LIMIT : undefined, + }, + }, + }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); +}; +``` -- `app` - для инициализации роутера *(HOC: withRouter)* -- `pages` - для хранения модулей страниц +```tsx title="pages/feed/ui/FeedPage.tsx" +import { Form, useLoaderData, useSearchParams } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; -### 1.5 Подключим UIKit {#15-lets-connect-uikit} +import { LIMIT, type loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; -Для упрощения туториала, воспользуемся готовым UIKit от [AntDesign](https://ant.design/components/overview/) +export function FeedPage() { + const [searchParams] = useSearchParams(); + const { articles, tags } = useLoaderData(); + const pageAmount = Math.ceil(articles.articlesCount / LIMIT); + const currentPage = parseInt(searchParams.get("page") ?? "1", 10); -```cmd -$ npm i antd @ant-design/icons + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ {articles.articles.map((article) => ( + + ))} + +
+ +
    + {Array(pageAmount) + .fill(null) + .map((_, index) => + index + 1 === currentPage ? ( +
  • + {index + 1} +
  • + ) : ( +
  • + +
  • + ), + )} +
+ +
+ +
+
+

Popular Tags

+ +
+ +
+ {tags.tags.map((tag) => ( + + ))} +
+ +
+
+
+
+
+ ); +} ``` -:::tip +Ну вот, это тоже сделали. Есть еще список вкладок, который можно реализовать аналогичным образом, но давайте повременим с этим, пока не реализуем аутентификацию. Кстати, о ней! + +### Аутентификация {#authentication} + +Аутентификация включает в себя две страницы — одну для входа в систему и другую для регистрации. Они, в основном, очень схожие, поэтому имеет смысл держать их в одном слайсе, `sign-in`, чтобы при необходимости можно было переиспользовать код. -Но вы можете использовать **любой другой UIKit** или же **создать собственный**, расположив компоненты в `shared/ui` - именно там рекомендуется хранить UIKit приложения: +Создайте `RegisterPage.tsx` в сегменте `ui` в `pages/sign-in` со следующим содержимым: -```ts -import { Checkbox } from "antd"; // ~ "shared/ui/checkbox" -import { Card } from "antd"; // ~ "shared/ui/card" +```tsx title="pages/sign-in/ui/RegisterPage.tsx" +import { Form, Link, useActionData } from "@remix-run/react"; + +import type { register } from "../api/register"; + +export function RegisterPage() { + const registerData = useActionData(); + + return ( +
+
+
+
+

Sign up

+

+ Have an account? +

+ + {registerData?.error && ( +
    + {registerData.error.errors.body.map((error) => ( +
  • {error}
  • + ))} +
+ )} + +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+ ); +} ``` -::: +Сейчас нам нужно исправить сломанный импорт. Он обращается к новому сегменту, поэтому создайте его: -## 2. Реализация бизнес-логики {#2-implementing-business-logic} +```bash +npx fsd pages sign-in -s api +``` -:::note +Однако прежде чем мы сможем реализовать бэкенд-часть регистрации, нам нужен некоторый инфраструктурный код для Remix для обработки сессий. Отправим его в Shared, на случай, если он понадобится какой-либо другой странице. -Постараемся сконцентрироваться не на реализации каждого модуля, а на их последовательной композиции +Поместите следующий код в `shared/api/auth.server.ts`. Этот код очень специфичен для Remix, так что не беспокойтесь, если там не все понятно, просто скопируйте и вставьте: -::: +```tsx title="shared/api/auth.server.ts" +import { createCookieSessionStorage, redirect } from "@remix-run/node"; +import invariant from "tiny-invariant"; -### 2.1 Проанализируем функциональность {#21-lets-analyze-the-functionality} +import type { User } from "./models"; -Прежде чем приступать к коду, надо определиться - [какую ценность мы хотим донести конечному пользователю][refs-needs] +invariant( + process.env.SESSION_SECRET, + "SESSION_SECRET must be set for authentication to work", +); -Для этого, декомпозируем нашу функциональность *по зонам ответственности (слоям)* +const sessionStorage = createCookieSessionStorage<{ + user: User; +}>({ + cookie: { + name: "__session", + httpOnly: true, + path: "/", + sameSite: "lax", + secrets: [process.env.SESSION_SECRET], + secure: process.env.NODE_ENV === "production", + }, +}); -![layers-flow-themed](/img/layers_flow.png) +export async function createUserSession({ + request, + user, + redirectTo, +}: { + request: Request; + user: User; + redirectTo: string; +}) { + const cookie = request.headers.get("Cookie"); + const session = await sessionStorage.getSession(cookie); + + session.set("user", user); + + return redirect(redirectTo, { + headers: { + "Set-Cookie": await sessionStorage.commitSession(session, { + maxAge: 60 * 60 * 24 * 7, // 7 days + }), + }, + }); +} -#### Pages +export async function getUserFromSession(request: Request) { + const cookie = request.headers.get("Cookie"); + const session = await sessionStorage.getSession(cookie); -Набросаем базово необходимые страницы, и пользовательские ожидания от них: + return session.get("user") ?? null; +} -1. `TasksListPage` - страница "Список задач" - - Смотреть список задач - - Переходить к странице конкретной задачи - - *Помечать выполненной/невыполненной конкретную задачу* - - Задавать фильтрацию по выполненным/невыполненным задачам +export async function requireUser(request: Request) { + const user = await getUserFromSession(request); -2. `TaskDetailsPage` - страница "Карточка задачи" - - Смотреть информацию по задаче - - *Помечать выполненной/невыполненной конкретную задачу* - - Возвращаться к списку задач + if (user === null) { + throw redirect("/login"); + } -Каждая из описанных возможностей - представляет из себя часть функциональности + return user; +} +``` -##### Обычный подход {#usual-approach} +А также экспортируйте модель `User` из файла `models.ts`, расположенного рядом с ним: -И есть большой соблазн +```tsx title="shared/api/models.ts" +import type { components } from "./v1"; -- либо всю логику реализовать в директории каждой конкретной страницы. -- либо все "возможно переиспользуемые" модули вынести в общую папку `src/components` или подобную +export type Article = components["schemas"]["Article"]; +export type User = components["schemas"]["User"]; +``` -Но если для маленького и недолгоживущего проекта такое решение подошло бы, то в реальной корпоративной разработке, оно **может поставить крест** на дальнейшем развитии проекта, превратив его в **"еще одно дремучее легаси"** +Прежде чем этот код заработает, необходимо установить переменную окружения `SESSION_SECRET`. Создайте файл `.env` в корне проекта, пропишите в нем `SESSION_SECRET=`, а затем пробегитесь по клавиатуре, чтобы создать длинную случайную строку. У вас должно получиться что-то вроде этого: -Обусловлено это обычными условиями развития проекта: +```bash title=".env" +SESSION_SECRET=несмейтеэтокопировать +``` -- требования меняются достаточно часто -- появляются новые обстоятельства -- техдолг копится с каждым днем и все сложнее добавлять новые фичи -- нужно масштабировать как сам проект, так и его команду +Наконец, добавьте несколько экспортов в публичный API, чтобы использовать этот код: -##### Альтернативный подход {#alternative-approach} +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; -Даже при базовом разбиении мы видим, что: +export type { Article } from "./models"; -- между страницами есть общие сущности и их отображение *(Task)* -- между страницами есть общие фичи *(Помечать задачу выполненной / невыполненной)* +export { createUserSession, getUserFromSession, requireUser } from "./auth.server"; +``` -Соответственно, кажется логичным продолжать декомпозировать задачу, но уже исходя из перечисленных выше возможностей для пользователя. +Теперь мы можем написать код, который будет общаться с бэкендом RealWorld для регистрации. Мы сохраним его в `pages/sign-in/api`. Создайте файл `register.ts` и поместите в него следующий код: -#### Features +```tsx title="pages/sign-in/api/register.ts" +import { json, type ActionFunctionArgs } from "@remix-run/node"; -Части функциональности, несущие ценность пользователю +import { POST, createUserSession } from "shared/api"; -- `` - (компонент) Пометить задачу выполненной / невыполненной -- `` - (компонент) Задать фильтрацию для списка задач +export const register = async ({ request }: ActionFunctionArgs) => { + const formData = await request.formData(); + const username = formData.get("username")?.toString() ?? ""; + const email = formData.get("email")?.toString() ?? ""; + const password = formData.get("password")?.toString() ?? ""; -#### Entities + const { data, error } = await POST("/users", { + body: { user: { email, password, username } }, + }); -Бизнес-сущности, на которых будет строится более высокоуровневая логика + if (error) { + return json({ error }, { status: 400 }); + } else { + return createUserSession({ + request: request, + user: data.user, + redirectTo: "/", + }); + } +}; +``` -- `` - (компонент) Карточка задачи, с отображением информации -- `getTasksListFx({ filters })` - (effect) Подгрузка списка задач с параметрами -- `getTaskByIdFx(taskId: number)`- (effect) Подгрузка задачи по ID +```tsx title="pages/sign-in/index.ts" +export { RegisterPage } from './ui/RegisterPage'; +export { register } from './api/register'; +``` -#### Shared +Почти готово! Осталось подключить страницу и действие регистрации к маршруту `/register`. Создайте `register.tsx` в `app/routes`: -Переиспользуемые общие модули, без привязки к предметной области +```tsx title="app/routes/register.tsx" +import { RegisterPage, register } from "pages/sign-in"; -- `` - (компонент) UIKit компонент - - *При этом можно как реализовывать собственный UIKit под проект, так воспользоваться готовым* -- `getTasksList({ filters })` - (api) Подгрузка списка задач с параметрами -- `getTaskById(taskId: number)`- (api) Подгрузка задачи по ID +export { register as action }; -#### В чем профит? {#what-is-the-profit} +export default RegisterPage; +``` -Теперь все модули можно проектировать со [слабой связностью][refs-low-coupling] и со своей зоной ответственности, а также распределить по команде без конфликтов при разработке +Теперь, если вы перейдете на [http://localhost:3000/register,](http://localhost:3000/register) вы сможете создать пользователя! Остальная часть приложения пока что на это не отреагирует, мы займемся этим в ближайшее время. -*А самое главное - теперь каждый модуль служит для построения конкретной бизнес-ценности, что снижает риски для создания ["фич ради фич"][refs-needs]* +Аналогичным образом мы можем реализовать страницу входа в систему. Попробуйте сами или просто возьмите код и двигайтесь дальше: -### 2.2 Про что еще стоит помнить {#22-what-else-is-worth-remembering} +```tsx title="pages/sign-in/api/sign-in.ts" +import { json, type ActionFunctionArgs } from "@remix-run/node"; -#### Слои и ответственность {#layers-and-responsibilities} +import { POST, createUserSession } from "shared/api"; -Как было описано выше, благодаря слоистой структуре мы можем **предсказуемо распределять сложность приложения** согласно [зонам ответственности, т.е. слоям][refs-layers]. +export const signIn = async ({ request }: ActionFunctionArgs) => { + const formData = await request.formData(); + const email = formData.get("email")?.toString() ?? ""; + const password = formData.get("password")?.toString() ?? ""; -При этом более высокоуровневая логика строится на основание нижележащих слоев: + const { data, error } = await POST("/users/login", { + body: { user: { email, password } }, + }); -```tsx -// (shared) => (entities) + (features) => (pages) - + => + => + if (error) { + return json({ error }, { status: 400 }); + } else { + return createUserSession({ + request: request, + user: data.user, + redirectTo: "/", + }); + } +}; ``` -#### Подготовка модулей к использованию {#preparing-modules-for-use} +```tsx title="pages/sign-in/ui/SignInPage.tsx" +import { Form, Link, useActionData } from "@remix-run/react"; + +import type { signIn } from "../api/sign-in"; -Каждый реализуемый модуль должен предоставлять к использованию свой [публичный интерфейс][refs-public-api]: +export function SignInPage() { + const signInData = useActionData(); -```ts title={layer}/foo/index.ts -export { Card as FooCard, Thumbnail as FooThumbnail, ... } from "./ui"; -export * as fooModel from "./model"; + return ( +
+
+
+
+

Sign in

+

+ Need an account? +

+ + {signInData?.error && ( +
    + {signInData.error.errors.body.map((error) => ( +
  • {error}
  • + ))} +
+ )} + +
+
+ +
+
+ +
+ +
+
+
+
+
+ ); +} ``` -:::info +```tsx title="pages/sign-in/index.ts" +export { RegisterPage } from './ui/RegisterPage'; +export { register } from './api/register'; +export { SignInPage } from './ui/SignInPage'; +export { signIn } from './api/sign-in'; +``` -Если вам нужны именованные экспорты неймспейсов для декларации Public API, можно посмотреть в сторону [@babel/plugin-proposal-export-namespace-from](https://babeljs.io/docs/en/babel-plugin-proposal-export-namespace-from) +```tsx title="app/routes/login.tsx" +import { SignInPage, signIn } from "pages/sign-in"; -Либо же, как альтернатива, использовать более развернутую конструкцию +export { signIn as action }; -```ts title={layer}/foo/index.ts -import { Card as FooCard, Thumbnail as FooThumbnail, ... } from "./ui"; -import * as fooModel from "./model"; +export default SignInPage; +``` + +Теперь давайте дадим пользователям возможность попасть на эти страницы. + +### Хэдер + +Как мы уже говорили в первой части, хэдер приложения обычно размещается либо в Widgets, либо в Shared. Мы поместим его в Shared, потому что он очень прост, и вся бизнес-логика может быть сохранена за его пределами. Давайте создадим для него место: -export { FooCard, FooThumbnail, fooModel }; +```bash +npx fsd shared ui ``` -::: +Теперь создайте `shared/ui/Header.tsx` со следующим содержимым: -### 2.3 Отобразим базово список задач {#23-lets-display-the-basic-task-list} +```tsx title="shared/ui/Header.tsx" +import { useContext } from "react"; +import { Link, useLocation } from "@remix-run/react"; -#### (entities) Карточка задачи {#entities-task-card} +import { CurrentUser } from "../api/currentUser"; -```tsx title=entities/task/ui/task-row/index.tsx -import { Link } from "react-router-dom"; -import cn from "classnames"; // Можно смело использовать аналоги -import { Row } from "antd"; // ~ "shared/ui/row" +export function Header() { + const currentUser = useContext(CurrentUser); + const { pathname } = useLocation(); -export const TaskRow = ({ data, titleHref }: TaskRowProps) => { - return ( - - {titleHref ? {data.title} : data.title} - - ) + return ( + + ); } ``` -#### (entities) Подгрузка списка задач {#entities-loading-the-task-list} +Экспортируйте этот компонент из `shared/ui`: -Можно разбивать по типу сущности, либо хранить все в duck-modular-стиле +```tsx title="shared/ui/index.ts" +export { Header } from "./Header"; +``` -> Более подробно с реализацией API по туториалу можно ознакомиться [здесь][ext-source-api] +В хэдере мы полагаемся на контекст, расположенный в `shared/api`. Создайте ещё его: -```ts title=entities/task/model/index.ts -import { createStore, combine, createEffect, createEvent } from "effector"; -import { useStore } from "effector-react"; +```tsx title="shared/api/currentUser.ts" +import { createContext } from "react"; -import { typicodeApi } from "shared/api"; -import type { Task } from "shared/api"; +import type { User } from "./models"; -// В каждом эффекте так же может быть своя доп. обработка -const getTasksListFx = createEffect((params?: typicodeApi.tasks.GetTasksListParams) => { - // Здесь также может быть доп. обработка эффекта - return typicodeApi.tasks.getTasksList(params); -}); +export const CurrentUser = createContext(null); +``` + +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; -// Можно хранить и в нормализованном виде -export const $tasks = createStore([]) - .on(getTasksListFx.doneData, (_, payload) => ...) +export type { Article } from "./models"; -export const $tasksList = combine($tasks, (tasks) => Object.values(tasks)); -// Можно промаппить и другие вещи вроде `isEmpty`, `isLoading`, ... +export { createUserSession, getUserFromSession, requireUser } from "./auth.server"; +export { CurrentUser } from "./currentUser"; ``` -#### (pages) Соединим всю логику на странице {#pages-lets-connect-all-the-logic-on-the-page} +Теперь давайте добавим хэдер на страницу. Мы хотим, чтобы он был на каждой странице, поэтому имеет смысл просто добавить его в корневой маршрут и обернуть аутлет (место, в которое будет отрендерена страница) провайдером контекста `CurrentUser`. Таким образом, все наше приложение, включая хэдер, получит доступ к объекту текущего пользователя. Мы также добавим загрузчик для получения объекта текущего пользователя из cookies. Добавьте следующее в `app/root.tsx`: + +```tsx title="app/root.tsx" +import { cssBundleHref } from "@remix-run/css-bundle"; +import type { LinksFunction, LoaderFunctionArgs } from "@remix-run/node"; +import { + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration, + useLoaderData, +} from "@remix-run/react"; -```tsx title=pages/tasks-list/index.tsx -import { useEffect } from "react"; -// Если чувствуете себя уверенно с @effector/reflect - можете сразу использовать его -// В рамках туториала некритично -import { useStore } from "effector"; -import { Layout, Row, Col, Typography, Spin, Empty } from "antd"; // ~ "shared/ui/{...}" +import { Header } from "shared/ui"; +import { getUserFromSession, CurrentUser } from "shared/api"; -import { TaskRow, taskModel } from "entities/task"; -import styles from "./styles.module.scss"; +export const links: LinksFunction = () => [ + ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []), +]; -const TasksListPage = () => { - const tasks = useStore(taskModel.$tasksList); - const isLoading = useStore(taskModel.$tasksListLoading); - const isEmpty = useStore(taskModel.$tasksListEmpty); +export const loader = ({ request }: LoaderFunctionArgs) => + getUserFromSession(request); - /** - * Запрашиваем данные при загрузке страницы - * @remark Является плохой практикой в мире effector и представлено здесь - лишь для наглядной демонстрации - * Лучше фетчить через event.pageMounted или reflect - */ - useEffect(() => taskModel.getTasksListFx(), []); +export default function App() { + const user = useLoaderData(); return ( - - - - Tasks List - - {/* TODO: TasksFilters */} - - - - {isLoading && } - {!isLoading && tasks.map((task) => ( - - - + + + + + + + + + + + + + +
+ + + + + + + + ); +} +``` + +В итоге на главной странице должно получиться следующее: + +
+ ![Страница фида Conduit, на которой есть хэдер, фид и теги. Вкладки по-прежнему отсутствуют.](/img/tutorial/realworld-feed-without-tabs.jpg) + +
Страница фида Conduit, на которой есть хэдер, фид и теги. Вкладки по-прежнему отсутствуют.
+
+ +### Вкладки + +Теперь, когда мы можем определить состояние аутентификации, давайте также быстренько реализуем вкладки и лайки, чтоб закончить со страницей ленты. Нам нужна еще одна форма, но этот файл страницы становится слишком большим, поэтому давайте перенесем эти формы в соседние файлы. Мы создадим `Tabs.tsx`, `PopularTags.tsx` и `Pagination.tsx` со следующим содержимым: + +```tsx title="pages/feed/ui/Tabs.tsx" +import { useContext } from "react"; +import { Form, useSearchParams } from "@remix-run/react"; + +import { CurrentUser } from "shared/api"; + +export function Tabs() { + const [searchParams] = useSearchParams(); + const currentUser = useContext(CurrentUser); + + return ( +
+
+
    + {currentUser !== null && ( +
  • + +
  • + )} +
  • + +
  • + {searchParams.has("tag") && ( +
  • + + {searchParams.get("tag")} + +
  • + )} +
+
+
+ ); +} +``` + +```tsx title="pages/feed/ui/PopularTags.tsx" +import { Form, useLoaderData } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import type { loader } from "../api/loader"; + +export function PopularTags() { + const { tags } = useLoaderData(); + + return ( +
+

Popular Tags

+ +
+ +
+ {tags.tags.map((tag) => ( + ))} - {!isLoading && isEmpty && } - - - +
+ +
); -}; +} ``` -### 2.4 Добавим переключение статуса задач {#24-adding-task-status-switching} +```tsx title="pages/feed/ui/Pagination.tsx" +import { Form, useLoaderData, useSearchParams } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; -#### (entities) Переключение статуса задачи {#entities-switching-the-task-status} +import { LIMIT, type loader } from "../api/loader"; -```ts title=entities/task/model/index.ts -export const toggleTask = createEvent(); +export function Pagination() { + const [searchParams] = useSearchParams(); + const { articles } = useLoaderData(); + const pageAmount = Math.ceil(articles.articlesCount / LIMIT); + const currentPage = parseInt(searchParams.get("page") ?? "1", 10); + + return ( +
+ +
    + {Array(pageAmount) + .fill(null) + .map((_, index) => + index + 1 === currentPage ? ( +
  • + {index + 1} +
  • + ) : ( +
  • + +
  • + ), + )} +
+ + ); +} +``` -export const $tasks = createStore(...) - ... - .on(toggleTask, (state, taskId) => produce(state, draft => { - const task = draft[taskId]; - task.completed = !task.completed; - console.log(1, { taskId, state, draft: draft[taskId].completed }); - })) +И теперь мы можем значительно упростить саму страницу с фидом: +```tsx title="pages/feed/ui/FeedPage.tsx" +import { useLoaderData } from "@remix-run/react"; -// Делаем хуком, чтобы завязаться на обновления react -// @see В случае эффектора, использование хука - это крайняя мера, т.к. более предпочтительны computed-сторы -export const useTask = (taskId: number): import("shared/api").Task | undefined => { - return useStoreMap({ - store: $tasks, - keys: [taskId], - fn: (tasks, [id]) => tasks[id] ?? null - }); +import type { loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; +import { Tabs } from "./Tabs"; +import { PopularTags } from "./PopularTags"; +import { Pagination } from "./Pagination"; + +export function FeedPage() { + const { articles } = useLoaderData(); + + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ + + {articles.articles.map((article) => ( + + ))} + + +
+ +
+ +
+
+
+
+ ); +} +``` + +Нам также нужно учесть новую вкладку в функции-загрузчике: + +```tsx title="pages/feed/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET, requireUser } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + /* unchanged */ +} + +/** Amount of articles on one page. */ +export const LIMIT = 20; + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const selectedTag = url.searchParams.get("tag") ?? undefined; + const page = parseInt(url.searchParams.get("page") ?? "", 10); + + if (url.searchParams.get("source") === "my-feed") { + const userSession = await requireUser(request); + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles/feed", { + params: { + query: { + limit: LIMIT, + offset: !Number.isNaN(page) ? page * LIMIT : undefined, + }, + }, + headers: { Authorization: `Token ${userSession.token}` }, + }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); + } + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles", { + params: { + query: { + tag: selectedTag, + limit: LIMIT, + offset: !Number.isNaN(page) ? page * LIMIT : undefined, + }, + }, + }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); }; ``` -#### (features) Чекбокс для задачи {#features-checkbox-for-the-task} +Прежде чем мы отложим страницу ленты, давайте добавим код, который будет обрабатывать лайки к постам. Измените ваш `ArticlePreview.tsx` на следующий: -```tsx title=features/toggle-task/ui.tsx -import { Checkbox } from "antd"; // ~ "shared/ui/checkbox" -import { taskModel } from "entities/task"; +```tsx title="pages/feed/ui/ArticlePreview.tsx" +import { Form, Link } from "@remix-run/react"; +import type { Article } from "shared/api"; -// resolve / unresolve -export const ToggleTask = ({ taskId }: ToggleTaskProps) => { - const task = taskModel.useTask(taskId); - if (!task) return null; +interface ArticlePreviewProps { + article: Article; +} - return ( - taskModel.toggleTask(taskId)} - checked={task.completed} - /> - ) +export function ArticlePreview({ article }: ArticlePreviewProps) { + return ( +
+
+ + + +
+ + {article.author.username} + + + {new Date(article.createdAt).toLocaleDateString(undefined, { + dateStyle: "long", + })} + +
+
+ +
+
+ +

{article.title}

+

{article.description}

+ Read more... +
    + {article.tagList.map((tag) => ( +
  • + {tag} +
  • + ))} +
+ +
+ ); } ``` -#### (pages) Внедряем чекбокс в страницу {#pages-embedding-the-checkbox-in-the-page} +Этот код отправит POST-запрос на `/article/:slug` с `_action=favorite`, чтобы отметить статью как любимую. Пока это не работает, но как только мы начнем работать над читалкой статей, мы реализуем и это. -Что примечательно - карточка задачи совсем не знает ни про страницу где используется, ни про то, какие кнопки-действия в нее могут вставляться (то же самое можно сказать и про саму фичу) +И на этом мы официально закончили работу над фидом! Ура! -Такой подход позволяет одновременно **грамотно разделять ответственность** и **гибко переиспользовать логику при реализации** +### Читалка статей -```tsx title=pages/tasks-list/index.tsx -import { ToggleTask } from "features/toggle-task"; -import { TaskRow, taskModel } from "entities/task"; -... - - } - /> - +Во-первых, нам нужны данные. Давайте создадим загрузчик: + +```bash +npx fsd pages article-read -s api ``` -### 2.5 Добавим фильтрацию задач {#25-adding-task-filtering} +```tsx title="pages/article-read/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import invariant from "tiny-invariant"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; -#### (entities) Фильтрация на уровне данных {#entities-filtering-at-the-data-level} +import { GET, getUserFromSession } from "shared/api"; -```ts title=entities/task/model/index.ts -import { combine, createEvent, createStore } from "effector"; +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; -export type QueryConfig = { completed?: boolean }; + if (error !== undefined) { + throw json(error, { status: response.status }); + } -const setQueryConfig = createEvent(); + return data as NonNullable; +} -// Можно вынести в отдельную директорию (для хранения нескольких моделей) -export const $queryConfig = createStore({}) - .on(setQueryConfig, (_, payload) => payload); +export const loader = async ({ request, params }: LoaderFunctionArgs) => { + invariant(params.slug, "Expected a slug parameter"); + const currentUser = await getUserFromSession(request); + const authorization = currentUser + ? { Authorization: `Token ${currentUser.token}` } + : undefined; + + return json( + await promiseHash({ + article: throwAnyErrors( + GET("/articles/{slug}", { + params: { + path: { slug: params.slug }, + }, + headers: authorization, + }), + ), + comments: throwAnyErrors( + GET("/articles/{slug}/comments", { + params: { + path: { slug: params.slug }, + }, + headers: authorization, + }), + ), + }), + ); +}; +``` -/** - * Отфильтрованные таски - * @remark Можно разруливать на уровне эффектов - но тогда нужно подключать дополнительную логику в стор - * > Например скрывать/показывать таск при `toggleTask` событии - */ -export const $tasksFiltered = combine( - $tasksList, - $queryConfig, - (tasksList, config) => { - return tasksList.filter(task => ( - config.completed === undefined || - task.completed === config.completed - ))}, -); +```tsx title="pages/article-read/index.ts" +export { loader } from "./api/loader"; +``` + +Теперь мы можем подключить его к маршруту `/article/:slug`, создав файл маршрута `article.$slug.tsx`: + +```tsx title="app/routes/article.$slug.tsx" +export { loader } from "pages/article-read"; ``` -#### (features) UI-контролы для фильтров {#features-ui-controls-for-filters} +Сама страница состоит из трех основных блоков — заголовка статьи с действиями (повторяется дважды), тела статьи и раздела комментариев. Это разметка страницы, она не особенно интересна: -```tsx title=features/tasks-filters/ui.tsx -// Если чувствуете себя уверенно с @effector/reflect - можете сразу использовать его -// В рамках туториала некритично -import { useStore } from "effector"; -import { Radio } from "antd"; // ~ "shared/ui/radio" +```tsx title="pages/article-read/ui/ArticleReadPage.tsx" +import { useLoaderData } from "@remix-run/react"; -import { taskModel } from "entities/task"; -import { filtersList, getFilterById, DEFAULT_FILTER } from "./config"; +import type { loader } from "../api/loader"; +import { ArticleMeta } from "./ArticleMeta"; +import { Comments } from "./Comments"; -export const const TasksFilters = () => { - const isLoading = useStore($tasksListLoading); +export function ArticleReadPage() { + const { article } = useLoaderData(); return ( - - {filtersList.map(({ title, id }) => ( - taskModel.setQueryConfig(getFilterById(id).config)} - value={id} - disabled={isLoading} - > - {title} - - ))} - +
+
+
+

{article.article.title}

+ + +
+
+ +
+
+
+

{article.article.body}

+
    + {article.article.tagList.map((tag) => ( +
  • + {tag} +
  • + ))} +
+
+
+ +
+ +
+ +
+ +
+ +
+
+
); +} +``` + +Более интересными являются `ArticleMeta` и `Comments`. Они содержат операции записи, такие как лайкнуть статью, оставить комментарий и т. д. Чтобы они заработали, нам сначала нужно реализовать бэкенд-часть. Создайте файл `action.ts` в сегменте `api` этой страницы: + +```tsx title="pages/article-read/api/action.ts" +import { redirect, type ActionFunctionArgs } from "@remix-run/node"; +import { namedAction } from "remix-utils/named-action"; +import { redirectBack } from "remix-utils/redirect-back"; +import invariant from "tiny-invariant"; + +import { DELETE, POST, requireUser } from "shared/api"; + +export const action = async ({ request, params }: ActionFunctionArgs) => { + const currentUser = await requireUser(request); + + const authorization = { Authorization: `Token ${currentUser.token}` }; + + const formData = await request.formData(); + + return namedAction(formData, { + async delete() { + invariant(params.slug, "Expected a slug parameter"); + await DELETE("/articles/{slug}", { + params: { path: { slug: params.slug } }, + headers: authorization, + }); + return redirect("/"); + }, + async favorite() { + invariant(params.slug, "Expected a slug parameter"); + await POST("/articles/{slug}/favorite", { + params: { path: { slug: params.slug } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async unfavorite() { + invariant(params.slug, "Expected a slug parameter"); + await DELETE("/articles/{slug}/favorite", { + params: { path: { slug: params.slug } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async createComment() { + invariant(params.slug, "Expected a slug parameter"); + const comment = formData.get("comment"); + invariant(typeof comment === "string", "Expected a comment parameter"); + await POST("/articles/{slug}/comments", { + params: { path: { slug: params.slug } }, + headers: { ...authorization, "Content-Type": "application/json" }, + body: { comment: { body: comment } }, + }); + return redirectBack(request, { fallback: "/" }); + }, + async deleteComment() { + invariant(params.slug, "Expected a slug parameter"); + const commentId = formData.get("id"); + invariant(typeof commentId === "string", "Expected an id parameter"); + const commentIdNumeric = parseInt(commentId, 10); + invariant( + !Number.isNaN(commentIdNumeric), + "Expected a numeric id parameter", + ); + await DELETE("/articles/{slug}/comments/{id}", { + params: { path: { slug: params.slug, id: commentIdNumeric } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async followAuthor() { + const authorUsername = formData.get("username"); + invariant( + typeof authorUsername === "string", + "Expected a username parameter", + ); + await POST("/profiles/{username}/follow", { + params: { path: { username: authorUsername } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async unfollowAuthor() { + const authorUsername = formData.get("username"); + invariant( + typeof authorUsername === "string", + "Expected a username parameter", + ); + await DELETE("/profiles/{username}/follow", { + params: { path: { username: authorUsername } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + }); }; ``` -#### (pages) Внедряем фильтрацию в страницу {#pages-implementing-filtering-in-the-page} +Реэкспортируйте её из слайса, а затем из маршрута. Пока мы здесь, давайте также подключим саму страницу: -И мы снова реализовали логику, особо не задаваясь вопросами: +```tsx title="pages/article-read/index.ts" +export { ArticleReadPage } from "./ui/ArticleReadPage"; +export { loader } from "./api/loader"; +export { action } from "./api/action"; +``` -- А куда положить логику фильтрации? -- А могут ли эти фильтры переиспользоваться в будущем? -- А могут ли фильтры знать про контекст страницы? +```tsx title="app/routes/article.$slug.tsx" +import { ArticleReadPage } from "pages/article-read"; -Мы просто разделили логику согласно зонам ответственности (слоям) +export { loader, action } from "pages/article-read"; -```tsx title=pages/tasks-list/index.tsx -import { TasksFilters } from "features/tasks-filters"; -... - - ... - - - - +export default ArticleReadPage; ``` -:::note +Теперь, несмотря на то, что мы еще не реализовали кнопку лайка в читалке, кнопка лайка в ленте начнет работать! Это потому, что она тоже отправляет запросы на этот маршрут. Попробуйте лайкнуть что-нибудь. -**К текущему этапу, такое разбиение может показаться излишним - "Почему бы не положить все сразу на уровне страницы / фичи"?** +`ArticleMeta` и `Comments` — это, опять же, просто формы. Мы уже делали это раньше, давайте возьмем их код и пойдем дальше: -Но тогда попробуем задать себе вопросы: +```tsx title="pages/article-read/ui/ArticleMeta.tsx" +import { Form, Link, useLoaderData } from "@remix-run/react"; +import { useContext } from "react"; -- А где гарантии, что сложность страницы не увеличится в будущем настолько, что все аспекты логики сильно будут переплетены? Как при этом без лишних затрат добавлять новую функциональность? -- А где гарантии, что новый человек, пришедший в команду (или даже вы, если на полгода отойдете от проекта) - поймет, что здесь происходит? -- А как построить логику, чтобы не нарушить поток данных / реактивность с другими фичами? -- А что, если эта логика фильтрации настолько сильно прикрепится к контексту страницы, что ее будет невозможно использовать на других страницах? +import { CurrentUser } from "shared/api"; +import type { loader } from "../api/loader"; -Именно поэтому мы и **разбиваем ответственность**, чтобы каждый слой занимался только одной задачей, и чтобы это понимал каждый из разработчиков +export function ArticleMeta() { + const currentUser = useContext(CurrentUser); + const { article } = useLoaderData(); -::: + return ( +
+
+ + + + +
+ + {article.article.author.username} + + {article.article.createdAt} +
+ + {article.article.author.username == currentUser?.username ? ( + <> + + Edit Article + +    + + + ) : ( + <> + + +    + + + )} +
+
+ ); +} +``` -### 2.6 Страница задачи {#26-task-page} +```tsx title="pages/article-read/ui/Comments.tsx" +import { useContext } from "react"; +import { Form, Link, useLoaderData } from "@remix-run/react"; -Аналогичным образом реализуем страницу задачи: +import { CurrentUser } from "shared/api"; +import type { loader } from "../api/loader"; -- Выделяем shared логику -- Выделяем entities логику -- Выделяем features логику -- Выделяем pages логику +export function Comments() { + const { comments } = useLoaderData(); + const currentUser = useContext(CurrentUser); -#### (pages) Страница "Карточка задачи" {#pages-thetask-card-page} + return ( +
+ {currentUser !== null ? ( +
+
+ +
+
+ + +
+
+ ) : ( +
+
+

+ Sign in +   or   + Sign up +   to add comments on this article. +

+
+
+ )} + + {comments.comments.map((comment) => ( +
+
+

{comment.body}

+
+ +
+ + + +   + + {comment.author.username} + + {comment.createdAt} + {comment.author.username === currentUser?.username && ( + +
+ + +
+
+ )} +
+
+ ))} +
+ ); +} +``` -```tsx title=pages/task-details/index.tsx -import { ToggleTask } from "features/toggle-task"; -import { TaskCard, taskModel } from "entities/task"; -import { Layout, Button } from "antd"; // ~ "shared/ui/{...}" -import styles from "./styles.module.scss"; +А вместе с этим и наша читалка статей! Кнопки "Подписаться на автора", "Мне нравится" и "Оставить комментарий" теперь должны работать как положено. -const TaskDetailsPage = (props: Props) => { - const taskId = Number(props.match?.params.taskId); - const task = taskModel.useTask(taskId); - const isLoading = useStore(taskModel.$taskDetailsLoading); +
+ ![Читалка статей с рабочими кнопками подписки и лайка](/img/tutorial/realworld-article-reader.jpg) - /** - * Запрашиваем данные по задаче - * @remark Является плохой практикой в мире effector и представлено здесь - лишь для наглядной демонстрации - * Лучше фетчить через event.pageMounted или reflect - */ - useEffect(() => taskModel.getTaskByIdFx({ taskId }), [taskId]); +
Читалка статей с рабочими кнопками подписки и лайка
+
- // Можно часть логики перенести в entity/task/card (как контейнер) - if (!task && !isLoading) { - return ... - } +### Редактор статей - return ( - - - Back to TasksList} - actions={[ - - ]} - /> - - - ) -}; +Это последняя страница, которую мы рассмотрим в этом руководстве, и самая интересная часть здесь — это то, как мы будем проверять данные формы. + +Сама страница, `article-edit/ui/ArticleEditPage.tsx`, будет довольно простой, дополнительная логика будет скрыта в двух других компонентах: + +```tsx title="pages/article-edit/ui/ArticleEditPage.tsx" +import { Form, useLoaderData } from "@remix-run/react"; + +import type { loader } from "../api/loader"; +import { TagsInput } from "./TagsInput"; +import { FormErrors } from "./FormErrors"; + +export function ArticleEditPage() { + const article = useLoaderData(); + + return ( +
+
+
+
+ + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+
+
+
+
+
+ ); +} ``` -### 2.7 Что дальше? {#27-whats-next} +Эта страница получает текущую статью (если мы пишем статью не с нуля) и заполняет соответствующие поля формы. Мы уже видели это. Интересной частью является `FormErrors`, потому что он будет получать результат проверки и отображать его пользователю. Давайте посмотрим: + +```tsx title="pages/article-edit/ui/FormErrors.tsx" +import { useActionData } from "@remix-run/react"; +import type { action } from "../api/action"; + +export function FormErrors() { + const actionData = useActionData(); + + return actionData?.errors != null ? ( +
    + {actionData.errors.map((error) => ( +
  • {error}
  • + ))} +
+ ) : null; +} +``` + +Здесь мы предполагаем, что наш экшн будет возвращать поле `errors`, массив понятных человеку сообщений об ошибках. К экшну мы перейдем чуть позже. + +Еще один компонент — это поле ввода тегов. Это обычное поле ввода с дополнительным предпросмотром выбранных тегов. Здесь особо не на что смотреть: -А дальше поступают новые задачи, выявляются новые требования +```tsx title="pages/article-edit/ui/TagsInput.tsx" +import { useEffect, useRef, useState } from "react"; -При этом старая кодовая база не требует значительных переработок +export function TagsInput({ + name, + defaultValue, +}: { + name: string; + defaultValue?: Array; +}) { + const [tagListState, setTagListState] = useState(defaultValue ?? []); -#### Появилась функциональность, завязанная на пользователе? {#has-the-functionality-tied-to-the-user-appeared} + function removeTag(tag: string): void { + const newTagList = tagListState.filter((t) => t !== tag); + setTagListState(newTagList); + } -=> Добавляем `entities/user` + const tagsInput = useRef(null); + useEffect(() => { + tagsInput.current && (tagsInput.current.value = tagListState.join(",")); + }, [tagListState]); -#### Понадобилось поменять логику фильтрации? {#did-you-need-to-change-the-filtering-logic} + return ( + <> + + setTagListState(e.target.value.split(",").filter(Boolean)) + } + /> +
+ {tagListState.map((tag) => ( + + + [" ", "Enter"].includes(e.key) && removeTag(tag) + } + onClick={() => removeTag(tag)} + >{" "} + {tag} + + ))} +
+ + ); +} +``` -=> Меняем обработку на `entities` или `pages` уровне, в зависимости от масштабности +Теперь перейдем к API-части. Загрузчик должен посмотреть на URL, и если в нем есть ссылка на статью, это означает, что мы редактируем существующую статью, и ее данные должны быть загружены. В противном случае ничего не возвращается. Давайте создадим этот загрузчик: -#### Нужно добавить больше фичей в карточку задачи, но при этом, чтобы ее можно было использовать по-старому? {#do-you-need-to-add-more-features-to-the-task-card-but-at-the-same-time-so-that-it-can-be-used-in-the-old-way} +```tsx title="pages/article-edit/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; -=> Добавляем фичи и вставляем их в карточку только на нужной **странице** +import { GET, requireUser } from "shared/api"; -#### Какой-то модуль стал слишком сложным для поддержки? {#has-a-module-become-too-complex-to-support} +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; - => Благодаря заложенной архитектуре, мы можем изолированно отрефакторить только этот модуль - без неявных сайд-эффектов для других [(и даже переписать с нуля)](https://youtu.be/BWAeYuWFHhs?t=1625) + if (error !== undefined) { + throw json(error, { status: response.status }); + } -## Итого {#summary} + return data as NonNullable; +} -### Мы научились применять методологию для базовых случаев {#we-have-learned-how-to-apply-the-methodology-for-basic-cases} +export const loader = async ({ params, request }: LoaderFunctionArgs) => { + const currentUser = await requireUser(request); -Понятно, что мир гораздо сложнее, но уже здесь мы зацепились за некоторые спорные моменты и разрешили их таким образом, чтобы проект оставался поддерживаемым и расширяемым. + if (!params.slug) { + return { article: null }; + } -### Мы получили масштабируемую и гибкую кодовую базу {#we-got-a-scalable-and-flexible-codebase} + return throwAnyErrors( + GET("/articles/{slug}", { + params: { path: { slug: params.slug } }, + headers: { Authorization: `Token ${currentUser.token}` }, + }), + ); +}; +``` -1. Переиспользуемые и расширяемые модули +Экшн примет новые значения полей, прогонит их через нашу схему данных и, если все правильно, зафиксирует изменения в бэкенде, либо обновив существующую статью, либо создав новую: - - *shared, features, entities* +```tsx title="pages/article-edit/api/action.ts" +import { json, redirect, type ActionFunctionArgs } from "@remix-run/node"; -1. Равномерное и предсказуемое распределение логики +import { POST, PUT, requireUser } from "shared/api"; +import { parseAsArticle } from "../model/parseAsArticle"; - - *Поскольку композиция у нас идет в одном направлении (вышележащие слои используют нижележащие) - мы можем предсказуемо ее отслеживать и модифицировать, не боясь непредвиденных последствий* +export const action = async ({ request, params }: ActionFunctionArgs) => { + try { + const { body, description, title, tags } = parseAsArticle( + await request.formData(), + ); + const tagList = tags?.split(",") ?? []; + + const currentUser = await requireUser(request); + const payload = { + body: { + article: { + title, + description, + body, + tagList, + }, + }, + headers: { Authorization: `Token ${currentUser.token}` }, + }; + + const { data, error } = await (params.slug + ? PUT("/articles/{slug}", { + params: { path: { slug: params.slug } }, + ...payload, + }) + : POST("/articles", payload)); + + if (error) { + return json({ errors: error }, { status: 422 }); + } -1. Структуру приложения, которая рассказывает о бизнес логике сама за себя + return redirect(`/article/${data.article.slug ?? ""}`); + } catch (errors) { + return json({ errors }, { status: 400 }); + } +}; +``` - - Какие есть страницы? - - `TasksList`, `TaskDetails` - - Какие есть фичи? Что может пользователь? - - `ToggleTask` `TasksFilters` - - Какие есть бизнес-сущности? С чем ведется работа? - - `Task (TaskCard, ...)` - - Что можно переиспользовать из вспомогательного? - - `UIKit (Card, ...)` `API (tasksApi)` +Наша схема данных будет ещё и парсить `FormData`, что позволяет нам удобно получать чистые поля или просто бросать ошибки для обработки в конце. Вот как может выглядеть эта функция парсинга: + +```tsx title="pages/article-edit/model/parseAsArticle.ts" +export function parseAsArticle(data: FormData) { + const errors = []; + + const title = data.get("title"); + if (typeof title !== "string" || title === "") { + errors.push("Give this article a title"); + } + + const description = data.get("description"); + if (typeof description !== "string" || description === "") { + errors.push("Describe what this article is about"); + } + + const body = data.get("body"); + if (typeof body !== "string" || body === "") { + errors.push("Write the article itself"); + } + + const tags = data.get("tags"); + if (typeof tags !== "string") { + errors.push("The tags must be a string"); + } + + if (errors.length > 0) { + throw errors; + } + + return { title, description, body, tags: data.get("tags") ?? "" } as { + title: string; + description: string; + body: string; + tags: string; + }; +} +``` -### Пример {#example} +Возможно, она покажется немного длинной и повторяющейся, но такова цена, которую мы платим за читаемые сообщения об ошибках. Это может быть и схема Zod, например, но тогда нам придется выводить сообщения об ошибках на фронтенде, а эта форма не стоит таких сложностей. -Ниже в [Codesandbox][ext-sandbox] представлен пример получившегося TodoApp, где можно подробно изучить финальную структуру приложения +Последний шаг — подключение страницы, загрузчика и действия к маршрутам. Поскольку мы аккуратно поддерживаем и создание, и редактирование, мы можем экспортировать одно и то же действие как из `editor._index.tsx`, так и из `editor.$slug.tsx`: - +```tsx title="pages/article-edit/index.ts" +export { ArticleEditPage } from "./ui/ArticleEditPage"; +export { loader } from "./api/loader"; +export { action } from "./api/action"; +``` -## См. также {#see-also} +```tsx title="app/routes/editor._index.tsx, app/routes/editor.$slug.tsx (одинаковое содержимое)" +import { ArticleEditPage } from "pages/article-edit"; -- [(Обзор) How to Organize Your React + Redux Codebase][ext-pluralsight] - - Разбор нескольких подходов к структуризации React проектов -- [Гайды и примеры применения методологии (+ Миграция с v1)][refs-guides] -- [Справочный материал по методологии][refs-reference] +export { loader, action } from "pages/article-edit"; -[refs-motivation]: /docs/about/motivation +export default ArticleEditPage; +``` -[refs-needs]: /docs/about/understanding/needs-driven -[refs-public-api]: /docs/reference/public-api +Мы закончили! Войдите в систему и попробуйте создать новую статью. Или “забудьте” написать статью и посмотрите, как сработает валидация. -[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion -[refs-guides]: /docs/guides -[refs-reference]: /docs/reference -[refs-layers]: /docs/reference/layers +
+ ![Редактор статей Conduit, в поле заголовка которого написано “New article”, а остальные поля пусты. Над формой есть две ошибки: “**Describe what this article is about**” и “**Write the article itself**”.](/img/tutorial/realworld-article-editor.jpg) + +
Редактор статей Conduit, в поле заголовка которого написано “New article”, а остальные поля пусты. Над формой есть две ошибки: **“Describe what this article is about”** и **“Write the article itself**”.
+
-[ext-pluralsight]: https://www.pluralsight.com/guides/how-to-organize-your-react-+-redux-codebase -[ext-pluralsight--flat]: https://www.pluralsight.com/guides/how-to-organize-your-react-+-redux-codebase#module-theflatstructure -[ext-sandbox]: https://codesandbox.io/s/github/feature-sliced/examples/tree/master/todo-app -[ext-source-api]: https://github.com/feature-sliced/examples/tree/master/todo-app/src/shared/api +Страницы профиля и настроек очень похожи на страницы чтения и редактирования статей, они оставлены в качестве упражнения для читателя, то есть для вас :) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/auth.md b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/auth.md new file mode 100644 index 0000000000..397b53a9ae --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/auth.md @@ -0,0 +1,224 @@ +--- +sidebar_position: 1 +--- + +# Авторизация + +В общих чертах авторизация состоит из следующих этапов: + +1. Получить учетные данные от пользователя +1. Отправить их на бэкенд +1. Сохранить токен для отправки авторизованных запросов. + +## Как получить учетные данные пользователя + +Мы предполагаем, что ваше приложение само собирает эти данные. Если у вас авторизация через OAuth, вы можете просто создать страницу логина со ссылкой на страницу провайдера OAuth и перейти к [шагу 3](#how-to-store-the-token-for-authenticated-requests). + +### Отдельная страница для логина + +Обычно на сайтах есть отдельные страницы для логина, где вы вводите свое имя пользователя и пароль. Эти страницы довольно просты, поэтому не требуют декомпозиции. Более того, формы логина и регистрации внешне очень похожи, поэтому их можно даже сгруппировать на одной странице. Создайте слайс для вашей страницы логина/регистрации на слое Pages: + +- 📂 pages + - 📂 login + - 📂 ui + - 📄 LoginPage.tsx (или аналог в вашем фреймворке) + - 📄 RegisterPage.tsx + - 📄 index.ts + - остальные страницы… + +Здесь мы создали два компонента и экспортировали их обоих в индексе слайса. Эти компоненты будут содержать формы, которые содержат понятные пользователю элементы для введения их учетных данных. + +### Диалог для логина + +Если в вашем приложении есть диалоговое окно для входа в систему, которое можно использовать на любой странице, вы можете создать для этого диалогового окна виджет. Таким образом, вы все равно сможете не сильно декомпозировать саму форму, но при этом переиспользовать этот диалог на любой странице. + +- 📂 widgets + - 📂 login-dialog + - 📂 ui + - 📄 LoginDialog.tsx + - 📄 index.ts + - остальные виджеты… + +Остальная часть этого руководства написана для первого подхода, где логин делается на отдельной странице, но те же принципы применимы и к виджету диалога. + +### Клиентская валидация + +Иногда, особенно при регистрации, имеет смысл выполнить проверку на стороне клиента, чтобы быстро сообщить пользователю, что они допустили ошибку. Проверка может происходить в сегменте `model` на странице логина. Используйте библиотеку проверки по схемам, например, [Zod][ext-zod] для JS/TS, и предоставьте эту схему сегменту `ui`: + +```ts title="pages/login/model/registration-schema.ts" +import { z } from "zod"; + +export const registrationData = z.object({ + email: z.string().email(), + password: z.string().min(6), + confirmPassword: z.string(), +}).refine((data) => data.password === data.confirmPassword, { + message: "Passwords do not match", + path: ["confirmPassword"], +}); +``` + +Затем в сегменте `ui` вы можете использовать эту схему для проверки ввода пользователя: + +```tsx title="pages/login/ui/RegisterPage.tsx" +import { registrationData } from "../model/registration-schema"; + +function validate(formData: FormData) { + const data = Object.fromEntries(formData.entries()); + try { + registrationData.parse(data); + } catch (error) { + // TODO: Показать пользователю сообщение об ошибке + } +} + +export function RegisterPage() { + return ( +
validate(new FormData(e.target))}> + + + + + + + + +
+ ) +} +``` + +## Как отправить учетные данные на бэкенд + +Создайте функцию, которая отправляет запрос к эндпоинту логина на бэкенде. Эту функцию можно вызвать либо непосредственно в коде компонента через библиотеку мутаций (например, TanStack Query), либо как побочный эффект в стейт-менеджере. + +### Где хранить функцию запроса + +Есть два места, куда можно положить эту функцию: в `shared/api` или в сегмент `api` на странице. + +#### В `shared/api` + +Этот подход хорошо сочетается с тем, чтобы размещать в `shared/api` все функции запросов, и группировать их по эндпоинту, например. Структура файлов в таком случае может выглядеть так: + +- 📂 shared + - 📂 api + - 📂 endpoints + - 📄 login.ts + - остальные функции запросов… + - 📄 client.ts + - 📄 index.ts + +Файл `📄 client.ts` содержит обёртку над примитивом, выполняющим запросы (например, `fetch()`). Эта обёртка знает про base URL вашего бэкенда, проставляет необходимые заголовки, сериализует данные, и т.д. + +```ts title="shared/api/endpoints/login.ts" +import { POST } from "../client"; + +export function login({ email, password }: { email: string, password: string }) { + return POST("/login", { email, password }); +} +``` + +```ts title="shared/api/index.ts" +export { login } from "./endpoints/login"; +``` + +#### В сегменте `api` страницы + +Если вы не храните все свои запросы в одном месте, возможно, вам подойдет разместить эту функцию запроса в сегменте `api` на странице логина. + +- 📂 pages + - 📂 login + - 📂 api + - 📄 login.ts + - 📂 ui + - 📄 LoginPage.tsx + - 📄 index.ts + - остальные страницы… + +```ts title="pages/login/api/login.ts" +import { POST } from "shared/api"; + +export function login({ email, password }: { email: string, password: string }) { + return POST("/login", { email, password }); +} +``` + +Эту функцию даже необязательно реэкспортировать из индекса страницы, потому что, скорее всего, она будет использоваться только внутри этой страницы. + +### Двухфакторная аутентификация + +Если ваше приложение поддерживает двухфакторную аутентификацию (2FA), возможно, вам придется перенаправить пользователя на другую страницу, где они смогут ввести одноразовый пароль. Обычно, ваш запрос `POST /login` возвращает объект пользователя с флагом, указывающим, что у пользователя включен 2FA. Если этот флаг установлен, перенаправьте пользователя на страницу 2FA. + +Поскольку эта страница очень связана с логином, вы также можете положить её в тот же слайс, `login`, на слое Pages. + +Вам также понадобится еще одна функция запроса, похожая на `login()`, которую мы создали выше. Поместите их вместе либо в Shared, либо в сегмент `api` на странице `login`. + +## Как хранить токен для авторизованных запросов {#how-to-store-the-token-for-authenticated-requests} + +Независимо от используемой вами схемы авторизации, будь то простой логин и пароль, OAuth или двухфакторная аутентификация, в конце вы получите токен. Этот токен следует хранить, чтобы последующие запросы могли идентифицировать себя. + +Идеальным хранилищем токенов для веб-приложения являются **cookies** — они не требуют ручного сохранения или обработки токенов. Таким образом, хранение cookies практически не требует усилий со стороны архитектуры фронтенда. Если ваш фронтенд-фреймворк имеет серверную часть (например, [Remix][ext-remix]), то серверную инфраструктуру cookies следует хранить в `shared/api`. В [разделе туториала «Аутентификация»][tutorial-authentication] есть пример того, как это сделать в Remix. + +Однако, иногда хранить токен в cookies — не вариант. В этом случае вам придется хранить токен самим. Помимо этого, вам также может потребоваться написать логику для обновления этого токена по истечении срока его действия. В рамках FSD есть несколько мест, где вы можете хранить токен, а также несколько способов сделать его доступным для остальной части приложения. + +### В Shared + +Этот подход хорошо работает, когда API-клиент определен в `shared/api`, поскольку токен свободно доступен ему для других функций-запросов, которые требуют авторизацию. Вы можете сделать так, чтобы клиент имел свой стейт, либо с помощью реактивного хранилища, либо просто с помощью переменной на уровне модуля. Затем вы можете обновлять этот стейт в ваших функциях `login()`/`logout()`. + +Автоматическое обновление токена может быть реализовано как middleware в API-клиенте — то, что выполняется каждый раз, когда вы делаете какой-либо запрос. Например, можно сделать так: + +- Авторизоваться и сохранить токен доступа, а также токен обновления. +- Сделать любой запрос, требующий авторизации +- Если запрос падает с кодом состояния, указывающим на истечение срока действия токена, а в хранилище есть токен, сделать запрос на обновление, сохранить новые токены и повторить исходный запрос. + +Одним из недостатков этого подхода является то, что логика хранения и обновления токена не имеет выделенного места. Это может подойти каким-то приложениям или командам, но если логика управления токенами более сложна, может захотеться разделить обязанности по отправке запросов и управлению токенами. В этом случае можно положить запросы и API-клиент в `shared/api`, а хранилище токенов и логику обновления — в `shared/auth`. + +Еще одним недостатком этого подхода является то, что если ваш сервер возвращает объект c информацией о вашем текущем пользователе вместе с токеном, вам будет некуда её положить, и придется запросить её снова из специального эндпоинта, например `/me` или `/users/current`. + +### В Entities + +У проектов на FSD часто есть сущность пользователя и/или сущность текущего пользователя. Это даже может быть одна сущность. + +:::note + +**Текущий пользователь** также иногда называется "viewer" или "me". Это делается для того, чтобы различать одного авторизованного пользователя с разрешениями и приватной информацией и всех остальных пользователей с публичной информацией. + +::: + +Чтобы хранить токен в сущности User, создайте реактивное хранилище в сегменте `model`. Это хранилище может содержать одновременно и токен, и объект с информацией о пользователе. + +Поскольку API-клиент обычно размещается в `shared/api` или распределяется между сущностями, главной проблемой этого подхода является обеспечение доступа к токену для других запросов, без нарушения [правил импортов для слоёв][import-rule-on-layers]: + +> Модуль (файл) в слайсе может импортировать другие слайсы только в том случае, если они расположены на слоях строго ниже. + +Есть несколько решений этой проблемы: + +1. **Передавать токен вручную каждый раз, когда делаете запрос** + Это самое простое решение, но оно быстро становится неудобным, и если у вас нет строгой типизации, об этом легко забыть. Это решение также несовместимо с паттерном middleware для API-клиента в Shared. +1. **Открыть доступ к токену для всего приложения через контекст или глобальное хранилище вроде `localStorage`** + Ключ, по которому можно будет получить токен, будет храниться в `shared/api`, чтобы API-клиент мог его использовать. Реактивное хранилище токена будет экспортировано из сущности User, а провайдер контекста (если требуется) будет настроен на слое App. Это дает больше свободы для дизайна API-клиента, но такой подход создаёт неявную зависимость +1. **Вставлять токен в API-клиент каждый раз, когда токен меняется** + Если ваше хранилище реактивное, то можно подписаться на изменения и обновлять токен в API-клиенте каждый раз, когда хранилище в сущности User меняется. Это похоже на прошлое решение тем, что они оба создают неявную зависимость, но это решение более императивное ("push"), тогда как предыдущее — более декларативное ("pull"). + +Решив проблему доступности токена, хранящегося в модели сущности User, вы сможете описать дополнительную бизнес-логику, связанную с управлением токенами. Например, сегмент `model` может содержать логику, которая делает токен недействительным через определенный период времени или обновляет токен по истечении срока его действия. Чтобы совершать запросы на бэкенд для выполнения этих задач, используйте сегмент `api` сущности User или `shared/api`. + +### В Pages/Widgets (не рекомендуется) + +Не рекомендуется хранить состояние, актуальное для всего приложения, как например токен доступа, в страницах или виджетах. Не стоит размещать хранилище токенов в сегменте `model` на странице логина. Вместо этого выберите одно из первых двух решений: Shared или Entities. + +## Логаут и аннулирование токена + +Обычно в приложениях не делают целую отдельную страницу для логаута, но функционал логаута, тем не менее, очень важен. В этот функционал входит авторизованный запрос на бэкенд и обновление хранилища токенов. + +Если вы храните все ваши запросы в `shared/api`, оставьте там функцию для запроса на логаут, рядом с функцией для логина. Если нет, разместите функцию-запрос на логаут рядом с кнопкой, которая её вызывает. Например, если у вас есть виджет хэдера, который есть на каждой странице и содержит ссылку для логаута, поместите этот запрос в сегмент `api` этого виджета. + +Обновление хранилища токенов также должно будет запускаться с места кнопки логаута, как, например, виджет заголовка. Вы можете объединить запрос и обновление хранилища в сегменте `model` этого виджета. + +### Автоматический логаут + +Не забудьте предусмотреть ситуации сбоя запроса на логаут или сбоя запроса на обновление токена. В обоих случаях вам следует очистить хранилище токенов. Если вы храните свой токен в Entities, этот код можно поместить в сегмент `model`, поскольку это чистая бизнес-логика. Если вы храните токен в Shared, размещение этой логики в `shared/api` может раздуть сегмент и размыть его предназначение. Если вы замечаете, что ваш сегмент `api` содержит две несвязанные вещи, рассмотрите возможность выделения логики управления токенами в другой сегмент, например, `shared/auth`. + +[tutorial-authentication]: /docs/get-started/tutorial#authentication +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-remix]: https://remix.run +[ext-zod]: https://zod.dev diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/auth.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/auth.mdx deleted file mode 100644 index 1c1118792f..0000000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/auth.mdx +++ /dev/null @@ -1,401 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Auth - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -Во всех приложениях так или иначе есть бизнес-логика, завязанная **на текущем авторизованном пользователе.** - -> Обычно такая сущность называется `Viewer` / `Principle` / `Session` - но в рамках статьи, остановимся именно на `viewer`, но все зависит от вашего проекта и команды - -Также, это один из показательных примеров, когда бизнес-сущность порождает за собой бизнес-фичи, затем страницы, и даже бизнес-процессы - -Рассмотрим их подробнее ниже с примерами - -:::note - -1. Названия директорий внутри сегментов (ui, model) могут отличаться от проекта к проекту - - *Методология пока никак не регламентирует этот уровень вложенности* - -2. Стоит также понимать, что приведенные ниже примеры - абстрактны и синтетичны, и сформированы для демонстрации только концепций методологии - - *FSD не регламентирует в себе бест-практисы конкретного дата-фетчера или стейт-менеджера* - -::: - -## Entities - -**Бизнес-сущность пользователя** - -- Представляет собой наиболее атомарную абстракцию для проектирования -- Здесь формируется контекст авторизации, на который потом обычно полагаются все вышележащие слои приложения - -:::info - -Стоит понимать, что нередко в приложении есть публичный "внешний" пользователь (user), а есть авторизованный "внутренний" пользователь (viewer) - -*Не забывайте учитывать эту разницу при проектировании архитектуры и логики* - -::: - -### Примеры - -```sh -# Сегменты могут быть как файлами, так и директориями -| -├── entities/viewer # Layer: Бизнес-сущности -| | # Slice: Текущий пользователь -| ├── ui/ # Segment: UI-логика (компоненты) -| ├── lib/ # Segment: Инфраструктурная-логика (helpers/utils) -| ├── model/ # Segment: Бизнес-логика -| └── index.ts # [Декларация Public API] -| ... -``` - -- `entities/viewer` - сущность текущего пользователя *(Session / Principle)* -- `entities/user` - сущность публичного пользователя *(не обязательно связанная с текущим)* - - *В зависимости от сложности приложения - можно использовать и `user` для нейминга текущего пользователя* - - *Но это может вызвать серьезные проблемы, когда/если придется разделять логику обычного пользователя и текущего, который зашел в систему* - -### `index.ts` - -Обычный [Public API модуля][refs-public-api] - -*Во многом повторяет декларацию API и для описанных ниже слоев* - -```ts title=entities/user/ui/index.ts -export { ViewerCard } from "./card"; -export { ViewerAvatar } from "./avatar"; -... -``` - - - - -В редаксе общепринят подход [redux-ducks](https://github.com/erikras/ducks-modular-redux), когда его юниты (selectors/actions/...) лежат рядом и явно выделены - -Но явное выделение далеко не всегда обязательно - -```ts title=entities/user/model/index.ts -export * as selectors from "./selectors"; -export * as events from "./events"; -export * as stores from "./stores"; -... -``` - - - -Модель эффектора чаще всего будет состоять из одного файла - т.к. там принято все юниты хранить рядом - -Если же в модели юниты можно семантично разделить на несколько сабмоделей, то [можно явно это](https://github.com/feature-sliced/examples/pull/1#discussion_r654841332) обозначить в Public API - -```ts title=entities/user/model/index.ts -export * as submodel1 from "./submodel1" -export * as submodel2 from "./submodel2" -... -``` - - - -```ts title=entities/user/index.ts -export * from "./ui" -export * as viewerModel from "./model"; -``` - -### `ui` - -Здесь могут содержаться компоненты, относящиеся не к конкретной странице/фиче, а напрямую к сущности пользователя - -```tsx title=entities/user/ui/card/index.tsx -import { Card } from "shared/ui/card"; - -// Считается хорошей практикой - не связывать напрямую с моделью ui-компоненты из entitites -// Чтобы можно было использовать не только для текущей модели, -// Но и для поступивших извне пропсов - -export type UserCardProps = { - data: User; - className?: string; - // И прочие card-пропсы -}; - -export const UserCard = ({ data, ... }: UserCardProps) => { - return ( - - ) -} -``` - -### `model` - -На этом уровне обычно создается сущность текущего пользователя, с реэкспортом хуков/контрактов/селекторов для использования вышележащими слоями - - - - -```ts -// entities/viewer/model/selectors.ts -export const useViewer = () => { - return useSelector((store) => store.entities.userSlice); -} -export const useAuth = () => { - const viewer = useViewer(); - return !!viewer -} -// entities/viewer/model/store.ts -export const userSlice = createSlice(...) -``` - - - -```ts -// entities/viewer/model/index.ts -export const $viewer = createStore(...); -export const $isAuth = $viewer.map((viewer) => !!viewer); -// **/**/ui.tsx -const viewer = useStore($viewer); -``` - - - - -Также тут может быть реализована и другая логика - -- `updateUserDetails` -- `logoutUser` -- ... - -## Features - -**Фичи, завязанные на текущем пользователе** - -- Использует в реализации бизнес-сущности (зачастую - `entities/viewer`) и shared ресурсы -- Фичи могут не быть напрямую связаны с вьювером, но при этом могут использовать его контекст при реализации логики - -### Примеры - -```sh -# Сегменты могут быть как файлами, так и директориями -| -├── features/auth # Layer: Бизнес-фичи -| | # Slice Group: Структурная группа "Авторизация пользователя" -| ├── by-phone/ # Slice: Фича "Авторизация по телефону" -| | ├── ui/ # Segment: UI-логика (компоненты) -| | ├── lib/ # Segment: Инфраструктурная-логика (helpers/utils) -| | ├── model/ # Segment: Бизнес-логика -| | └── index.ts # [Декларация Public API] -| | -| ├── by-oauth/ # Slice: Фича "Авторизация по внешнему ресурсу" -| ... -``` - -- `features/auth/{by-phone, by-oauth, logout ...}` - **структурная** группа фич авторизации *(по телефону, по внешнему ресурсу, выход из системы, ...)* -- `features/wallet/{add-funds, ...}` - **структурная** группа фич по работе со внутренним счетом пользователя *(пополнение счета, ...)* - -### `ui` - -- Авторизация по внешнему ресурсу - -```tsx title=features/auth/by-oauth/ui.tsx -import { viewerModel } from "entities/viewer"; - -export const AuthByOAuth = () => { - return ( - viewerModel.setUser(user)) - /> - ) -} -``` - -- Использование контекста пользователя в фичах - -```tsx title=features/wallet/ui.tsx -import { viewerModel } from "entities/viewer"; - -export const Wallet = () => { - const viewer = viewerModel.useViewer(); - const { moneyCount } = viewer; - - ... -} -``` - -- Использование компонентов вьювера - -```tsx title=features/header/ui.tsx -import { ViewerAvatar } from "entities/viewer"; -... -export const Header = () => { - ... - return ( - - ... - - - ) -} -``` - -## Pages - -**Страницы, так или иначе связанные с текущим пользователем** - -- Могут как напрямую затрагивать функциональность вьювера -- Так и использовать его косвенно (в том числе - и его контекст / фичи) - -### Примеры - -```sh -# Сегменты могут быть как файлами, так и директориями -| -├── pages/viewer # Layer: Страницы приложения -| | # Slice Group: Структурная группа "Текущий пользователь" -| ├── profile/ # Slice: Страница профиля вьювера -| | ├── ui.tsx # Segment: UI-логика (компоненты) -| | ├── lib.ts # Segment: Инфраструктурная-логика (helpers/utils) -| | ├── model.ts # Segment: Бизнес-логика -| | └── index.ts # [Декларация Public API] -| | -| ├── settings/ # Slice: Страница настроек аккаунта вьювера -| ... -``` - -- `pages/viewer/profile` - страница ЛК пользователя -- `pages/viewer/settings` - страница настроек аккаунта пользователя -- `pages/user` - страница пользователя (не обязательно текущего) -- `pages/auth/{sign-in, sign-up, reset}` - **структурная** группа страниц авторизации *(вход в систему / регистрация / восстановление пароля)* - -### `ui` - -- Использование компонентов вьювера и *viewer-based* фич на страницах - -```tsx title=pages/user/ui.tsx -import { Wallet } from "features/wallet"; -import { ViewerCard } from "entities/viewer"; -... -export const UserPage = () => { - ... - return ( - -
} - /> - ... - - - ) -} -``` - -- Использование модели вьювера - -```tsx title=pages/some/ui.tsx -import { viewerModel } from "entities/viewer"; -... -export const SomePage = () => { - ... - return ( - - ... - viewerModel.saveChanges(payload)} /> - - ) -} -``` - -## Processes - -**Бизнес-процессы, затрагивающие текущего пользователя** - -- Затрагивает юзкейсы, пронизывающие страницы системы -- **Слой процессов - опционален**, и обычно используется *только когда логика разрастается в страницах* и нужно *отдельное управление контекстом* на сразу нескольких страницах - -### Примеры - -```sh -# Сегменты могут быть как файлами, так и директориями -| -├── processes # Layer: Бизнес процессы -| ├── auth/ # Slice: Процесс авторизации пользователя -| | ├── lib.ts # Segment: Инфраструктурная-логика (helpers/utils) -| | ├── model.ts # Segment: Бизнес-логика -| | └── index.ts # [Декларация Public API] -| | -| ├── quick-tour/ # Slice: Процесс онбординга нового пользователя -| ... -``` - -- `processes/auth` - бизнес-процесс авторизации пользователя -- `processes/quick-tour` - бизнес-процесс для ознакомления пользователя с системой *(~ UserOnboard)* - -## App - -**Инициализация данных по учетной записи пользователя** - -- Как правило, здесь проходит проверка на то, **был ли уже авторизован пользователь** до того, как зашел в сервис - - **Если да** - провайдер пополняет контекст пользователя, для дальнейшего использования в системе - - **Если нет** - запускается процесс авторизации или меняется контекст вьювера, чтобы страница авторизации предприняла нужные действия - -### Примеры - -```sh -# Структура `app` уникальна для каждого проекта и не регламентируется методологией -| -├── app/providers # Layer: Инициализация приложения (HOCs провайдеры) -| ├── withAuth.tsx # HOC: Инициализация контекста авторизации -| | ... # -| ... -``` - -- `app/providers/withAuth` - HOC для авторизации пользователя - - Используется **только на верхнем уровне, как провайдер** с инициализацией логики, к которому имеет доступ только *`app`-слой* - - **Не путать с хуком `useViewer`**, к которому идет обращение всех остальных слоев *(processes / pages / features)* - -## Выводы - -Как мы видим на примерах выше - **вся бизнес-логика начинает строится от одной сущности, и может распространится до самого верхнего слоя.** - -Поэтому нужно уметь **грамотно выделять скоуп влияния модуля**, и в зависимости от этого выбирать подходящий слой для расположения логики. - -*Таким образом - мы получим наиболее поддерживаемый, читаемый и переиспользуемый код.* - -## FAQ - - - -### Как прокинуть токен {#how-to-pass-a-token} - -> - -### Login - -> - -### Logout - -> - -## См. также {#see-also} - -- [Дискуссия "Применимость Feature-Sliced Design в бою"](https://github.com/feature-sliced/documentation/discussions/65) (*внутри также есть примеры с viewer*) -- [Вопрос про профиль и сущность пользователя (community-chat)](https://t.me/feature_sliced/342) -- [Пояснение про UIKIT#Card и User#Card (community-chat)](https://t.me/feature_sliced/552) - -[refs-public-api]: /docs/reference/public-api diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/autocompleted.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/autocompleted.mdx index 40545bc4b8..23e2a0bd23 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/autocompleted.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/autocompleted.mdx @@ -1,6 +1,7 @@ --- sidebar_position: 5 sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/browser-api.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/browser-api.mdx index eb7429f180..1666321fab 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/browser-api.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/browser-api.mdx @@ -1,5 +1,6 @@ --- sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/cms.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/cms.mdx index 6174b34284..c026ca19c5 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/cms.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/cms.mdx @@ -1,5 +1,6 @@ --- sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/feedback.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/feedback.mdx index c291f06351..6ec8c3faf0 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/feedback.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/feedback.mdx @@ -1,5 +1,6 @@ --- sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' @@ -8,4 +9,4 @@ import WIP from '@site/src/shared/ui/wip/tmpl.mdx' -> Errors, Alerts, Notifications, ... \ No newline at end of file +> Errors, Alerts, Notifications, ... diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/i18n.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/i18n.mdx index f36a706ab1..fe95010973 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/i18n.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/i18n.mdx @@ -1,6 +1,7 @@ --- sidebar_position: 6 sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/index.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/index.mdx index f88be08e33..fe9a693fff 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/index.mdx @@ -18,18 +18,19 @@ import { UserSwitchOutlined, LayoutOutlined, FontSizeOutlined } from "@ant-desig description="Декомпозиция логики авторизации" to="/docs/guides/examples/auth" Icon={UserSwitchOutlined} - tags={["Viewer", "AuthForm", "ProfilePage", "AuthProvider", "AuthToken"]} -/> - + diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/metric.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/metric.mdx index e3e7425f9b..03bef7eece 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/metric.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/metric.mdx @@ -1,5 +1,6 @@ --- sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/monorepo.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/monorepo.mdx index d21e4d5d00..7527071815 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/monorepo.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/monorepo.mdx @@ -1,6 +1,7 @@ --- sidebar_position: 9 sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md new file mode 100644 index 0000000000..72cfb80093 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md @@ -0,0 +1,104 @@ +--- +sidebar_position: 3 +--- + +# Лейауты страниц + +Это руководство рассматривает абстракцию _лейаута страницы_ — когда несколько страниц имеют одинаковую структуру, отличаясь только основным содержимым. + +:::info + +Вашего вопроса нет в этом руководстве? Напишите свой вопрос, оставив отзыв к этой статье (синяя кнопка справа), и мы рассмотрим возможность расширения этого руководства! + +::: + +## Простой лейаут + +Самый простой лейаут можно увидеть прямо на этой странице. Он имеет хэдер с навигацией по сайту, два сайдбара и футер с внешними ссылками. Здесь нет сложной бизнес-логики, и единственные динамические части — это сайдбары и переключатели справа в хэдере. Такой лейаут можно разместить целиком в `shared/ui` или в `app/layouts`, с заполнением контента сайдбаров через пропы: + +```tsx title="shared/ui/layout/Layout.tsx" +import { Link, Outlet } from "react-router-dom"; +import { useThemeSwitcher } from "./useThemeSwitcher"; + +export function Layout({ siblingPages, headings }) { + const [theme, toggleTheme] = useThemeSwitcher(); + + return ( +
+
+ + +
+
+ + {/* Здесь будет основное содержимое страницы */} + +
+
+
    +
  • GitHub
  • +
  • Twitter
  • +
+
+
+ ); +} +``` + +```ts title="shared/ui/layout/useThemeSwitcher.ts" +export function useThemeSwitcher() { + const [theme, setTheme] = useState("light"); + + function toggleTheme() { + setTheme(theme === "light" ? "dark" : "light"); + } + + useEffect(() => { + document.body.classList.remove("light", "dark"); + document.body.classList.add(theme); + }, [theme]); + + return [theme, toggleTheme] as const; +} +``` + +Код сайдбаров оставлен читателю в качестве упражнения 😉. + +## Использование виджетов в лейауте + +Иногда есть необходимость включить в лейаут определенную бизнес-логику, особенно если вы используете глубоко вложенные маршруты с роутером типа [React Router][ext-react-router]. Тогда вы не можете хранить лейаут в Shared или в Widgets из-за [правила импорта для слоёв][import-rule-on-layers]: + +> Модуль в слайсе может импортировать другие слайсы только в том случае, если они расположены на слоях строго ниже. + +Прежде чем обсуждать решения, нам нужно обсудить, действительно ли это проблема. Вам _действительно нужен_ этот лейаут, и если да, _действительно ли_ он должен быть виджетом? Если блок бизнес-логики, про который идёт речь, используется на 2-3 страницах, и лейаут просто является небольшой обёрткой для этого виджета, рассмотрите один из этих двух вариантов: + +1. **Напишите ваш лейаут прямо в коде роутера на уровне App** + Это отлично подходит для роутеров, поддерживающих вложенность, потому что вы можете группировать определенные маршруты и применять нужный лейаут только к ним. + +2. **Просто скопируйте его** + Желание абстрагировать код часто переоценено. Это особенно верно для лейаутов, потому что они редко меняются. В какой-то момент, если одна из этих страниц потребует изменений, вы можете просто внести изменения, не затрагивая другие страницы. Если вы беспокоитесь, что кто-то может забыть обновить другие страницы, всегда можно оставить комментарий, описывающий отношения между страницами. + +Если ни один из вышеперечисленных вариантов не подходит, есть два решения для включения виджета в лейаут: + +1. **Используйте render props или слоты** + Большинство фреймворков позволяют передавать часть UI внешне. В React это называется [render props][ext-render-props], в Vue — [слоты][ext-vue-slots]. + +2. **Переместите лейаут на уровень App** + Вы также можете хранить свой лейаут на уровне App, например, в `app/layouts`, и комбинировать любые виджеты, которые вам нужны. + +## Дополнительные материалы + +- Пример создания лейаута с аутентификацией с помощью React и Remix (аналогичен React Router) можно найти в [туториале][tutorial]. + +[tutorial]: /docs/get-started/tutorial +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-react-router]: https://reactrouter.com/ +[ext-render-props]: https://www.patterns.dev/react/render-props-pattern/ +[ext-vue-slots]: https://ru.vuejs.org/guide/components/slots + diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/page-layout.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/page-layout.mdx deleted file mode 100644 index dd6457a0f1..0000000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/page-layout.mdx +++ /dev/null @@ -1,28 +0,0 @@ ---- -sidebar_position: 2 -sidebar_class_name: sidebar-item--wip ---- - -import WIP from '@site/src/shared/ui/wip/tmpl.mdx' - -# PageLayout - - - -## Header - -> https://t.me/feature_sliced/2978 - -## Sidebar - -> - https://t.me/feature_sliced/4046 -> - https://t.me/feature_sliced/4090 - -> https://t.me/feature_sliced/3475 - -> https://t.me/feature_sliced/3425 - -## См. также {#see-also} - -- [(DiscussionTalk) О PageLayout с применением FSD](https://youtu.be/b_nBvHWqxP8?t=4452) - diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/platforms.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/platforms.mdx index 7985c6c002..cb309b99aa 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/platforms.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/platforms.mdx @@ -1,5 +1,6 @@ --- sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/ssr.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/ssr.mdx index aaae9e565b..ab9872549f 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/ssr.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/ssr.mdx @@ -1,5 +1,6 @@ --- sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/theme.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/theme.mdx index 3ffe813abd..36f3dfeb32 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/theme.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/theme.mdx @@ -1,6 +1,7 @@ --- sidebar_position: 4 sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/types.md b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/types.md new file mode 100644 index 0000000000..5108e959e3 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/types.md @@ -0,0 +1,434 @@ +--- +sidebar_position: 2 +--- + +# Типы + +В этом руководстве рассматриваются типы данных из типизированных языков, таких как TypeScript, и где они вписываются в FSD. + +:::info + +Вашего вопроса нет в этом руководстве? Напишите свой вопрос, оставив отзыв к этой статье (синяя кнопка справа), и мы рассмотрим возможность расширения этого руководства! + +::: + +## Типы-утилиты + +Типы-утилиты — это типы, которые сами по себе не имеют особого смысла и обычно используются с другими типами. Например: + +
+ +```ts +type ArrayValues = T[number]; +``` + +
+ Источник: https://github.com/sindresorhus/type-fest/blob/main/source/array-values.d.ts +
+ +
+ +Чтобы добавить типы-утилиты в ваш проект, установите библиотеку, например [`type-fest`][ext-type-fest], или создайте свою собственную библиотеку в `shared/lib`. Обязательно четко укажите, какие новые типы _можно_ добавлять в эту библиотеку, а какие — _нельзя_. Например, назовите ее `shared/lib/utility-types` и добавьте внутрь файл README, описывающий, что такое типы-утилиты в понимании вашей команды. + +Не переоценивайте потенциал переиспользования типов-утилит. То, что их _можно_ использовать повторно, не означает, что так и будет, и поэтому не каждый тип-утилита должен быть в Shared. Некоторые типы-утилиты должны лежать прямо там, где они нужны: + +- 📂 pages + - 📂 home + - 📂 api + - 📄 ArrayValues.ts (тип-утилита) + - 📄 getMemoryUsageMetrics.ts (код, который будет использовать эту утилиту) + +:::warning + +Не поддавайтесь искушению создать папку `shared/types` или добавить сегмент `types` в ваши слайсы. Категория "типы" похожа на категорию "компоненты" или "хуки" в том, что она описывает содержимое, а не то, для чего оно нужно. Сегменты должны описывать цель кода, а не его суть. + +::: + +## Бизнес-сущности и их ссылки друг на друга + +Одними из наиболее важных типов в приложении являются типы бизнес-сущностей, т. е. реальных вещей, с которыми работает ваше приложение. Например, в приложении сервиса онлайн-музыки у вас могут быть бизнес-сущности _Песня_ (song), _Альбом_ (album) и т. д. + +Бизнес-сущности часто приходят с бэкенда, поэтому первым шагом является типизация ответов бэкенда. Удобно иметь функцию запроса к каждому эндпоинту и типизировать результат вызова этой функции. Для дополнительной безопасности типов вы можете пропустить результат через библиотеку проверки по схемам, например [Zod][ext-zod]. + +Например, если вы храните все свои запросы в Shared, вы можете сделать так: + +```ts title="shared/api/songs.ts" +import type { Artist } from "./artists"; + +interface Song { + id: number; + title: string; + artists: Array; +} + +export function listSongs() { + return fetch('/api/songs').then((res) => res.json() as Promise>); +} +``` + +Вы могли заметить, что тип `Song` ссылается на другую сущность, `Artist`. Это преимущество хранения ваших запросов в Shared — реальные типы часто ссылаются друг на друга. Если бы мы положили эту функцию в `entities/song/api`, мы бы не смогли просто импортировать `Artist` из `entities/artist`, потому что FSD ограничивает кросс-импорт между слайсами через [правило импорта для слоёв][import-rule-on-layers]: + +> Модуль в слайсе может импортировать другие слайсы только в том случае, если они расположены на слоях строго ниже. + +Есть два способа решения этой проблемы: + +1. **Параметризируйте типы** + Вы можете сделать так, чтоб ваши типы принимали типовые аргументы в качестве слотов для соединения с другими сущностями, и даже накладывать ограничения на эти слоты. Например: + + ```ts title="entities/song/model/song.ts" + interface Song { + id: number; + title: string; + artists: Array; + } + ``` + + Это хорошо работает для некоторых типов, и иногда хуже работает для других. Простой тип, такой как `Cart = { items: Array }`, можно легко заставить работать с любым типом продукта. Более связанные типы, такие как `Country` и `City`, может быть не так легко разделить. + +2. **Кросс-импортируйте (но только правильно)** + Чтоб сделать кросс-импорт между сущностями в FSD, вы можете использовать отдельный публичный API специально для каждого слайса, который будет кросс-импортировать. Например, если у нас есть сущности `song` (песня), `artist` (исполнитель), и `playlist` (плейлист), и последние две должны ссылаться на `song`, мы можем создать два специальных публичных API для них обоих в сущности `song` через `@x`-нотацию: + + - 📂 entities + - 📂 song + - 📂 @x + - 📄 artist.ts (публичный API, из которого будет импортировать сущность `artist`) + - 📄 playlist.ts (публичный API, из которого будет импортировать сущность `playlist`) + - 📄 index.ts (обыкновенный публичный API) + + Содержимое файла `📄 entities/song/@x/artist.ts` похоже на `📄 entities/song/index.ts`: + + + ```ts title="entities/song/@x/artist.ts" + export type { Song } from "../model/song.ts"; + ``` + + Затем `📄 entities/artist/model/artist.ts` может импортировать `Song` следующим образом: + + ```ts title="entities/artist/model/artist.ts" + import type { Song } from "entities/song/@x/artist"; + + export interface Artist { + name: string; + songs: Array; + } + ``` + + С помощью явных связей между сущностями мы получаем точный контроль взаимозависимостей и при этом поддерживаем достаточный уровень разделения доменов. + +## Объекты передачи данных (DTO) и мапперы {#data-transfer-objects-and-mappers} + +Объекты передачи данных, или DTO (от англ. _data transfer object_), — это термин, описывающий форму данных, которые поступают из бэкенда. Иногда DTO можно использовать как есть, но иногда их формат неудобен для фронтенда. Тут приходят на помощь мапперы — это функции, которые преобразуют DTO в более удобную форму. + +### Куда положить DTO + +Если ваши типы бэкенда находятся в отдельном пакете (например, если вы делите код между фронтендом и бэкендом), просто импортируйте ваши DTO оттуда, и готово! Если вы не делите код между бэкендом и фронтендом, вам нужно хранить DTO где-то в вашем фронтенд-коде, и мы рассмотрим этот случай ниже. + +Если вы храните функции запросов в `shared/api`, то именно там должны быть DTO, прямо рядом с функцией, которая их использует: + +```ts title="shared/api/songs.ts" +import type { ArtistDTO } from "./artists"; + +interface SongDTO { + id: number; + title: string; + artist_ids: Array; +} + +export function listSongs() { + return fetch('/api/songs').then((res) => res.json() as Promise>); +} +``` + +Как упоминалось в предыдущем разделе, хранение ваших запросов и DTO в Shared имеет преимущество того, что вы можете ссылаться на другие DTO. + +### Куда положить мапперы + +Мапперы — это функции, которые принимают DTO для преобразования, и, следовательно, они должны находиться рядом с определением DTO. На практике это означает, что если ваши запросы и DTO определены в `shared/api`, то и мапперы должны быть там же: + +```ts title="shared/api/songs.ts" +import type { ArtistDTO } from "./artists"; + +interface SongDTO { + id: number; + title: string; + disc_no: number; + artist_ids: Array; +} + +interface Song { + id: string; + title: string; + /** The full title of the song, including the disc number. */ + fullTitle: string; + artistIds: Array; +} + +function adaptSongDTO(dto: SongDTO): Song { + return { + id: String(dto.id), + title: dto.title, + fullTitle: `${dto.disc_no} / ${dto.title}`, + artistIds: dto.artist_ids.map(String), + }; +} + +export function listSongs() { + return fetch('/api/songs').then(async (res) => (await res.json()).map(adaptSongDTO)); +} +``` + +Если ваши запросы и хранилища определены в слайсах сущностей, то весь этот код должен быть там, с учётом ограничения кросс-импортов между сущностями: + +```ts title="entities/song/api/dto.ts" +import type { ArtistDTO } from "entities/artist/@x/song"; + +export interface SongDTO { + id: number; + title: string; + disc_no: number; + artist_ids: Array; +} +``` + +```ts title="entities/song/api/mapper.ts" +import type { SongDTO } from "./dto"; + +export interface Song { + id: string; + title: string; + /** Полное название песни, включая номер диска. */ + fullTitle: string; + artistIds: Array; +} + +export function adaptSongDTO(dto: SongDTO): Song { + return { + id: String(dto.id), + title: dto.title, + fullTitle: `${dto.disc_no} / ${dto.title}`, + artistIds: dto.artist_ids.map(String), + }; +} +``` + +```ts title="entities/song/api/listSongs.ts" +import { adaptSongDTO } from "./mapper"; + +export function listSongs() { + return fetch('/api/songs').then(async (res) => (await res.json()).map(adaptSongDTO)); +} +``` + +```ts title="entities/song/model/songs.ts" +import { createSlice, createEntityAdapter } from "@reduxjs/toolkit"; + +import { listSongs } from "../api/listSongs"; + +export const fetchSongs = createAsyncThunk('songs/fetchSongs', listSongs); + +const songAdapter = createEntityAdapter(); +const songsSlice = createSlice({ + name: "songs", + initialState: songAdapter.getInitialState(), + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchSongs.fulfilled, (state, action) => { + songAdapter.upsertMany(state, action.payload); + }) + }, +}); +``` + +### Что делать с вложенными DTO + +Самый проблемный момент — это когда ответ от бэкенда содержит несколько сущностей. Например, если песня включает в себя не только ID авторов, но и сами объекты данных об авторах целиком. В этом случае сущности не могут не знать друг о друге (если только мы не хотим выбрасывать данные или проводить серьезную беседу с командой бэкенда). Вместо того, чтобы придумывать решения для неявных связей между срезами (например, общий middleware, который будет диспатчить действия другим слайсам), предпочитайте явный кросс-импорт через `@x`-нотацию. Вот как мы можем это реализовать с Redux Toolkit: + + +```ts title="entities/song/model/songs.ts" +import { + createSlice, + createEntityAdapter, + createAsyncThunk, + createSelector, +} from '@reduxjs/toolkit' +import { normalize, schema } from 'normalizr' + +import { getSong } from "../api/getSong"; + +// Объявляем схемы сущностей в normalizr +export const artistEntity = new schema.Entity('artists') +export const songEntity = new schema.Entity('songs', { + artists: [artistEntity], +}) + +const songAdapter = createEntityAdapter() + +export const fetchSong = createAsyncThunk( + 'songs/fetchSong', + async (id: string) => { + const data = await getSong(id) + // Нормализуем данные, чтобы редьюсеры могли загружать предсказуемый объект, например: + // `action.payload = { songs: {}, artists: {} }` + const normalized = normalize(data, songEntity) + return normalized.entities + } +) + +export const slice = createSlice({ + name: 'songs', + initialState: songAdapter.getInitialState(), + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchSong.fulfilled, (state, action) => { + songAdapter.upsertMany(state, action.payload.songs) + }) + }, +}) + +const reducer = slice.reducer +export default reducer +``` + +```ts title="entities/song/@x/artist.ts" +export { fetchSong } from "../model/songs"; +``` + +```ts title="entities/artist/model/artists.ts" +import { createSlice, createEntityAdapter } from '@reduxjs/toolkit' + +import { fetchSong } from 'entities/song/@x/artist' + +const artistAdapter = createEntityAdapter() + +export const slice = createSlice({ + name: 'users', + initialState: artistAdapter.getInitialState(), + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchSong.fulfilled, (state, action) => { + // И здесь обрабатываем тот же ответ с бэкенда, добавляя исполнителей + artistAdapter.upsertMany(state, action.payload.artists) + }) + }, +}) + +const reducer = slice.reducer +export default reducer +``` + +Это немного ограничивает преимущества изоляции слайсов, но чётко обозначает связь между этими двумя сущностями, которую мы не контролируем. Если эти сущности когда-либо будут рефакториться, их нужно будет рефакторить вместе. + +## Глобальные типы и Redux + +Глобальные типы — это типы, которые будут использоваться во всем приложении. Существует два вида глобальных типов, в зависимости от того, что им нужно знать: +1. Универсальные типы, которые не имеют никакой специфики приложения +2. Типы, которым нужно знать обо всем приложении + +Первый случай легко решить — поместите свои типы в Shared, в соответствующий сегмент. Например, если у вас есть интерфейс глобальной переменной для аналитики, вы можете поместить его в `shared/analytics`. + +:::warning + +Избегайте создания папки `shared/types`. Она группирует несвязанные вещи только на основе свойства «быть типом», и это свойство обычно бесполезно при поиске кода в проекте. + +::: + +Второй случай часто встречается в проектах с Redux без RTK. Ваш окончательный тип хранилища доступен только после того, как вы соедините все редьюсеры, но этот тип хранилища нужен селекторам, которые вы используете в приложении. Например, вот типичное определение хранилища в Redux: + +```ts title="app/store/index.ts" +import { combineReducers, rootReducer } from "redux"; + +import { songReducer } from "entities/song"; +import { artistReducer } from "entities/artist"; + +const rootReducer = combineReducers(songReducer, artistReducer); + +const store = createStore(rootReducer); + +type RootState = ReturnType; +type AppDispatch = typeof store.dispatch; +``` + +Было бы неплохо иметь типизированные хуки `useAppDispatch` и `useAppSelector` в `shared/store`, но они не могут импортировать `RootState` и `AppDispatch` из слоя App из-за [правила импорта для слоёв][import-rule-on-layers]: + +> Модуль в слайсе может импортировать другие слайсы только в том случае, если они расположены на слоях строго ниже. + +Рекомендуемое решение в этом случае — создать неявную зависимость между слоями Shared и App. Эти два типа, `RootState` и `AppDispatch`, вряд ли изменятся, и они будут знакомы разработчикам на Redux, поэтому неявная связь вряд ли станет проблемой. + +В TypeScript это можно сделать, объявив типы как глобальные, например так: + +```ts title="app/store/index.ts" +/* то же содержимое, что и в блоке кода до этого… */ + +declare type RootState = ReturnType; +declare type AppDispatch = typeof store.dispatch; +``` + +```ts title="shared/store/index.ts" +import { useDispatch, useSelector, type TypedUseSelectorHook } from "react-redux"; + +export const useAppDispatch = useDispatch.withTypes() +export const useAppSelector: TypedUseSelectorHook = useSelector; +``` + +## Схемы валидации типов и Zod + +Если вы хотите проверить, что ваши данные соответствуют определенной форме или ограничениям, вы можете создать схему валидации. В TypeScript популярной библиотекой для этой задачи является [Zod][ext-zod]. Схемы валидации также должны быть размещены рядом с кодом, который их использует, насколько это возможно. + +Схемы валидации похожи на мапперы (как обсуждалось в разделе [Объекты передачи данных (DTO) и мапперы](#data-transfer-objects-and-mappers)) в том смысле, что они принимают объект передачи данных и парсят его, выдавая ошибку, если парсинг не удался. + +Один из наиболее распространенных случаев валидации — это данные, поступающие с бэкенда. Обычно вы хотите пометить запрос как неудавшийся, если данные не соответствуют схеме, поэтому имеет смысл поместить схему в том же месте, что и функция запроса, что обычно является сегментом `api`. + +Если ваши данные поступают через пользовательский ввод, например, через форму, валидация должна происходить во время ввода данных. Вы можете разместить свою схему в сегменте `ui`, рядом с компонентом формы, или в сегменте `model`, если сегмент `ui` слишком перегружен. + +## Типизация пропов компонентов и контекста + +В целом, лучше хранить интерфейс пропов или контекста в том же файле, что и компонент или контекст, который их использует. Если у вас фреймворк с однофайловыми компонентами, например, Vue или Svelte, и вы не можете определить интерфейс пропов в том же файле, или вы хотите переиспользовать этот интерфейс между несколькими компонентами, создайте отдельный файл в той же папке, обычно в сегменте `ui`. + +Вот пример с JSX (React или Solid): + +```ts title="pages/home/ui/RecentActions.tsx" +interface RecentActionsProps { + actions: Array<{ id: string; text: string }>; +} + +export function RecentActions({ actions }: RecentActionsProps) { + /* … */ +} +``` + +И вот пример с интерфейсом, хранящимся в отдельном файле, для Vue: + +```ts title="pages/home/ui/RecentActionsProps.ts" +export interface RecentActionsProps { + actions: Array<{ id: string; text: string }>; +} +``` + +```html title="pages/home/ui/RecentActions.vue" + +``` + +## Декларационные файлы окружения (`*.d.ts`) + +Некоторые пакеты, например, [Vite][ext-vite] или [ts-reset][ext-ts-reset], требуют декларационные файлы окружения для работы в вашем приложении. Обычно они небольшие и несложные, поэтому часто не требуют какой-либо архитектуры, их можно просто поместить в папку `src/`. Чтобы `src` был более организованным, вы можете хранить их на слое App, в `app/ambient/`. + +Другие пакеты просто не имеют типов, и вам может понадобиться объявить их как нетипизированные или даже написать собственные типы для них. Хорошим местом для этих типов будет `shared/lib`, в папке типа `shared/lib/untyped-packages`. Создайте там файл `%LIBRARY_NAME%.d.ts` и объявите типы, которые вам нужны: + +```ts title="shared/lib/untyped-packages/use-react-screenshot.d.ts" +// У этой библиотеки нет типов, и мы не хотели заморачиваться с написанием своих. +declare module "use-react-screenshot"; +``` + +## Автогенерация типов + +Часто бывает полезно генерировать типы из внешних источников, например, генерировать типы бэкенда из схемы OpenAPI. В этом случае создайте специальное место в вашем коде для этих типов, например, `shared/api/openapi`. Идеально, если вы также включите README в эту папку, который описывает, что это за файлы, как их перегенерировать и т. д. + +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-type-fest]: https://github.com/sindresorhus/type-fest +[ext-zod]: https://zod.dev +[ext-vite]: https://vitejs.dev +[ext-ts-reset]: https://www.totaltypescript.com/ts-reset diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/types.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/types.mdx deleted file mode 100644 index 28b1edc43b..0000000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/types.mdx +++ /dev/null @@ -1,30 +0,0 @@ ---- -sidebar_position: 3 -sidebar_class_name: sidebar-item--wip ---- - -import WIP from '@site/src/shared/ui/wip/tmpl.mdx' - -# Типы - - - -> **Типы бывают разные** -> -> Это может быть как служебный type (`Maybe`), так и по предметной области (Tls, Contract, Camera, ...) -> -> 1) То что относится к предметной области обычно лучше класть в shared/api (особенно когда есть кодогенерация в одно место) -> (по entities такое распылять не очень выйдет) -> -> 2) Общие служебные типы, которые можно задекларировать - в тот же react-app-env.d.ts -> -> 3) А если у тебя служебные типы и надо импортировать прям везде - то некритично особо, можно и в shared, можно и рядом с react-app.env.dts положить -> -> https://t.me/feature_sliced/3879 -> -> 4) Нормализованные типы для сущностей имеет смысл хранить в entities -> -> https://t.me/feature_sliced/4513 - - -> https://t.me/feature_sliced/3877 \ No newline at end of file diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx index e1a64a539c..33ec366f58 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx @@ -1,6 +1,7 @@ --- sidebar_position: 8 sidebar_class_name: sidebar-item--wip +unlisted: true --- import WIP from '@site/src/shared/ui/wip/tmpl.mdx' diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/index.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/index.mdx index 74b124da46..f720736132 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/index.mdx @@ -25,10 +25,10 @@ import { ToolOutlined, ImportOutlined, BugOutlined, FunctionOutlined } from "@an /> + 📁 src +
    +
  • +
    + 📁 actions +
      +
    • 📁 product
    • +
    • 📁 order
    • +
    +
    +
  • +
  • 📁 api
  • +
  • 📁 components
  • +
  • 📁 containers
  • +
  • 📁 constants
  • +
  • 📁 i18n
  • +
  • 📁 modules
  • +
  • 📁 helpers
  • +
  • +
    + 📁 routes +
      +
    • 📁 products.jsx
    • +
    • 📄 products.[id].jsx
    • +
    +
    +
  • +
  • 📁 utils
  • +
  • 📁 reducers
  • +
  • 📁 selectors
  • +
  • 📁 styles
  • +
  • 📄 App.jsx
  • +
  • 📄 index.js
  • +
+ + +## Перед началом {#before-you-start} + +Самый важный вопрос, который нужно задать своей команде при рассмотрении перехода на Feature-Sliced Design, — _действительно ли вам это нужно?_ Мы любим Feature-Sliced Design, но даже мы признаем, что некоторые проекты прекрасно обойдутся и без него. + +Вот несколько причин, по которым стоит рассмотреть переход: + +1. Новые члены команды жалуются, что сложно достичь продуктивного уровня +2. Внесение изменений в одну часть кода **часто** приводит к тому, что ломается другая несвязанная часть +3. Добавление новой функциональности затруднено из-за огромного количества вещей, о которых нужно думать + +**Избегайте перехода на FSD против воли ваших коллег**, даже если вы являетесь тимлидом. +Сначала убедите своих коллег в том, что преимущества перевешивают стоимость миграции и стоимость изучения новой архитектуры вместо установленной. + +Также имейте в виду, что любые изменения в архитектуре незаметны для руководства в моменте. Убедитесь, что они поддерживают переход, прежде чем начинать, и объясните им, как этот переход может быть полезен для проекта. + +:::tip + +Если вам нужна помощь в убеждении менеджера проекта в том, что FSD вам полезен, вот несколько идей: + +1. Миграция на FSD может происходить постепенно, поэтому она не остановит разработку новых функций +2. Хорошая архитектура может значительно сократить время, которое потребуется новым разработчикам для достижения производительности +3. FSD — это документированная архитектура, поэтому команде не нужно постоянно тратить время на поддержание собственной документации + +::: + +--- + +Если вы всё-таки приняли решение начать миграцию, то первое, что вам следует сделать, — настроить алиас для `📁 src`. Это будет полезно позже, чтоб ссылаться на папки верхнего уровня. Далее в тексте мы будем считать `@` псевдонимом для `./src`. + +## Шаг 1. Разделите код по страницам {#divide-code-by-pages} + +Большинство кастомных архитектур уже имеют разделение по страницам, независимо от размера логики. Если у вас уже есть `📁 pages`, вы можете пропустить этот шаг. + +Если у вас есть только `📁 routes`, создайте `📁 pages` и попробуйте переместить как можно больше кода компонентов из `📁 routes`. Идеально, если у вас будет маленький файл роута и больший файл страницы. При перемещении кода создайте папку для каждой страницы и добавьте в нее индекс-файл: + +:::note + +Пока что ваши страницы могут импортировать друг из друга, это нормально. Позже будет отдельный шаг для устранения этих зависимостей, но сейчас сосредоточьтесь на установлении явного разделения по страницам. + +::: + +Файл роута: + +```js title="src/routes/products.[id].js" +export { ProductPage as default } from "@/pages/product" +``` + +Индекс-файл страницы: + +```js title="src/pages/product/index.js" +export { ProductPage } from "./ProductPage.jsx" +``` + +Файл с компонентом страницы: + +```jsx title="src/pages/product/ProductPage.jsx" +export function ProductPage(props) { + return
; +} +``` + +## Шаг 2. Отделите все остальное от страниц {#separate-everything-else-from-pages} + +Создайте папку `📁 src/shared` и переместите туда все, что не импортируется из `📁 pages` или `📁 routes`. Создайте папку `📁 src/app` и переместите туда все, что импортирует страницы или роуты, включая сами роуты. + +Помните, что у слоя Shared нет слайсов, поэтому сегменты могут импортировать друг из друга. + +В итоге у вас должна получиться структура файлов, похожая на эту: + +
+ 📁 src +
    +
  • +
    + 📁 app +
      +
    • +
      + 📁 routes +
        +
      • 📄 products.jsx
      • +
      • 📄 products.[id].jsx
      • +
      +
      +
    • +
    • 📄 App.jsx
    • +
    • 📄 index.js
    • +
    +
    +
  • +
  • +
    + 📁 pages +
      +
    • +
      + 📁 product +
        +
      • +
        + 📁 ui +
          +
        • 📄 ProductPage.jsx
        • +
        +
        +
      • +
      • 📄 index.js
      • +
      +
      +
    • +
    • 📁 catalog
    • +
    +
    +
  • +
  • +
    + 📁 shared +
      +
    • 📁 actions
    • +
    • 📁 api
    • +
    • 📁 components
    • +
    • 📁 containers
    • +
    • 📁 constants
    • +
    • 📁 i18n
    • +
    • 📁 modules
    • +
    • 📁 helpers
    • +
    • 📁 utils
    • +
    • 📁 reducers
    • +
    • 📁 selectors
    • +
    • 📁 styles
    • +
    +
    +
  • +
+
+ +## Шаг 3. Устраните кросс-импорты между страницами {#tackle-cross-imports-between-pages} + +Найдите все случаи, когда одна страница импортирует что-то из другой, и сделайте одно из двух: + +1. Скопируйте код, который импортируется, в зависимую страницу, чтобы убрать зависимость +2. Переместите код в соответствующий сегмент в Shared: + - если это часть UI-кита, переместите в `📁 shared/ui`; + - если это константа конфигурации, переместите в `📁 shared/config`; + - если это взаимодействие с бэкендом, переместите в `📁 shared/api`. + +:::note + +**Копирование само по себе не является архитектурной проблемой**, на самом деле иногда даже правильнее продублировать что-то, чем абстрагировать в новый переиспользуемый модуль. Дело в том, что иногда общие части страниц начинают расходиться, и в этих случаях вам не нужно, чтобы эти зависимости мешались. + +Однако существует смысл в принципе DRY ("don't repeat yourself" — "не повторяйтесь"), поэтому убедитесь, что вы не копируете бизнес-логику. В противном случае вам придется держать в голове, что баги нужно исправлять в нескольких местах одновременно. + +::: + +## Шаг 4. Разберите слой Shared {#unpack-shared-layer} + +На данном этапе у вас может быть много всего в слое Shared, и в целом, следует избегать таких ситуаций. Причина этому в том, что слой Shared может быть зависимостью для любого другого слоя в вашем коде, поэтому внесение изменений в этот код автоматически более чревато непредвиденными последствиями. + +Найдите все объекты, которые используются только на одной странице, и переместите их в слайс этой страницы. И да, _это относится и к экшнам (actions), редьюсерам (reducers) и селекторам (selectors)_. Нет никакой пользы в группировке всех экшнов вместе, но есть польза в том, чтобы поместить актуальные экшны рядом с их местом использования. + +В итоге у вас должна получиться структура файлов, похожая на эту: + +
+ 📁 src +
    +
  • 📁 app (unchanged)
  • +
  • +
    + 📁 pages +
      +
    • +
      + 📁 product +
        +
      • 📁 actions
      • +
      • 📁 reducers
      • +
      • 📁 selectors
      • +
      • +
        + 📁 ui +
          +
        • 📄 Component.jsx
        • +
        • 📄 Container.jsx
        • +
        • 📄 ProductPage.jsx
        • +
        +
        +
      • +
      • 📄 index.js
      • +
      +
      +
    • +
    • 📁 catalog
    • +
    +
    +
  • +
  • +
    + 📁 shared (only objects that are reused) +
      +
    • 📁 actions
    • +
    • 📁 api
    • +
    • 📁 components
    • +
    • 📁 containers
    • +
    • 📁 constants
    • +
    • 📁 i18n
    • +
    • 📁 modules
    • +
    • 📁 helpers
    • +
    • 📁 utils
    • +
    • 📁 reducers
    • +
    • 📁 selectors
    • +
    • 📁 styles
    • +
    +
    +
  • +
+
+ +## Шаг 5. Распределите код по техническому назначению {#organize-by-technical-purpose} + +В FSD разделение по техническому назначению происходит с помощью _сегментов_. Существует несколько часто встречающихся сегментов: + +- `ui` — всё, что связано с отображением интерфейса: компоненты UI, форматирование дат, стили и т. д. +- `api` — взаимодействие с бэкендом: функции запросов, типы данных, мапперы и т. д. +- `model` — модель данных: схемы, интерфейсы, хранилища и бизнес-логика. +- `lib` — библиотечный код, который нужен другим модулям на этом слайсе. +- `config` — файлы конфигурации и фиче-флаги. + +Вы можете создавать свои собственные сегменты, если это необходимо. Убедитесь, что не создаете сегменты, которые группируют код по тому, чем он является, например, `components`, `actions`, `types`, `utils`. Вместо этого группируйте код по тому, для чего он предназначен. + +Перераспределите код ваших страниц по сегментам. У вас уже должен быть сегмент `ui`, теперь пришло время создать другие сегменты, например, `model` для ваших экшнов, редьюсеров и селекторов, или `api` для ваших thunk-ов и мутаций. + +Также перераспределите слой Shared, чтобы удалить следующие папки: +- `📁 components`, `📁 containers` — большинство из их содержимого должно стать `📁 shared/ui`; +- `📁 helpers`, `📁 utils` — если остались какие-то повторно используемые хелперы, сгруппируйте их по назначению, например, даты или преобразования типов, и переместите эти группы в `📁 shared/lib`; +- `📁 constants` — так же сгруппируйте по назначению и переместите в `📁 shared/config`. + +## Шаги по желанию {#optional-steps} + +### Шаг 6. Создайте сущности/фичи ёмкостью из Redux-слайсов, которые используются на нескольких страницах {#form-entities-features-from-redux} + +Обычно эти переиспользуемые Redux-слайсы будут описывать что-то, что имеет отношение к бизнесу, например, продукты или пользователи, поэтому их можно переместить в слой Entities, одна сущность на одну папку. Если Redux-слайс скорее связан с действием, которое ваши пользователи хотят совершить в вашем приложении, например, комментарии, то его можно переместить в слой Features. + +Сущности и фичи должны быть независимы друг от друга. Если ваша бизнес-область содержит встроенные связи между сущностями, обратитесь к [руководству по бизнес-сущностям][business-entities-cross-relations] за советом по организации этих связей. + +API-функции, связанные с этими слайсами, могут остаться в `📁 shared/api`. + +### Шаг 7. Проведите рефакторинг modules {#refactor-your-modules} + +Папка `📁 modules` обычно используется для бизнес-логики, поэтому она уже довольно похожа по своей природе на слой Features из FSD. Некоторые модули могут также описывать большие части пользовательского интерфейса, например, шапку приложения. В этом случае их можно переместить в слой Widgets. + +### Шаг 8. Сформируйте чистый фундамент UI в `shared/ui` {#form-clean-ui-foundation} + +`📁 shared/ui`, в идеале, должен содержать набор UI-элементов, в которых нет бизнес-логики. Они также должны быть очень переиспользуемыми. + +Проведите рефакторинг UI-компонентов, которые раньше находились в `📁 components` и `📁 containers`, чтобы отделить бизнес-логику. Переместите эту бизнес-логику в верхние слои. Если она не используется в слишком многих местах, вы даже можете рассмотреть копирование как вариант. + +## See also {#see-also} + +- [(Доклад) Ilya Klimov — Крысиные бега бесконечного рефакторинга: как не дать техническому долгу убить мотивацию и продукт](https://youtu.be/aOiJ3k2UvO4) + +[ext-steiger]: https://github.com/feature-sliced/steiger +[business-entities-cross-relations]: /docs/guides/examples/types#business-entities-and-their-cross-references diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-legacy.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-legacy.mdx deleted file mode 100644 index 7bc5252981..0000000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-legacy.mdx +++ /dev/null @@ -1,121 +0,0 @@ ---- -sidebar_position: 3 -sidebar_class_name: sidebar-item--wip ---- - -import WIP from '@site/src/shared/ui/wip/tmpl.mdx' - -# Миграция с legacy - - - -> В статье агрегируется опыт нескольких компаний и проектов по переезду на Feature-Sliced Design с разными изначальными условиями - -## Зачем? {#why} - -> Насколько нужен переезд? "Смерть от тысячи порезов" и тех долг. Чего не хватает? Чем может помочь методология? - -> См. доклад [Илья Климова про необходимость и порядок рефакторинга](https://youtu.be/aOiJ3k2UvO4) - -![approaches-themed-bordered](/img/approaches.png) - -## Какой план? {#whats-the-plan} - -### 1. Унификация кодовой базы {#1-unification-of-the-code-base} - -```diff -- ├── products/ -- | ├── components/ -- | ├── containers/ -- | ├── store/ -- | ├── styles/ -- ├── checkout/ -- | ├── components/ -- | ├── containers/ -- | ├── helpers/ -- | ├── styles/ -+ └── src/ - ├── actions/ - ├── api/ -+ ├── components/ -+ ├── containers/ - ├── constants/ - ├── epics/ -+ ├── i18n/ - ├── modules/ -+ ├── helpers/ -+ ├── pages/ -- ├── routes/ -- ├── utils/ - ├── reducers/ -- ├── redux/ - ├── selectors/ -+ ├── store -+ ├── styles/ - ├── App.jsx - └── index.jsx -``` - - -### 2. Собираем вместе излишне раздробленное {#2-putting-together-the-destructive-decoupled} - -```diff - └── src/ -- ├── actions/ - ├── api/ -- ├── components/ -- ├── containers/ -- ├── constants/ -- ├── epics/ -+ ├── entities/{...} -+ | ├── ui -+ | ├── model/{actions, selectors, ...} -+ | ├── lib - ├── i18n/ - | # Временно можем положить сюда оставшиеся сегменты -+ ├── modules/{helpers, constants} -- ├── helpers/ - ├── pages/ -- ├── reducers/ -- ├── selectors/ -- ├── store/ - ├── styles/ - ├── App.jsx - └── index.jsx -``` - -### 3. Выделяем скоупы ответственности {#3-allocate-scopes-of-responsibility} - -```diff - └── src/ -- ├── api/ -+ ├── app/ -+ | ├── index.jsx -+ | ├── style.css - ├── pages/ -+ ├── features/ -+ | ├── add-to-cart/{ui, model, lib} -+ | ├── choose-delivery/{ui, model, lib} -+ ├── entities/{...} -+ | ├── delivery/{ui, model, lib} -+ | ├── cart/{ui, model, lib} -+ | ├── product/{ui, model, lib} -+ ├── shared/ -+ | ├── api/ -+ | ├── lib/ # helpers -+ | | ├── i18n/ -+ | ├── config/ # constants -- ├── i18n/ -- ├── modules/{helpers, constants} - └── index.jsx -``` - -### 4. Final ? - -> Про оставшиеся проблемы и насколько стоит их устранять - -## См. также {#see-also} - -- [(Доклад) Илья Климов - Крысиные бега бесконечного рефакторинга: как не дать техническому долгу убить мотивацию и продукт](https://youtu.be/aOiJ3k2UvO4) -- [(Доклад) Илья Азин - Архитектура Frontend проектов](https://youtu.be/SnzPAr_FJ7w) - - В докладе в том числе рассмотрены подходы к архитектуре и стоимости рефакторинга \ No newline at end of file diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md index c0f4d33e02..5613ab5d27 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 2 --- # Миграция с v1 @@ -157,10 +157,10 @@ sidebar_position: 4 - [Новые идеи v2 с пояснениями (atomicdesign-chat)][ext-tg-v2-draft] - [Обсуждение абстракций и нейминга для новой версии методологии (v2)](https://github.com/feature-sliced/documentation/discussions/31) -[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion +[refs-low-coupling]: /docs/reference/slices-segments#zero-coupling-high-cohesion [refs-adaptability]: /docs/about/understanding/naming -[ext-v1]: https://featureslices.dev/v1.0.html +[ext-v1]: https://feature-sliced.github.io/featureslices.dev/v1.0.html [ext-tg-spb]: https://t.me/feature_slices [ext-fdd]: https://github.com/feature-sliced/documentation/tree/rc/feature-driven [ext-fdd-issues]: https://github.com/kof/feature-driven-architecture/issues diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md new file mode 100644 index 0000000000..371843d981 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md @@ -0,0 +1,45 @@ +--- +sidebar_position: 3 +--- + +# Миграция с v2.0 на v2.1 + +Основным изменением в v2.1 является новая ментальная модель разложения интерфейса — сначала страницы. + +В версии FSD 2.0 рекомендовалось найти сущности и фичи в вашем интерфейсе, рассматривая даже малейшие части представления сущностей и интерактивность как кандидаты на декомпозицию. Затем вы бы могли строить виджеты и страницы из сущностей и фич. В этой модели декомпозиции большая часть логики находилась в сущностях и фичах, а страницы были просто композиционными слоями, которые сами по себе не имели большого значения. + +В версии FSD 2.1 мы рекомендуем начинать со страниц, и возможно даже на них и остановиться. Большинство людей уже знают, как разделить приложение на страницы, и страницы также часто являются отправной точкой при попытке найти компонент в кодовой базе. В новой модели декомпозиции вы храните большую часть интерфейса и логики в каждой отдельной странице, а повторно используемый фундамент — в Shared. Если возникнет необходимость переиспользования бизнес-логики на нескольких страницах, вы можете переместить её на слой ниже. + +Другим нововведением в Feature-Sliced Design 2.1 является стандартизация кросс-импортов между сущностями с помощью `@x`-нотации. + +## Как мигрировать {#how-to-migrate} + +В версии 2.1 нет ломающих изменений, что означает, что проект, написанный с использованием FSD v2.0, также является валидным проектом в FSD v2.1. Однако мы считаем, что новая ментальная модель более полезна для команд и особенно для обучения новых разработчиков, поэтому рекомендуем внести небольшие изменения в вашу декомпозицию. + +### Соедините слайсы + +Простой способ начать — запустить на проекте наш линтер, [Steiger][steiger]. Steiger построен с новой ментальной моделью, и наиболее полезные правила будут: + +- [`insignificant-slice`][insignificant-slice] — если сущность или фича используется только на одной странице, это правило предложит целиком переместить код этой сущности или фичи прямо в эту страницу. +- [`excessive-slicing`][excessive-slicing] — если у слоя слишком много слайсов, это обычно означает, что декомпозиция слишком мелкая. Это правило предложит объединить или сгруппировать некоторые слайсы, чтобы помочь в навигации по проекту. + +```bash +npx steiger src +``` + +Это поможет вам определить, какие слайсы используются только один раз, чтобы вы могли ещё раз подумать, действительно ли они необходимы. Помните, что слой формирует своего рода глобальное пространство имен для всех слайсов внутри него. Точно так же, как вы не захотите загрязнять глобальное пространство имен переменными, которые используются только один раз, вы должны относиться к месту в пространстве имен слоя как к ценному месту, которое следует использовать сдержанно. + +### Стандартизируйте кросс-импорты + +Если у вас были кросс-импорты в вашем проекте до этого (мы не осуждаем!), вы теперь можете воспользоваться новой нотацией для кросс-импортов в Feature-Sliced Design — `@x`-нотацией. Она выглядит так: + +```ts title="entities/B/some/file.ts" +import type { EntityA } from "entities/A/@x/B"; +``` + +Чтоб узнать больше об этом, обратитесь к разделу [Публичный API для кросс-импортов][public-api-for-cross-imports] в разделе справочника. + +[insignificant-slice]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice +[steiger]: https://github.com/feature-sliced/steiger +[excessive-slicing]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/excessive-slicing +[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx index 14f857424e..b4778fa09b 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx @@ -1,16 +1,120 @@ --- sidebar_position: 10 -sidebar_class_name: sidebar-item--wip --- +# Использование с NextJS -import WIP from '@site/src/shared/ui/wip/tmpl.mdx' +В NextJS проекте возможно реализовать FSD, но возникают конфликты из-за различий между требованиями к структуре проекта NextJS и принципами FSD в двух пунктах:  -# Использование с NextJS +- Маршрутизация файлов в слое `pages` +- Конфликт или отсутствие слоя `app` в NextJS + +## Конфликт между FSD и NextJS в слое `pages` {#pages-conflict} + +NextJS предлагает использовать папку `pages` для определения маршрутов приложения. NextJS ожидает, что файлы в папке `pages` будут соответствовать URL-адресам. +Этот механизм маршрутизации **не соответствует** концепции FSD, поскольку в таком механизме маршрутизации не представляется возможным соблюсти плоскую структуру слайсов. + +### Перемещение папки `pages` NextJS в корневую папку проекта (рекомендуется) + +Подход заключается в перемещении папки `pages` NextJS в корневую папку проекта и импорте страниц FSD в папку `pages` NextJS. Это сохраняет +структуру проекта FSD внутри папки `src`. + +```sh +├── pages # Папка pages (NextJS) +├── src +│ ├── app +│ ├── entities +│ ├── features +│ ├── pages # Папка pages (FSD) +│ ├── shared +│ ├── widgets +``` + +### Переименование папки `pages` в структуре FSD + +Другой способ решить проблему - переименовать слой `pages` в структуре FSD, чтобы избежать конфликтов с папкой `pages` NextJS. +Вы можете переименовать слой `pages` в FSD в `views`.  +Таким образом, структура проекта в папке `src` сохраняется без противоречий с требованиями NextJS. + +```sh +├── app +├── entities +├── features +├── pages # Папка pages (NextJS) +├── views # Переименованная папка страниц FSD +├── shared +├── widgets +``` + +Учтите, что в этом случае рекомендуется задокументировать это переименование в видном месте — в README проекта или внутренней документации. Это переименование — часть ["проектных знаний"][project-knowledge]. + +## Отсутствие папки `app` в NextJS {#app-absence} + +В NextJS ниже версии 13 нет явной папки `app`, вместо этого NextJS предоставляет файл `_app.tsx`, +который играет роль компонента обертывания для всех страниц проекта. + +### Импорт функциональности в `pages/_app.tsx` - +Чтобы решить проблему отсутствия папки `app` в структуре NextJS, вы можете создать компонент `App` внутри слоя `app` и импортировать компонент `App` в `pages/_app.tsx`, чтобы NextJS мог с ним работать. +Например: -> Про специфику фреймворка и совместимость с методологией +```tsx +// app/providers/index.tsx + +const App = ({ Component, pageProps }: AppProps) => { + return ( + + + + + + + + ); +}; + +export default App; +``` +Затем вы можете импортировать компонент `App` и глобальные стили проекта в `pages/_app.tsx` следующим образом: + +```tsx +// pages/_app.tsx + +import 'app/styles/index.scss' + +export { default } from 'app/providers'; +``` + +## Работа с App Router {#app-router} + +App Router стал стабильным в NextJS версии 13.4. App Router позволяет использовать папку `app` для маршрутизации вместо папки `pages`. +Для соответствия принципам FSD, вам следует обращаться с папкой `app` NextJS так же, как рекомендуется +для устранения конфликта с папкой `pages` NextJS. + +Подход заключается в перемещении папки `app` NextJS в корневую папку проекта и импорте страниц FSD в папку `app` NextJS. Это сохраняет +структуру проекта FSD внутри папки `src`. Вам также стоит добавить в корневую папку проекта папку `pages`, потому что App Router совместим с Pages Router. + +``` +├── app # Папка app (NextJS) +├── pages # Пустая папка pages (NextJS) +│ ├── README.md # Описание того, зачем нужна эта папка +├── src +│ ├── app # Папка app (FSD) +│ ├── entities +│ ├── features +│ ├── pages # Папка pages (FSD) +│ ├── shared +│ ├── widgets +``` + +[![Открыть в StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)][ext-app-router-stackblitz] + +## Middleware + +Если вы используете middleware в проекте, его также нужно переместить из `src` в корень проекта. Иначе NextJS просто не увидит его — middleware должен находиться строго в корне рядом с `app` или `pages`. ## См. также {#see-also} - [(Тред) Про pages директорию в NextJS](https://t.me/feature_sliced/3623) + +[project-knowledge]: /docs/about/understanding/knowledge-types +[ext-app-router-stackblitz]: https://stackblitz.com/edit/stackblitz-starters-aiez55?file=README.md diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx new file mode 100644 index 0000000000..d0c6ebd2a8 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx @@ -0,0 +1,179 @@ +--- +sidebar_position: 10 +--- +# Использование с NuxtJS + +В NuxtJS проекте возможно реализовать FSD, однако возникают конфликты из-за различий между требованиями к структуре проекта NuxtJS и принципами FSD: + +- Изначально, NuxtJS предлагает файловую структуру проекта без папки `src`, то есть в корне проекта. +- Файловый роутинг находится в папке `pages`, а в FSD эта папка отведена под плоскую структуру слайсов. + + +## Добавление алиаса для `src` директории + +Добавьте обьект `alias` в ваш конфиг: +```ts title="nuxt.config.ts" +export default defineNuxtConfig({ + devtools: { enabled: true }, // Не относятся к FSD, включёны при старте проекта + alias: { + "@": '../src' + }, +}) +``` +## Выбор способа настройки роутера + +В NuxtJS есть два способа настройки роутинга - с помощью конфига и с помощью файловой структуры. +В случае с файловым роутингом вы будете создавать index.vue файлы в папках внутри директории app/routes, а в случае конфига - настраивать роуты в `router.options.ts` файле. + + +### Роутинг с помощью конфига + +В слое `app` создайте файл `router.options.ts`, и экспортируйте из него обьект конфига: +```ts title="app/router.options.ts" +import type { RouterConfig } from '@nuxt/schema'; + +export default { + routes: (_routes) => [], +}; + +``` + +Чтобы добавить страницу `Home` в проект, вам нужно сделать следующие шаги: +- Добавить слайс страницы внутри слоя `pages` +- Добавить соответствующий роут в конфиг `app/router.config.ts` + + +Для того чтобы создать слайс страницы, воспользуемся [CLI](https://github.com/feature-sliced/cli): + +```shell +fsd pages home +``` + +Создайте файл `home-page.vue` внутри сегмента ui, откройте к нему доступ с помощью Public API + +```ts title="src/pages/home/index.ts" +export { default as HomePage } from './ui/home-page'; +``` + +Таким образом, файловая структура будет выглядеть так: +```sh +|── src +│ ├── app +│ │ ├── router.config.ts +│ ├── pages +│ │ ├── home +│ │ │ ├── ui +│ │ │ │ ├── home-page.vue +│ │ │ ├── index.ts +``` +Наконец, добавим роут в конфиг: + +```ts title="app/router.config.ts" +import type { RouterConfig } from '@nuxt/schema' + +export default { + routes: (_routes) => [ + { + name: 'home', + path: '/', + component: () => import('@/pages/home.vue').then(r => r.default || r) + } + ], +} +``` + +### Файловый роутинг + +В первую очередь, создайте `src` директорию в корне проекта, а также создайте внутри этой директории слои app и pages и папку routes внутри слоя app. +Таким образом, ваша файловая структура должна выглядеть так: + +```sh +├── src +│ ├── app +│ │ ├── routes +│ ├── pages # Папка pages, закреплённая за FSD +``` + +Для того чтобы NuxtJS использовал папку routes внутри слоя `app` для файлового роутинга, вам нужно изменить `nuxt.config.ts` следующим образом: +```ts title="nuxt.config.ts" +export default defineNuxtConfig({ + devtools: { enabled: true }, // Не относятся к FSD, включёны при старте проекта + alias: { + "@": '../src' + }, + dir: { + pages: './src/app/routes' + } +}) +``` + +Теперь, вы можете создавать роуты для страниц внутри `app` и подключать к ним страницы из `pages`. + +Например, чтобы добавить страницу `Home` в проект, вам нужно сделать следующие шаги: +- Добавить слайс страницы внутри слоя `pages` +- Добавить соответствующий роут внутрь слоя `app` +- Совместить страницу из слайса с роутом + +Для того чтобы создать слайс страницы, воспользуемся [CLI](https://github.com/feature-sliced/cli): + +```shell +fsd pages home +``` + +Создайте файл `home-page.vue` внутри сегмента ui, откройте к нему доступ с помощью Public API + +```ts title="src/pages/home/index.ts" +export { default as HomePage } from './ui/home-page'; +``` + +Создайте роут для этой страницы внутри слоя `app`: + +```sh + +├── src +│ ├── app +│ │ ├── routes +│ │ │ ├── index.vue +│ ├── pages +│ │ ├── home +│ │ │ ├── ui +│ │ │ │ ├── home-page.vue +│ │ │ ├── index.ts +``` + +Добавьте внутрь `index.vue` файла компонент вашей страницы: + +```html title="src/app/routes/index.vue" + + + +``` + +## Что делать с `layouts`? + +Вы можете разместить layouts внутри слоя `app`, для этого нужно изменить конфиг следующим образом: + +```ts title="nuxt.config.ts" +export default defineNuxtConfig({ + devtools: { enabled: true }, // Не относятся к FSD, включёны при старте проекта + alias: { + "@": '../src' + }, + dir: { + pages: './src/app/routes', + layouts: './src/app/layouts' + } +}) +``` + + +## См. также + +- [Документация по изменению конфига директорий в NuxtJS](https://nuxt.com/docs/api/nuxt-config#dir) +- [Документация по изменению конфига роутера в NuxtJS](https://nuxt.com/docs/guide/recipes/custom-routing#router-config) +- [Документация по изменению алиасов в NuxtJS](https://nuxt.com/docs/api/nuxt-config#alias) + diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx new file mode 100644 index 0000000000..db40bcd655 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx @@ -0,0 +1,436 @@ +--- +sidebar_position: 10 +--- +# Использование с React Query + +## Проблема «куда положить ключи» + +### Решение - разбить по сущностям + +Если в проекте уже присутствует разделение на сущности, и каждый запрос соответствует одной сущности, +наиболее чистым будет разделение по сущностям. В таком случае, предлагаем использовать следующую структуру: + +```sh +└── src/ # + ├── app/ # + | ... # + ├── pages/ # + | ... # + ├── entities/ # + | ├── {entity}/ # + | ... └── api/ # + | ├── `{entity}.query` # Фабрика запросов, где определены ключи и функции + | ├── `get-{entity}` # Функция получения сущности + | ├── `create-{entity}` # Функция создания сущности + | ├── `update-{entity}` # Функция обновления объекта + | ├── `delete-{entity}` # Функция удаления объекта + | ... # + | # + ├── features/ # + | ... # + ├── widgets/ # + | ... # + └── shared/ # + ... # +``` + +Если среди сущностей есть связи (например, у сущности Страна есть поле-список сущностей Город), то можно воспользоваться +[публичным API для кросс-импортов][public-api-for-cross-imports] или рассмотреть альтернативное решение ниже. + + +### Альтернативное решение — хранить запросы в общем доступе. + +В случаях, когда не подходит разделение по сущностям, можно рассмотреть следующую структуру: + +```sh +└── src/ # + ... # + └── shared/ # + ├── api/ # + ... ├── `queries` # Query-factories + | ├── `document.ts` # + | ├── `background-jobs.ts` # + | ... # + └── index.ts # +``` + +Затем в `@/shared/api/index.ts`: + +```ts title="@/shared/api/index.ts" +export { documentQueries } from "./queries/document"; +``` + +## Проблема «Куда мутации?» + +Мутации не рекомендуется смешивать с запросами. Возможны два варианта: + +### 1. Определить кастомный хук в сегменте api рядом с местом использования + +```tsx title="@/features/update-post/api/use-update-title.ts" +export const useUpdateTitle = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ id, newTitle }) => + apiClient + .patch(`/posts/${id}`, { title: newTitle }) + .then((data) => console.log(data)), + + onSuccess: (newPost) => { + queryClient.setQueryData(postsQueries.ids(id), newPost); + }, + }); +}; +``` + +### 2. Определить функцию мутации в другом месте (Shared или Entities) и использовать `useMutation` напрямую в компоненте + +```tsx +const { mutateAsync, isPending } = useMutation({ + mutationFn: postApi.createPost, +}); +``` + +```tsx title="@/pages/post-create/ui/post-create-page.tsx" +export const CreatePost = () => { + const { classes } = useStyles(); + const [title, setTitle] = useState(""); + + const { mutate, isPending } = useMutation({ + mutationFn: postApi.createPost, + }); + + const handleChange = (e: ChangeEvent) => + setTitle(e.target.value); + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + mutate({ title, userId: DEFAULT_USER_ID }); + }; + + return ( +
+ + + Create + + + ); +}; +``` + +## Организация запросов + +### Фабрика запросов + +В этом гайде рассмотрим, как использовать фабрику запросов (объект, где значениями ключа - являются функции, возвращающие список ключей запроса) + +```ts +const keyFactory = { + all: () => ["entity"], + lists: () => [...postQueries.all(), "list"], +}; +``` + +:::info +`queryOptions` - встроенная утилита react-query@v5 (опционально) + +```ts +queryOptions({ + queryKey, + ...options, +}); +``` + +Для большей типобезопасности и дальнейшей совместимости со следующими версиями react-query, а также упрощенного доступа к функциям и ключам запросов - +можно использовать встроенную в функцию queryOptions из “@tanstack/react-query” [(Подробнее здесь)](https://tkdodo.eu/blog/the-query-options-api#queryoptions). +::: + +### 1. Создание Фабрики запросов + +```tsx title="@/entities/post/api/post.queries.ts" +import { keepPreviousData, queryOptions } from "@tanstack/react-query"; +import { getPosts } from "./get-posts"; +import { getDetailPost } from "./get-detail-post"; +import { PostDetailQuery } from "./query/post.query"; + +export const postQueries = { + all: () => ["posts"], + + lists: () => [...postQueries.all(), "list"], + list: (page: number, limit: number) => + queryOptions({ + queryKey: [...postQueries.lists(), page, limit], + queryFn: () => getPosts(page, limit), + placeholderData: keepPreviousData, + }), + + details: () => [...postQueries.all(), "detail"], + detail: (query?: PostDetailQuery) => + queryOptions({ + queryKey: [...postQueries.details(), query?.id], + queryFn: () => getDetailPost({ id: query?.id }), + staleTime: 5000, + }), +}; +``` + +### 2. Применение Фабрики запросов в коде приложения +```tsx +import { useParams } from "react-router-dom"; +import { postApi } from "@/entities/post"; +import { useQuery } from "@tanstack/react-query"; + +type Params = { + postId: string; +}; + +export const PostPage = () => { + const { postId } = useParams(); + const id = parseInt(postId || ""); + const { + data: post, + error, + isLoading, + isError, + } = useQuery(postApi.postQueries.detail({ id })); + + if (isLoading) { + return
Loading...
; + } + + if (isError || !post) { + return <>{error?.message}; + } + + return ( +
+

Post id: {post.id}

+
+

{post.title}

+
+

{post.body}

+
+
+
Owner: {post.userId}
+
+ ); +}; +``` + +### Преимущества использования Фабрики запросов +- **Структурирование запросов:** Фабрика позволяет организовать все запросы к API в одном месте, что делает код более читаемым и поддерживаемым. +- **Удобный доступ к запросам и ключам:** Фабрика предоставляет удобные методы для доступа к различным типам запросов и их ключам. +- **Возможность рефетчинга запросов:** Фабрика обеспечивает возможность легкой рефетчинга без необходимости изменения ключей запросов в разных частях приложения. + +## Пагинация + +В этом разделе рассмотрим пример функции `getPosts`, которая выполняет запрос к API для получения сущностей постов с применением пагинации. + +### 1. Создание функции `getPosts` +Функция getPosts находится в файле `get-posts.ts`, который находится в сегменте API. + +```tsx title="@/pages/post-feed/api/get-posts.ts" +import { apiClient } from "@/shared/api/base"; + +import { PostWithPaginationDto } from "./dto/post-with-pagination.dto"; +import { PostQuery } from "./query/post.query"; +import { mapPost } from "./mapper/map-post"; +import { PostWithPagination } from "../model/post-with-pagination"; + +const calculatePostPage = (totalCount: number, limit: number) => + Math.floor(totalCount / limit); + +export const getPosts = async ( + page: number, + limit: number, +): Promise => { + const skip = page * limit; + const query: PostQuery = { skip, limit }; + const result = await apiClient.get("/posts", query); + + return { + posts: result.posts.map((post) => mapPost(post)), + limit: result.limit, + skip: result.skip, + total: result.total, + totalPages: calculatePostPage(result.total, limit), + }; +}; +``` + +### 2. Фабрика запросов для пагинации +Фабрика запросов `postQueries` определяет различные варианты запросов для работы с постами, +включая запрос списка постов с заранее определенной страницей и лимитом. + +```tsx +import { keepPreviousData, queryOptions } from "@tanstack/react-query"; +import { getPosts } from "./get-posts"; + +export const postQueries = { + all: () => ["posts"], + lists: () => [...postQueries.all(), "list"], + list: (page: number, limit: number) => + queryOptions({ + queryKey: [...postQueries.lists(), page, limit], + queryFn: () => getPosts(page, limit), + placeholderData: keepPreviousData, + }), +}; +``` + + +### 3. Использование в коде приложения +```tsx title="@/pages/home/ui/index.tsx" +export const HomePage = () => { + const itemsOnScreen = DEFAULT_ITEMS_ON_SCREEN; + const [page, setPage] = usePageParam(DEFAULT_PAGE); + const { data, isFetching, isLoading } = useQuery( + postApi.postQueries.list(page, itemsOnScreen), + ); + return ( + <> + setPage(page)} + page={page} + count={data?.totalPages} + variant="outlined" + color="primary" + /> + + + ); +}; +``` +:::note +Пример упрощен, полная версия доступна на [GitHub](https://github.com/ruslan4432013/fsd-react-query-example) +::: + +## `QueryProvider` для управления запросами +В этом гайде рассмотрим, как организовать `QueryProvider`. + +### 1. Создание `QueryProvider` +Файл `query-provider.tsx` расположен по пути `@/app/providers/query-provider.tsx`. + +```tsx title="@/app/providers/query-provider.tsx" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { ReactNode } from "react"; + +type Props = { + children: ReactNode; + client: QueryClient; +}; + +export const QueryProvider = ({ client, children }: Props) => { + return ( + + {children} + + + ); +}; +``` + +### 2. Создание `QueryClient` +`QueryClient` представляет собой экземпляр, используемый для управления запросами к API. +Файл `query-client.ts` расположен по пути `@/shared/api/query-client.ts`. +`QueryClient` создается с определенными настройками для кэширования запросов. + +```tsx title="@/shared/api/query-client.ts" +import { QueryClient } from "@tanstack/react-query"; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, + gcTime: 5 * 60 * 1000, + }, + }, +}); +``` + +## Кодогенерация + +Существуют инструменты для автоматической генерации кода, +которые менее гибкие, по сравнению с теми, что можно настроить, +как описано выше. Если ваш Swagger-файл хорошо структурирован, и вы используете одно из таких инструментов, +то возможно имеет смысл сгенерировать весь код в каталоге @/shared/api. + + +## Дополнительный совет по организации RQ +### API-Клиент + +Используя собственный класс клиента API в общем слое shared, +можно стандартизировать настройку и работу с API в проекте. +Это позволяет управлять логированием, заголовками и форматом обмена данными (например, JSON или XML) из одного места. +Такой подход облегчает поддержку и развитие проекта, поскольку упрощает изменения и обновления взаимодействия с API. + +```tsx title="@/shared/api/api-client.ts" +import { API_URL } from "@/shared/config"; + +export class ApiClient { + private baseUrl: string; + + constructor(url: string) { + this.baseUrl = url; + } + + async handleResponse(response: Response): Promise { + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + try { + return await response.json(); + } catch (error) { + throw new Error("Error parsing JSON response"); + } + } + + public async get( + endpoint: string, + queryParams?: Record, + ): Promise { + const url = new URL(endpoint, this.baseUrl); + + if (queryParams) { + Object.entries(queryParams).forEach(([key, value]) => { + url.searchParams.append(key, value.toString()); + }); + } + const response = await fetch(url.toString(), { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + return this.handleResponse(response); + } + + public async post>( + endpoint: string, + body: TData, + ): Promise { + const response = await fetch(`${this.baseUrl}${endpoint}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + return this.handleResponse(response); + } +} + +export const apiClient = new ApiClient(API_URL); +``` + +## Полезные ссылки {#see-also} + +- [(GitHub) Пример проекта](https://github.com/ruslan4432013/fsd-react-query-example) +- [(CodeSandbox) Пример проекта](https://codesandbox.io/p/github/ruslan4432013/fsd-react-query-example/main) +- [О фабрике запросов](https://tkdodo.eu/blog/the-query-options-api) + +[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx new file mode 100644 index 0000000000..35e05047ec --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx @@ -0,0 +1,98 @@ +--- +sidebar_position: 10 +--- +# Использование с SvelteKit + +В SvelteKit проекте возможно реализовать FSD, однако возникают конфликты из-за различий между требованиями к структуре проекта SvelteKit и принципами FSD: + +- Изначально, SvelteKit предлагает файловую структуру внутри папки `src/routes`, в то время как в FSD роутинг должен быть частью слоя `app`. +- SvelteKit предлагает складывать всё, что не относится к роутингу в папку `src/lib`. + + +## Настроим конфиг + +```ts title="svelte.config.ts" +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config}*/ +const config = { + preprocess: [vitePreprocess()], + kit: { + adapter: adapter(), + files: { + routes: 'src/app/routes', // перемещаем роутинг внутрь app слоя + lib: 'src', + appTemplate: 'src/app/index.html', // Перемещаем входную точку приложения внутрь слоя app + assets: 'public' + }, + alias: { + '@/*': 'src/*' // Создаём алиас для директории src + } + } +}; +export default config; +``` + +## Перемещение файлового роутинга в `src/app` + +Создадим слой app, переместим в него входную точку приложения `index.html` и создадим папку routes. +Таким образом, ваша файловая структура должна выглядеть так: + +```sh +├── src +│ ├── app +│ │ ├── index.html +│ │ ├── routes +│ ├── pages # Папка pages, закреплённая за FSD +``` + +Теперь, вы можете создавать роуты для страниц внутри `app` и подключать к ним страницы из `pages`. + +Например, чтобы добавить главную страницу в проект, вам нужно сделать следующие шаги: +- Добавить слайс страницы внутри слоя `pages` +- Добавить соответствующий роут в папку `routes` из слоя `app` +- Совместить страницу из слайса с роутом + +Для того чтобы создать слайс страницы, воспользуемся [CLI](https://github.com/feature-sliced/cli): + +```shell +fsd pages home +``` + +Создайте файл `home-page.svelte` внутри сегмента ui, откройте к нему доступ с помощью Public API + +```ts title="src/pages/home/index.ts" +export { default as HomePage } from './ui/home-page.svelte'; +``` + +Создайте роут для этой страницы внутри слоя `app`: + +```sh + +├── src +│ ├── app +│ │ ├── routes +│ │ │ ├── +page.svelte +│ │ ├── index.html +│ ├── pages +│ │ ├── home +│ │ │ ├── ui +│ │ │ │ ├── home-page.svelte +│ │ │ ├── index.ts +``` + +Добавьте внутрь `+page.svelte` файла компонент вашей страницы: + +```html title="src/app/routes/+page.svelte" + + + + +``` + +## См. также + +- [Документация по изменению конфига директорий в SvelteKit](https://kit.svelte.dev/docs/configuration#files) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/privacy.md b/i18n/ru/docusaurus-plugin-content-docs/current/privacy.md deleted file mode 100644 index d2bcfd5f76..0000000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/privacy.md +++ /dev/null @@ -1,17 +0,0 @@ -# Конфиденциальность - -**Мы используем куки, для сбора данных по использованию документации (для дальнейшей аналитики и улучшения сайта, и ни для каких иных целей)** - -Единственные плагины для сбора метрик это [@docusaurus/plugin-google-analytics](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-google-analytics) и [docusaurus-plugin-hotjar](https://github.com/symblai/docusaurus-plugin-hotjar). Мы анализируем только те данные, что предоставляются с помощью [Google Analytics](https://analytics.google.com/) и [Hotjar](https://www.hotjar.com/) - чтобы собрать фидбек и улучшить ваш пользовательский опыт. - -:::warning NOTE -Эта страница не является юридически полноценной, поэтому за другими подробностями обратитесь пожалуйста к офиц.сайтам [Google Analytics](https://analytics.google.com/) и [Hotjar](https://www.hotjar.com/) -::: - -**Кто имеет доступ к метрикам?** - -- Core-team методологии -- Google Analytics, на которую мы опираемся для анализа использования сервиса [(см. подробнее)](https://www.google.com/analytics/terms/us.html) -- Hotjar, на который мы опираемся при сборе фидбека по взаимодействию [(см. подробнее)](https://help.hotjar.com/hc/en-us/categories/360003405813) - -![feature-sliced-banner](/img/banner.jpg) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/index.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/reference/index.mdx index 6312615359..10f8ffcda4 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/reference/index.mdx @@ -24,15 +24,9 @@ import { ApiOutlined, GroupOutlined, AppstoreOutlined, NodeIndexOutlined } from to="/docs/reference/slices-segments" Icon={AppstoreOutlined} /> - diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/_category_.yaml b/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/_category_.yaml deleted file mode 100644 index 2baf3c4c0a..0000000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/_category_.yaml +++ /dev/null @@ -1 +0,0 @@ -label: Изоляция diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md b/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md deleted file mode 100644 index f1efcb1e29..0000000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md +++ /dev/null @@ -1,143 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Low Coupling & High Cohesion - -Модули приложения должны проектироваться как обладающие **сильной связностью** (направленные на решение одной четкой задачи) и **слабой зацепленностью** (как можно менее зависимые от других модулей) - -![coupling-cohesion-themed](/img/coupling.png) - -В рамках методологии это достигается через: - -* Разбиение приложения на слои и слайсы - модули, реализующие конкретную функциональность. -* Требование к каждому модулю - предоставлять [публичный интерфейс доступа][refs-public-api] -* Введение специальных ограничений на [взаимодействие модулей между собой][refs-isolation] - каждый модуль может зависеть только от "нижележащих" модулей, но не от модулей с того же или более высокого слоя. - -## Композиция компонентов (UI level) {#components-composition-ui-level} - -Абсолютное большинство современных UI-фреймворков и библиотек предоставляют компонентную модель, в которой каждый компонент может иметь собственные свойства, собственное состояние и дочерние компоненты, а также, зачастую, слоты. - -Такая модель позволяет собирать интерфейс как **композицию различных, напрямую не связанных между собой компонентов** и, тем самым, достигать **слабой зацепленности** компонентов интерфейса - -### Пример {#example} - -Рассмотрим такую композицию на примере **списка с хедером:** - -#### Закладываем расширяемость {#laying-the-extensibility} - -Компонент списка не будет сам определять вид и структуру компонентов хедера и элементов списка, вместо этого будет принимать их в качестве параметров - -```tsx -interface ListProps { - Header: React.ReactNode; - Items: React.ReactNode; -} - -const List: Component = ({ Header, Items }) => ( -
- {Header} -
    - {Items} -
-
-) - -``` - -#### Используем композицию {#using-the-composition} - -Это позволяет **переиспользовать и независимо изменять** компоненты различных версий хедера и элементов списка. Компоненты хедера и элементов списка могут иметь как свое локальное состояние, так и свою привязку к любым частям общего состояния приложения - компонент списка не будет ничего про это знать, а следовательно, не будет от этого зависеть - -```tsx -} Items={} /> - -} /> - -} Items={} /> - -``` - -## Композиция слоев (APP level) {#layer-composition-app-level} - -Методология предлагает разделять ценную для пользователя функциональность на отдельные модули - **фичи (features)**, а логику, относящуюся к бизнес сущностям - в **сущности (entities)**. И фичи, и сущности **должны проектироваться как высоко-связные модули**, т.е. направленные на решение **одной конкретной задачи** или сконцентрированные вокруг **одной конкретной сущности.** - -Все взаимодействия между такими модулями, аналогично UI-компонентам из примера выше, должны быть организованы как **композиция различных модулей.** - -### Пример {#example} - -На примере приложения-чата с такими возможностями - -* можно открыть список контактов и выбрать друга -* можно открыть переписку с выбранным другом - -В рамках методологии, это может быть представлено примерно так: - -Entities - -* Пользователь (содержит состояние пользователя) -* Контакт (состояние списка контактов, инструменты для работы с отдельным контактом) -* Переписка (состояние текущей переписки и работа с ней) - -Features - -* Форма отправки сообщения -* Меню выбора переписки - -#### Свяжем все это вместе {#lets-tie-it-all-together} - -В приложении, для начала, будет одна страница, интерфейс будет основан на слегка модифицированном компоненте из первого примера - -```tsx title=page/main/ui.tsx -} - Items={} - Footer={} -/> -``` - -#### Модель данных {#data-model} - -Модель данных страницы будет организована как **композиция фич и сущностей**. В рамках этого примера фичи будут реализованы как фабрики и получать доступ к интерфейсу сущностей через параметры этих фабрик. - -> Однако, реализация в виде фабрики необязательна - фича может зависеть от нижележащих слоев и напрямую - -```ts title=pages/main/model.ts -import { userModel } from "entitites/user" -import { conversationModel } from "entities/conversation" -import { contactModel } from "entities/contact" - -import { createMessageInput } from "features/message-input" -import { createConversationSwitch } from "features/conversation-switch" - -import { beautifiy } from "shared/lib/beautify-text" - -export const { allConversations, setConversation } = createConversationSwitch({ - contacts: contactModel.allContacts, - setConversation: conversationModel.setConversation, - currentConversation: conversationModel.conversation, - currentUser: userModel.currentUser -}) - -export const { sendMessage, attachFile } = createMessageInput({ - author: userModel.currentUser - send: conversationModel.sendMessage, - formatMessage: beautify -}) -``` - -## Итого {#summary} - -1. Модули должны обладать **сильной связностью** (иметь одну ответственность, решать одну конкретную задачу) и предоставлять [**публичный интерфейс**][refs-public-api] доступа -2. **Слабая зацепленность** достигается через композицию элементов - компонентов UI, фич и сущностей -3. Также, для снижения зацепленности, модули **должны зависеть друг от друга только через публичные интерфейсы** - так достигается независимость модулей от внутренней реализации друг друга - -## См. также {#see-also} - -* [(Статья) Про Low Coupling и High Cohesion наглядно](https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/) - * *Схема в начале вдохновлена именно этой статьей* -* [(Статья) Low Coupling и High Cohesion. Закон Деметры](https://medium.com/german-gorelkin/low-coupling-high-cohesion-d36369fb1be9) -* [(Презентация) Про принципы проектирования (включая Low Coupling & High Cohesion)](https://www.slideshare.net/cristalngo/software-design-principles-57388843) - -[refs-public-api]: /docs/reference/public-api -[refs-isolation]: /docs/reference/isolation diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/decouple-entities.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/decouple-entities.mdx deleted file mode 100644 index 46b2c1e093..0000000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/decouple-entities.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -sidebar_position: 2 -sidebar_class_name: sidebar-item--wip ---- - -import WIP from '@site/src/shared/ui/wip/tmpl.mdx' - -# Decouple entities - - - -> Про кросс-импорты типов, адаптеры и про то - как явно выстраивать связи между сущностями - -> Также про мифические absolutely-decoupled entities - -## См. также {#see-also} - -- [(Тред) Памятка про декомпозицию по сущностям и выстраивание явных связей между ними](https://t.me/feature_sliced/3633) -- [(Тред) Пример декомпозиции для "связных сущностей" (users/pets/friends)](https://t.me/feature_sliced/3316) -- [(Тред) Про кросс-импорты типов/адаптеров в сущностях](https://t.me/feature_sliced/4276) -- [(Тред) Про границы сущностей и фич](https://t.me/feature_sliced/4521) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/index.md deleted file mode 100644 index 4281508ba9..0000000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/index.md +++ /dev/null @@ -1,63 +0,0 @@ -# Изоляция модулей - -В рамках методологии все модули распределены по зонам ответственности (layer, slice, segment) - -Слои, в свою очередь, организованы вертикально: - -- "Внизу" находятся переиспользуемые модули (ui-kit, внутренние библиотеки проекта), как наиболее абстрактные -- А по мере продвижения "вверх" располагаются более специфичные модули. - -Независимо от принадлежности к какому-либо слайсу, каждый модуль [**обязан предоставлять публичный интерфейс доступа**][refs-public-api]. - -## Требования {#requirements} - -Взаимодействие каждого модуля с остальным приложением проектируется с учетом ряда требований: - -1. **Слабое зацепление** с другими модулями - - *Изменение в одном модуле должно слабо и предсказуемо влиять на другие* -1. **Высокая связность** - обязанности каждого модуля "сфокусированы" на одной задаче - - *Если модуль имеет слишком много ответственностей (начинает "делать слишком много") - это должно быть замечено как можно раньше* -1. **Отсутствие циклических зависимостей** на масштабе всего приложения - - *Часто приводят к неожиданному, нежелательному поведению, лучше избегать их совсем* - -## Правило {#rule} - -Для выполнения этих требований, в рамках методологии, необходимо соблюдать базовое правило: - -:::info Важно - -Модуль может зависеть только от "нижележащих" модулей, но не от модулей с того же или более высокого слоя - -::: - -- `features/auth` **не может** зависеть от `features/filters` **и наоборот** -- `features/auth` **может** зависеть от `shared/ui/button`, **но не наоборот** - -Следование этому правилу позволяет поддерживать зависимости **"однонаправленными"** - что автоматически **исключает циклические импорты** и значительно **упрощает отслеживание зависимостей** между модулями в приложении. - -## Выявление проблем {#identifying-problems} - - -Нарушение этого правила является сигналом проблем: - -1. Модуль имеет **импорт из другого модуля** со своего слоя - - Возможно, модуль был **излишне раздроблен** или имеет **лишнюю ответственность.** - - Следует **объединить** его с импортируемым модулем или **вынести его (частично или целиком) на слой ниже** или перенести логику связей в модули на вышестоящих слоях. -1. Модуль **импортируется многими модулями** со своего слоя - - Возможно, модуль имеет **лишнюю ответственность.** - - Следует **вынести его (частично или целиком) на слой ниже**, либо перенести логику связей в модули на вышестоящих слоях. -1. Модуль **имеет импорты из множества модулей** со своего слоя - - Возможно, модуль принадлежит к **другой области ответственности.** - - Следует **вынести его (частично или целиком) на слой выше**. - -## См. также {#see-also} - -- [(Гайд) Про достижение низкой связанности][refs-low-coupling] -- [(Обсуждение) Entities в методологии и их связность](https://github.com/feature-sliced/documentation/discussions/49) -- [(Обсуждение) Про cross-импорты и анализ зависимостей](https://github.com/feature-sliced/documentation/discussions/65#discussioncomment-480822) -- [Паттерны **GRASP**](https://ru.wikipedia.org/wiki/GRASP) - -[refs-public-api]: /docs/reference/public-api -[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/layers.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/reference/layers.mdx index 1be351e659..278efd4112 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/layers.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/reference/layers.mdx @@ -3,157 +3,124 @@ sidebar_position: 1 pagination_next: reference/slices-segments --- -# Слои - -Слои - это первый уровень организационной иерархии в Feature-Sliced Design. Их цель - разделить код на основе того, сколько ответственности ему требуется и от скольких других модулей в приложении он зависит. - -:::note +import useBaseUrl from "@docusaurus/useBaseUrl"; -На этой странице _модуль_ означает внутренний модуль в приложении - файл или каталог с индексным файлом. Не путать с npm-пакетами. - -::: +# Слои -Каждый слой несет в себе особый семантический смысл, помогающий определить, какую ответственность следует возложить на модуль в вашем коде. Названия и значения слоёв стандартизированы для всех проектов, построенных с использованием Feature-Sliced Design. +Слои - это первый уровень организационной иерархии в Feature-Sliced Design. Их цель - разделить код на основе того, сколько ответственности ему требуется и от скольких других модулей в приложении он зависит. Каждый слой несет особое семантическое значение, чтобы помочь вам определить, сколько ответственности следует выделить вашему коду. -Всего существует **7 слоёв**, расположенных от наибольшей ответственности и зависимости к наименьшей: +Всего существует **7 слоев**, расположенных от наибольшей ответственности и зависимости к наименьшей: -Дерево файловой системы с одной корневой папкой src и семью подпапками: app, processes, pages, widgets, features, entities, shared. Папка processes отображена немного тускло. -Дерево файловой системы с одной корневой папкой src и семью подпапками: app, processes, pages, widgets, features, entities, shared. Папка processes отображена немного тускло. +Дерево файловой системы с одной корневой папкой под названием src и семью подпапками: app, processes, pages, widgets, features, entities, shared. Папка processes слегка выцвечена. +Дерево файловой системы с одной корневой папкой под названием src и семью подпапками: app, processes, pages, widgets, features, entities, shared. Папка processes слегка выцвечена. -1. App (Приложение) +1. App (Эпп) 2. Processes (Процессы, устаревший слой) 2. Pages (Страницы) 3. Widgets (Виджеты) 4. Features (Фичи/функции) 5. Entities (Сущности) -6. Shared (Общий) +6. Shared (Шэред) + +Вы не обязаны использовать все слои в своем проекте - добавляйте только те, что приносят пользу вашему проекту. Как правило, в большинстве фронтенд-проектов будут как минимум слои Shared, Pages и App. + +На практике слои представляют собой папки с названиями в нижнем регистре (например, `📁 shared`, `📁 pages`, `📁 app`). Добавление новых слоев _не рекомендуется_, так как их семантика стандартизирована. + +## Правило импорта слоев -Вы не обязаны использовать все слои в своем проекте - добавляйте только те, что приносят пользу вашему проекту. +Слои состоят из _слайсов_ — высокосвязанных групп модулей. Зависимости между слайсами регулируются **правилом импорта слоев**: -## Правило импортов для слоёв +> _Модуль (файл) в слайсе может импортировать другие слайсы только если они находятся на слоях строго ниже._ -Слои состоят из _слайсов_ — сильно сцепленных групп модулей. Feature-Sliced Design поддерживает низкую связанность, поэтому зависимости между слайсами регулируются **правилом импортов для слоёв**: +Например, папка `📁 ~/features/aaa` является слайсом с именем "aaa". Файл внутри нее, `~/features/aaa/api/request.ts`, не может импортировать код из любого файла в `📁 ~/features/bbb`, но может импортировать код из `📁 ~/entities` и `📁 ~/shared`, а также любой код из `📁 ~/features/aaa`, например, `~/features/aaa/lib/cache.ts`. -> _Модуль в слайсе может импортировать другие слайсы только в том случае, если они расположены на слоях строго ниже._ +Слои App и Shared являются **исключениями** из этого правила — они одновременно являются и слоем, и слайсом. Слайсы разделяют код по бизнес-доменам, и эти два слоя являются исключениями, потому что в Shared нет бизнес-доменов, а App объединяет все бизнес-домены. -Например, в `~/features/aaa`, слайсом является `aaa`, поэтому файл `~/features/aaa/api/request.ts` не может импортировать код ни из какого модуля в папку из `~/features/bbb`, но может импортировать код из `~/entities` и `~/shared`, а также из соседних модулей в `~/features/aaa`. +На практике это означает, что слои App и Shared состоят из сегментов, и сегменты могут свободно импортировать друг друга. ## Определения слоёв +В этом разделе описывается семантическое значение каждого слоя, чтобы создать интуитивное представление о том, какой код в нём будет лежать. + ### Shared -Изолированные модули, компоненты и абстракции, отдельные от специфики проекта или бизнеса. -Внимание: не следует использовать этот слой как [свалку утилит][ext--sova-utility-dump]! +Этот слой формирует фундамент для остальной части приложения. Это место для создания связей с внешним миром, например, бэкенды, сторонние библиотеки, среда выполнения приложения (environment). Также это место для ваших собственных библиотек, сконцентрированных на конкретной задаче. + +Этот слой, как и слой App, _не содержит слайсов_. Слайсы предназначены для разделения слоя на предметные области, но предметные области не существуют в Shared. Это означает, что все файлы в Shared могут ссылаться и импортировать друг друга. -Этот слой, в отличие от других, состоит не из слайсов, а непосредственно из сегментов. +Вот сегменты, которые вы обычно можете найти в этом слое: -**Примеры содержимого**: +- `📁 api` — API-клиент и, возможно, функции для выполнения запросов к конкретным эндпоинтам бэкенда. +- `📁 ui` — UI-кит приложения. + Компоненты на этом слое не должны содержать бизнес-логику, но могут быть тематически связаны с бизнесом. Например, здесь можно разместить логотип компании и макет страницы. Компоненты с UI-логикой также допустимы (например, автозаполнение или строка поиска). +- `📁 lib` — коллекция внутренних библиотек. + Эта папка не должна рассматриваться как хелперы или утилиты ([прочитайте здесь, почему эти папки часто превращаются в свалку][ext-sova-utility-dump]). Вместо этого каждая библиотека в этой папке должна иметь одну область фокуса, например, даты, цвета, манипуляции с текстом и т.д. Эта область фокуса должна быть задокументирована в файле README. Разработчики в вашей команде должны знать, что можно и что нельзя добавлять в эти библиотеки. +- `📁 config` — переменные окружения, глобальные фиче-флаги и другая глобальная конфигурация для вашего приложения. +- `📁 routes` — константы маршрутов или шаблоны для сопоставления маршрутов. +- `📁 i18n` — код, настраивающий систему переводов, а также глобальные строки перевода. -* UI-библиотека -* API-клиент -* Код, работающий с API браузера +Вы можете добавлять ещё сегментов, но убедитесь, что название этих сегментов описывает цель содержимого, а не его суть. Например, `components`, `hooks` и `types` — плохие имена сегментов, поскольку они не очень полезны при поиске кода. ### Entities -Понятия из реального мира, которые вместе образуют суть проекта. Как правило, это термины, которые бизнес использует для описания продукта. +Слайсы на этом слое представляют концепции из реального мира, с которыми работает проект. Обычно это термины, которые бизнес использует для описания продукта. Например, социальная сеть может работать с бизнес-сущностями, такими как Пользователь, Публикация и Группа. -Каждый слайс этого слоя содержит статические элементы пользовательского интерфейса, хранилища данных и операции CRUD (создание-чтение-изменение-удаление). +Слайс сущности может содержать хранилище данных (`📁 model`), схемы валидации данных (`📁 model`), функции запросов API, связанные с сущностями (`📁 api`), а также визуальное представление этой сущности в интерфейсе (`📁 ui`). Это визуальное представление не обязательно должно создавать полный блок пользовательского интерфейса — оно в первую очередь предназначено для переиспользования одного и того же внешнего вида на нескольких страницах приложения, и к нему может быть присоединена различная бизнес-логика через пропы или слоты. -**Примеры слайсов**: +#### Связи между сущностями - - -
Для социальной сети Для Git-фронтенда (например, GitHub)
    -
  • Пользователь
  • -
  • Публикация
  • -
  • Группа
  • -
    -
  • Репозиторий
  • -
  • Файл
  • -
  • Коммит
  • -
+Сущности в FSD являются слайсами, и по умолчанию слайсы не могут знать друг о друге. Однако в реальной жизни сущности часто взаимодействуют друг с другом, и иногда одна сущность владеет или содержит другие сущности. Из-за этого бизнес-логику этих взаимодействий лучше всего хранить на более высоких уровнях, таких как Features или Pages. +Когда объект данных одной сущности содержит другие объекты данных, обычно хорошей идеей является сделать связь между сущностями явной и обойти изоляцию слайсов, создав API для кросс-ссылок через `@x`-нотацию. Причина в том, что связанные сущности должны рефакториться вместе, поэтому лучше сделать так, чтоб связь было невозможно не заметить. -:::tip +Например: -Вы можете заметить в примере фронтенда для Git, что _репозиторий_ содержит _файлы_. Это делает репозиторий сущностью более высокого уровня, внутри которой вложены другие сущности. Это частая ситуация с сущностями, и иногда трудно иметь такие сущности высокого уровня, не нарушая правило импортов для слоёв. +```ts title="entities/artist/model/artist.ts" +import type { Song } from "entities/song/@x/artist"; -Вот несколько предложений по решению этой проблемы: -* UI сущностей должен содержать слоты для мест, куда будут вставляться сущности нижнего уровня -* Бизнес-логика, связанная с взаимодействием сущностей, должна быть размещена в Features (в большинстве случаев) -* Интерфейсы сущностей из базы данных могут быть извлечены в слой Shared, рядом с API-клиентом +export interface Artist { + name: string; + songs: Array; +} +``` -::: +```ts title="entities/song/@x/artist.ts" +export type { Song } from "../model/song.ts"; +``` + +Вы можете узнать больше о `@x`-нотации в разделе [Публичный API для кросс-импортов][public-api-for-cross-imports]. ### Features -Действия, которые пользователь может совершать в приложении для взаимодействия с бизнес-сущностями, чтоб достичь ценного для себя результата. Сюда также входят действия, которые приложение выполняет от имени пользователя, чтобы создать для него ценность. +Этот слой предназначен для основных взаимодействий в вашем приложении, действий, которые важны вашим пользователям. Эти взаимодействия часто затрагивают бизнес-сущности, поскольку сущности — это то, о чём ваше приложение. -Слайс на этом слое может содержать _интерактивные_ элементы пользовательского интерфейса, внутреннее состояние и запросы к API, которые позволяют выполнять действия, создающие ценность. +Важный принцип эффективного использования слоя Features: **не все должно быть фичей**. Хорошим показателем того, что что-то должно быть фичей, является тот факт, что оно переиспользуется. Например, если в приложении есть несколько редакторов, и у всех них есть комментарии, то комментарии являются переиспользуемой фичей. Помните, что слайсы — это механизм для быстрого поиска кода, и если фич слишком много, важные фичи теряются. -**Примеры слайсов**: +В идеале, когда вы приходите в новый проект, вы узнаёте о его функциональности, просматривая страницы и фичи. Принимая решение о том, что должно быть функцией, оптимизируйте опыт новичка в проекте, который/ая хочет быстро обнаружить большие важные области кода. - - -
Для социальной сети Для Git-фронтенда (например, GitHub) Действия от имени пользователя
    -
  • Авторизоваться
  • -
  • Создать публикацию
  • -
  • Вступить в группу
  • -
    -
  • Изменить файл
  • -
  • Оставить комментарий
  • -
  • Слить ветки
  • -
    -
  • Автоматически включить тёмную тему
  • -
  • Выполнить вычисления в фоне
  • -
  • Действия на основе User-Agent
  • -
+Слайс фичи может содержать UI для выполнения действия, например, форму (`📁 ui`), вызовы API, необходимые для выполнения действия (`📁 api`), валидацию и внутреннее состояние (`📁 model`), фиче-флаги (`📁 config`). ### Widgets -Самодостаточные блоки пользовательского интерфейса возникли из композиции единиц более низкого уровня, таких как сущности и функции. +Слой Widgets предназначен для больших самодостаточных блоков интерфейса. Виджеты наиболее полезны, когда они используются на нескольких страницах или когда страница, к которой они принадлежат, имеет несколько больших независимых блоков, и это один из них. -Этот слой предоставляет возможность заполнить слоты, оставленные в интерфейсе сущностей, другими сущностями и интерактивными элементами из фич. Поэтому обычно на этом слое не размещается бизнес-логика, вместо этого она хранится в фичах. Каждый слайс на этом слое содержит готовые к использованию компоненты пользовательского интерфейса и иногда не-бизнес-логику, например, жесты, взаимодействие с клавиатурой и т.д. +Если блок интерфейса составляет бо́льшую часть интересного контента на странице и никогда не используется повторно, он **не должен быть виджетом**, и вместо этого его следует разместить непосредственно на этой странице. -Иногда удобнее разместить бизнес-логику на этом слое. Зачастую, это происходит тогда, когда виджет имеет довольно много интерактивности (например, интерактивные таблицы) и бизнес-логика в нём не переиспользуется. - -**Примеры слайсов**: +:::tip - - -
Для социальной сети Для Git-фронтенда (например, GitHub)
    -
  • Карточка публикации
  • -
  • Шапка профиля пользователя (с действиями)
  • -
    -
  • Список файлов в репозитории (с действиями)
  • -
  • Комментарий в ветке комментариев
  • -
  • Карточка репозитория
  • -
+Если вы используете систему вложенного роутинга (например, роутер [Remix][ext-remix]), может быть полезно использовать слой Widgets так же, как плоская система роутинга использует слой Pages — для создания полных блоков роутинга, включая связанные запросы данных, состояния загрузки и границы ошибок. -:::tip +Таким же образом, вы можете хранить лейауты страниц на этом слое. -Если вы используете вложенную систему маршрутизации (например, роутер [Remix][ext--remix]), может быть полезно использовать слой Widgets аналогично слою Pages в плоской системе маршрутизации - для создания полноценных интерфейсных блоков с получением соответствующих данных с бэкенда, состояниями загрузки и ограничителями ошибок. Также здесь можно разместить лейауты для слоя Pages. ::: ### Pages -Полноценные страницы для страничных приложений (например, веб-сайтов) или экраны/активности для экранных приложений (например, мобильных приложений). - -По своей композиционной природе этот слой похож на Widgets, хоть и в большем масштабе. Каждый слайс на этом слое содержит компоненты пользовательского интерфейса, готовые к подключению к роутеру, а также может содержать логику получения данных и обработки ошибок. +Страницы — это то, из чего состоят веб-сайты и приложения (также называются "экраны" или "активности"). Одна страница обычно соответствует одному слайсу, однако, если есть несколько очень похожих страниц, их можно сгруппировать в один слайс, например, формы регистрации и входа. -**Примеры слайсов**: +Нет никаких ограничений на количество кода, которое можно разместить в слайсе страницы, по крайней мере, до тех пор, пока вашей команде не станет сложно ориентироваться в ней. Если блок интерфейса со страницы не переиспользуется на других страницах, вполне допустимо оставить его внутри слайса страницы. - - -
Для социальной сети Для Git-фронтенда (например, GitHub)
    -
  • Лента новостей
  • -
  • Страница сообщества
  • -
  • Публичный профиль пользователя
  • -
    -
  • Страница репозитория
  • -
  • Репозитории пользователя
  • -
  • Ветки в репозитории
  • -
+В слайсе страницы вы обычно найдете интерфейс страницы, а также состояния загрузки и границы ошибок (`📁 ui`). Также вы можете найти там запросы на получение и изменение данных (`📁 api`). Обычно у страницы нет выделенной модели данных, и небольшие части состояния могут храниться в самих компонентах. ### Processes @@ -165,20 +132,21 @@ pagination_next: reference/slices-segments Выход из ситуаций, когда требуется сложное многостраничное взаимодействие. -Этот уровень намеренно оставлен не очень определенным. Большинству приложений этот слой не пригодится, логику на уровне роутера и сервера следует оставить на уровне App. Рассмотрите возможность использования этого слоя только тогда, когда слой App вырастет настолько, что станет неподдерживаемым и потребует разгрузки. +Этот уровень намеренно оставлен не очень определенным. Большинству приложений этот слой не пригодится, логику на уровне роутера и сервера следует оставить на уровне App. Используйте этот слой только тогда, когда слой App вырастет настолько, что станет неподдерживаемым и потребует разгрузки. ### App -Всё, что касается всего приложения, как в техническом смысле (например, провайдеры контекста), так и в бизнес-смысле (например, аналитика). +Всё, что касается приложения целиком, как в техническом смысле (например, провайдеры контекста), так и в бизнес-смысле (например, аналитика). -Этот слой обычно не содержит слайсов, как и Shared, вместо этого он содержит непосредственно сегменты. +Этот слой обычно не содержит слайсов, как и Shared, и вместо этого внутри него сразу находятся сегменты. -**Примеры содержимого**: +Вот сегменты, которые вы обычно можете найти в этом слое: -* Стили -* Роутер -* Хранилища данных и прочие провайдеры контекста -* Инициализация аналитики +- `📁 routes` — конфигурация роутера +- `📁 store` — глобальная конфигурация хранилища +- `📁 styles` — глобальные стили +- `📁 entrypoint` — точка входа в код приложения, специфичная для вашего фреймворка -[ext--remix]: https://remix.run -[ext--sova-utility-dump]: https://sova.dev/ru/why-utils-and-helpers-is-a-dump/ +[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports +[ext-remix]: https://remix.run +[ext-sova-utility-dump]: https://sergeysova.com/ru/why-utils-and-helpers-is-a-dump/ diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/public-api.md b/i18n/ru/docusaurus-plugin-content-docs/current/reference/public-api.md index 0b68c4e58f..c7c01d60a9 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/public-api.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/reference/public-api.md @@ -1,218 +1,156 @@ --- sidebar_position: 3 -sidebar_label: Public API -pagination_next: about/index --- -# Публичное API модуля приложения +import useBaseUrl from "@docusaurus/useBaseUrl"; -Каждая сущность методологии проектируется как **удобный в использовании и интеграции модуль.** +# Публичный API -## Цели {#goals} +Публичный API — это _контракт_ между группой модулей, например, слайсом, и кодом, который его использует. Он также действует как ворота, разрешая доступ только к определенным объектам и только через этот публичный API. -Удобство использования и интеграции модуля достигается через выполнение *ряда целей*: +На практике это обычно реализуется как индексный файл с реэкспортами: -1. Приложение должно быть **защищено от изменений** внутренней структуры отдельных модулей -1. Переработка внутренней структуры модуля **не должна затрагивать** другие модули -1. Существенные изменения поведения модуля должны быть **легко определяемы** - > **Существенные изменения поведения модуля** - изменения, ломающие ожидания сущностей-пользователей модуля. - -Достичь этих целей позволяет введение публичного интерфейса (Public API), представляющего собой единую точку доступа к возможностям модуля и определяющего "контракт" взаимодействия модуля с внешним миром. - -:::info Важно - -Структура сущности должна иметь единую точку входа, предоставляющую публичный интерфейс - -::: - -```sh -└── features/                        #  - └── auth-form/                  # Внутренняя структура фичи - ├── ui/                    # - ├── model/                 # - ├── {...}/                 # - └── index.ts               # Энтрипоинт фичи с ее публичным API +```js title="pages/auth/index.js" +export { LoginPage } from "./ui/LoginPage"; +export { RegisterPage } from "./ui/RegisterPage"; ``` -```ts title=**/**/index.ts -export { Form as AuthForm } from "./ui" -export * as authFormModel from "./model" -``` - -## Требования к публичному API {#requirements-for-the-public-api} - -Выполнение этих требований позволяет свести взаимодействие с модулем к **выполнению публичного интерфейса-контракта** и, тем самым, достичь надежности и удобства в использовании модуля. - -### 1. Контроль доступа {#1-access-control} - -Public API должен осуществлять **контроль доступа** к содержимому модуля - -- Другие части приложения могут использовать **только те сущности модуля, которые представлены в публичном интерфейсе** -- Внутренняя часть модуля за пределами публичного интерфейса **доступны только самому модулю**. - -#### Примеры {#examples} +## Что делает публичный API хорошим? -##### Отстранение от приватных импортов {#suspension-from-private-imports} +Хороший публичный API делает использование и интеграцию слайса в другой код удобным и надежным. Этого можно достичь, поставив три цели: -- **Плохо**: Идет обращение напрямую к внутренним частям модуля, минуя публичный интерфейс доступа - опасно, особенно при рефакторинге модуля +1. Остальная часть приложения должна быть защищена от структурных изменений в слайсе, таких как рефакторинг. +2. Значительные изменения в поведении слайса, которые нарушают предыдущие ожидания, должны вызывать изменения в публичном API. +3. Только необходимые части слайса должны быть доступны. - ```diff - - import { Form } from "features/auth-form/components/view/form" - ``` +Последняя цель имеет важные практические последствия. Может возникнуть соблазн создать слепые реэкспорты всего, особенно на ранних этапах разработки слайса, потому что любые новые объекты, которые вы экспортируете из своих файлов, также автоматически экспортируются из слайса: -- **Хорошо:** API заранее экспортирует только нужное и разрешенное, разработчику модуля теперь нужно думать только о том, чтобы не ломать Public API при рефакторинге - - ```diff - + import { AuthForm } from "features/auth-form" - ``` - -### 2. Устойчивость к изменениям {#2-sustainability-for-changes} - -Public API должен быть **устойчивым к изменениям** внутри модуля - -- Изменения, ломающие поведения модуля, должны отражаться в изменении Public API - -#### Примеры {#examples} - -##### Абстрагирование от реализации {#abstracting-from-the-implementation} +```js title="Плохая практика, features/comments/index.js" +// ❌ НИЖЕ ПЛОХОЙ КОД, НЕ ДЕЛАЙТЕ ТАК +export * from "./ui/Comment"; // 👎 не пытайтесь повторить дома +export * from "./model/comments"; // 💩 это плохая практика +``` -Изменение внутренней структуры не должно приводить к изменению Public API +Это ухудшает понимаемость слайса беглым взглядом, потому что вы не можете легко определить, каков интерфейс этого слайса. Не зная интерфейс, вам придется глубоко погружаться в код слайса, чтобы понять, как его интегрировать. Еще одна проблема заключается в том, что вы можете случайно раскрыть внутренние модули, что усложнит рефакторинг, если кто-то начнет от них зависеть. -- **Плохо:** перемещение или переименование этого компонента внутри фичи приведет к необходимости рефакторить импорты во всех местах использования компонента. +## Публичный API для кросс-импортов {#public-api-for-cross-imports} - ```diff - - import { Form } from "features/auth-form/ui/form" - ``` +Кросс-импорты — это ситуация, когда один слайс импортирует из другого слайса на том же слое. Обычно это запрещено [правилом импорта для слоёв][import-rule-on-layers], но часто есть реальные причины, чтоб сделать кросс-импорт. Например, в реальном мире бизнес-сущности часто ссылаются друг на друга, и лучше отразить эти отношения в коде, а не пытаться избавиться от них. -- **Хорошо:** интерфейс фичи не отображает её внутреннюю структуру, внешние "пользователи" фичи не пострадают от перемещения или переименования компонента внутри фичи +Для этой цели существует особый вид публичного API, также известный как `@x`-нотация. Если у вас есть сущности A и B, и сущность B должна импортировать из сущности A, то сущность A может объявить отдельный публичный API только для сущности B. - ```diff - + import { AuthForm } from "features/auth-form" - ``` +- `📂 entities` + - `📂 A` + - `📂 @x` + - `📄 B.ts` — специальный публичный API только для кода внутри `entities/B/` + - `📄 index.ts` — обычный публичный API -### 3. Интегрируемость {#3-integrability} +Затем код внутри `entities/B/` может импортировать из `entities/A/@x/B`: -Public API должен способствовать **легкой и гибкой интеграции** +```ts +import type { EntityA } from "entities/A/@x/B"; +``` -- Должен быть удобен для использования остальными частями приложения, в частности, решать проблему коллизии имен +Нотацию `A/@x/B` следует читать как "пересечение A и B". -#### Примеры {#examples} +:::note -##### Коллизия имен {#name-collision} +Старайтесь свести кросс-импорты к минимуму и **используйте эту нотацию только на уровне Entities**, где устранение кросс-импортов часто неразумно. -- **Плохо:** будет коллизия имен +::: - ```ts title=features/auth-form/index.ts - export { Form } from "./ui" - export * as model from "./model" - ``` +## Проблемы с индексными файлами - ```ts title=features/post-form/index.ts - export { Form } from "./ui" - export * as model from "./model" - ``` +Индексные файлы, такие как `index.js`, также известные как barrel-файлы (файлы-бочки), являются самым распространенным способом определения публичного API. Их легко создать, но они иногда вызывают проблемы с некоторыми сборщиками и фреймворками. - ```diff - - import { Form, model } from "features/auth-form" - - import { Form, model } from "features/post-form" - ``` +### Циклические импорты -- **Хорошо:** коллизия решена на уровне интерфейса +Циклический импорт — это когда два или более файла импортируют друг друга по кругу. - ```ts title=features/auth-form/index.ts - export { Form as AuthForm } from "./ui" - export * as authFormModel from "./model" - ``` + - ```ts title=features/post-form/index.ts - export { Form as PostForm } from "./ui" - export * as postFormModel from "./model" - ``` +
+ Три файла, импортирующие друг друга по кругу + Три файла, импортирующие друг друга по кругу +
+ На изображении выше: три файла, `fileA.js`, `fileB.js` и `fileC.js`, импортирующие друг друга по кругу. +
+
- ```diff - + import { AuthForm, authFormModel } from "features/auth-form" - + import { PostForm, postFormModel } from "features/post-form" - ``` +Эти ситуации часто трудно обрабатывать сборщикам, и в некоторых случаях они могут даже привести к ошибкам во время выполнения кода, которые может быть трудно отладить. -##### Гибкое использование {#flexible-use} +Циклические импорты могут возникать и без индексных файлов, но наличие индексного файла создает явную возможность случайно создать циклический импорт. Это часто происходит, когда у вас есть два объекта, доступных в публичном API слайса, например, `HomePage` и `loadUserStatistics`, и `HomePage` нужно получить доступ к `loadUserStatistics`, но он делает это следующим образом: -- **Плохо:** неудобно писать, неудобно читать, "пользователь" фичи страдает +```jsx title="pages/home/ui/HomePage.jsx" +import { loadUserStatistics } from "../"; // импортируем из pages/home/index.js - ```diff - - import { storeActionUpdateUserDetails } from "features/auth-form" - - dispatch(storeActionUpdateUserDetails(...)) - ``` +export function HomePage() { /* … */ } +``` -- **Хорошо:** "пользователь" фичи получает доступ к нужным вещам итеративно и гибко +```js title="pages/home/index.js" +export { HomePage } from "./ui/HomePage"; +export { loadUserStatistics } from "./api/loadUserStatistics"; +``` - ```diff - + import { authFormModel } from "features/auth-form" - + dispatch(authFormModel.effects.updateUserDetails(...)) // redux - + authFormModel.updateUserDetailsFx(...) // effector - ``` +Эта ситуация создает циклический импорт, потому что `index.js` импортирует `ui/HomePage.jsx`, но `ui/HomePage.jsx` тоже импортирует `index.js`. -##### Разрешение коллизий {#resolution-of-collisions} +Чтобы предотвратить эту проблему, воспользуйтесь этими принципами. Если у вас есть два файла, и один импортирует из другого: +- Если они находятся в одном слайсе, всегда используйте _относительные_ импорты и пишите полный путь импорта +- Если они находятся в разных слайсах, всегда используйте _абсолютные_ импорты, например, через алиас -Коллизия имен должна решаться на уровне публичного интерфейса, а не реализации +### Большие бандлы и неработающий tree-shaking в Shared {#large-bundles} -- **Плохо:** коллизия имен решается на уровне реализации +Некоторым сборщикам может быть трудно выполнять tree-shaking (удаление неимпортированного кода), когда у вас есть индексный файл, который реэкспортирует все. - ```ts title=features/auth-form/index.ts - export { AuthForm } from "./ui" - export { authFormActions, authFormReducer } from "model" - ``` +Обычно это не проблема для публичных API, потому что содержимое модуля обычно довольно тесно связано, поэтому вам редко нужно импортировать одну вещь, но удалить другую. Однако есть два очень распространенных случая, когда обычные правила публичного API в FSD могут привести к проблемам — `shared/ui` и `shared/lib`. - ```ts title=features/post-form/index.ts - export { PostForm } from "./ui" - export { postFormActions, postFormReducer } from "model" - ``` +Эти две папки являются коллекциями несвязанных вещей, которые часто не нужны все в одном месте. Например, в `shared/ui` могут быть модули для каждого компонента в библиотеке UI: -- **Хорошо:** коллизия имен решается на уровне интерфейса +- `📂 shared/ui/` + - `📁 button` + - `📁 text-field` + - `📁 carousel` + - `📁 accordion` - ```ts title=features/auth-form/model.ts - export { actions, reducer } - ``` +Эта проблема усугубляется, когда один из этих модулей имеет тяжелую зависимость, такую как подсветка синтаксиса или библиотека drag'n'drop. Вы не хотите подтягивать их на каждую страницу, которая использует что-то из `shared/ui`, например, кнопку. - ```ts title=features/auth-form/index.ts - export { Form as AuthForm } from "./ui" - export * as authFormModel from "./model" - ``` +Если ваши бандлы нежелательно растут из-за единого публичного API в `shared/ui` или `shared/lib`, рекомендуется вместо этого иметь отдельный индексный файл для каждого компонента или библиотеки: - ```ts title=features/post-form/model.ts - export { actions, reducer } - ``` +- `📂 shared/ui/` + - `📂 button` + - `📄 index.js` + - `📂 text-field` + - `📄 index.js` - ```ts title=features/post-form/index.ts - export { Form as PostForm } from "./ui" - export * as postFormModel from "./model" - ``` +Тогда потребители этих компонентов могут импортировать их напрямую, как показано ниже: -## О реэкспортах {#about-re-exports} +```js title="pages/sign-in/ui/SignInPage.jsx" +import { Button } from '@/shared/ui/button'; +import { TextField } from '@/shared/ui/text-field'; +``` -В JavaScript публичный интерфейс модуля создается с помощью реэкспорта сущностей изнутри модуля в `index` файле: +### Нет реальной защиты от обхода публичного API -```ts title=**/**/index.ts -export { Form as AuthForm } from "./ui" -export * as authModel from "./model" -``` +Когда вы создаете индексный файл для слайса, ничто не мешает другим не использовать его и импортировать напрямую. Это особенно заметно с автоимпортами, потому что существует несколько мест, откуда объект может быть импортирован, поэтому IDE должна решить за вас. Иногда она может выбрать прямой импорт, нарушая правило публичного API для слайсов. -### Недостатки {#disadvantages} +Чтобы автоматически выявлять эти проблемы, мы рекомендуем использовать [Steiger][ext-steiger], архитектурный линтер с набором правил для Feature-Sliced Design. -- В большинстве популярных бандлеров из-за реэкспортов **хуже работает код-сплиттинг**, т.к. [tree-shaking](https://webpack.js.org/guides/tree-shaking/) при таком подходе может безопасно отбросить только модуль целиком, но не его часть. - > Например, импорт `authModel` в модели страницы приведет к попаданию компонента `AuthForm` в чанк этой страницы, даже если этот компонент там не используется. +### Худшая производительность сборщиков на больших проектах -- Как следствие, инициализация чанка становится дороже, т.к. браузер должен обработать все модули в нем, в том числе и те, что попали в бандл "за компанию" +Наличие большого количества индексных файлов в проекте может замедлить работу сервера разработки, как отметил TkDodo в [своей статье "Please Stop Using Barrel Files"][ext-please-stop-using-barrel-files]. -### Возможные пути решения {#possible-solutions} +Есть несколько вещей, которые вы можете сделать, чтобы справиться с этой проблемой: -- `webpack` позволяет отметить файлы-реэкспорты как [**side effects free**](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free) - это разрешает `webpack` использовать более агрессивные оптимизации при работе с таким файлом +1. То же самое, что и в разделе "Большие бандлы и неработающий tree-shaking в Shared" — создайте отдельные индексные файлы для каждого компонента/библиотеки в shared/ui и shared/lib вместо одного большого +2. Избегайте наличия индексных файлов в сегментах на слоях, которые имеют слайсы. + Например, если у вас есть индекс для фичи "comments", `📄 features/comments/index.js`, нет смысла иметь еще один индекс для `ui` сегмента этой фичи, `📄 features/comments/ui/index.js`. +3. Если у вас очень большой проект, есть большая вероятность, что ваше приложение можно разделить на несколько больших кусков. + Например, у Google Docs очень разные обязанности для редактора документов и для файлового браузера. Вы можете создать монорепозиторий, где каждый пакет является отдельным корнем FSD со своим набором слоев. Некоторые пакеты могут иметь только слои Shared и Entities, другие могут иметь только Pages и App, а некоторые могут включать свой небольшой Shared, но при этом использовать большой Shared из другого пакета. -## См. также {#see-also} + -- [(Обсуждение) Public API абстракции][disc-src] -- [Принципы **SOLID**][ext-solid] -- [Паттерны **GRASP**][ext-grasp] + -[disc-src]: https://github.com/feature-sliced/documentation/discussions/41 -[ext-solid]: https://ru.wikipedia.org/wiki/SOLID -[ext-grasp]: https://ru.wikipedia.org/wiki/GRASP +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-please-stop-using-barrel-files]: https://tkdodo.eu/blog/please-stop-using-barrel-files diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx index a0b81db38f..1600db03e2 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx @@ -4,54 +4,70 @@ sidebar_position: 2 pagination_next: reference/public-api --- +import useBaseUrl from "@docusaurus/useBaseUrl"; + # Слайсы и сегменты ## Слайсы -Слайсы - это второй уровень в организационной иерархии Feature-Sliced Design. Их основное назначение - группировать код по его значению для продукта, бизнеса или просто приложения. +Слайсы — это второй уровень в организационной иерархии Feature-Sliced Design. Их основное назначение — группировать код по его значению для продукта, бизнеса или просто приложения. + +Имена слайсов не стандартизированы, поскольку они напрямую определяются бизнес-областью вашего приложения. Например, фотогалерея может иметь слайсы `photo`, `effects`, `gallery-page`. Для социальной сети потребуются другие слайсы, например, `post`, `comments`, `news-feed`. + +Слои Shared и App не содержат слайсов. Это потому, что Shared не должен содержать никакой бизнес-логики, следовательно, не имеет продуктового значения, а App должен содержать только код, касающийся всего приложения, поэтому разделение не требуется. + +### Нулевая сцепленность и высокая связность {#zero-coupling-high-cohesion} -Имена слайсов не стандартизированы, поскольку они напрямую определяются бизнес-областью вашего приложения. Например, фотогалерея может иметь слайсы `photo`, `create-album`, `gallery-page`. Для социальной сети потребуются другие слайсы, например, `post`, `add-user-to-friends`, `news-feed`. +Слайсы задуманы как независимые и сильно связанные группы файлов кода. Картинка ниже может помочь визуализировать такие сложные концепции как _связность_ (cohesion) и _сцепленность_ (coupling): -Близко связанные слайсы могут быть структурно сгруппированы в одной папке, но они должны соблюдать те же правила изоляции, что и другие слайсы - в этом каталоге не должно быть **никакого кода для совместного использования несколькими слайсами**. +
+ + +
+ Картинка вдохновлена https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/ +
+
-![Фичи "compose" (написать), "like" (отметить как понравившееся) и "delete" (удалить), сгруппированные в папку "post" (публикация). В этой папке также есть файл "some-shared-code.ts" (какой-то общий код), название которого перечеркнуто, что означает, что этот файл не должен там быть.](/img/graphic-nested-slices.svg) +Идеальный слайс независим от других слайсов на своем уровне (нулевая сцепленность) и содержит бо́льшую часть кода, связанного с его основной целью (высокая связность). -Слои Shared и App не содержат слайсов. Это связано с тем, что Shared не должен содержать никакой бизнес-логики, следовательно, не имеет значения для продукта, а App должен содержать только код, относящийся ко всему приложению, поэтому в разбиении нет необходимости. +Независимость слайсов обеспечивается [правилом импорта для слоёв][layers--import-rule]: + +> _Модуль (файл) в слайсе может импортировать другие слайсы только если они находятся на слоях строго ниже._ ### Правило публичного API для слайсов -Внутри слайса код может быть организован как угодно, и это не создаст никаких проблем до тех пор, пока срез имеет качественный публичный API. В этом суть правила **публичного API для слайсов**: +Внутри слайса код может быть организован как угодно, и это не создаст никаких проблем до тех пор, пока слайс имеет качественный публичный API. В этом суть правила **публичного API для слайсов**: > _Каждый слайс (и сегмент на слоях, не имеющих слайсов) должен содержать определение публичного API._ > > _Модули вне этого слайса/сегмента могут ссылаться только на публичный API, а не на внутреннюю файловую структуру этого слайса/сегмента._ -Подробнее о причинах требования публичных API и лучших практиках их создания читайте в [справочнике о публичных API][ref--public-api]. +Подробнее о причинах требования публичных API и лучших практиках их создания читайте в [справочнике о публичных API][ref-public-api]. + +### Группы слайсов + +Тесно связанные слайсы могут быть структурно сгруппированы в папку, но они должны соблюдать те же правила изоляции, что и другие слайсы — **никакого совместного использования кода** в этой папке быть не должно. + +![Функции "compose", "like" и "delete" сгруппированы в папку "post". В этой папке также есть файл "some-shared-code.ts", который зачеркнут, чтобы показать, что это запрещено.](/img/graphic-nested-slices.svg) ## Сегменты -Сегменты - это третий и последний уровень в организационной иерархии, их цель - группировать код по его технической природе. +Сегменты — это третий и последний уровень в организационной иерархии, их цель — группировать код по его техническому назначению. Существует несколько стандартизированных названий сегментов: -* `ui` - компоненты пользовательского интерфейса, функции форматирования данных -* `model` - бизнес-логика и хранилища данных, функции для обработки этих данных -* `lib` - вспомогательный инфраструктурный код -* `api` - взаимодействие с внешними API, API-методы бэкенда -Другие сегменты допускаются, но должны создаваться только по необходимости. Наиболее распространенными местами для других сегментов являются слои App и Shared, где срезы не имеют смысла. +- `ui` — все, что связано с отображением UI: UI-компоненты, форматтеры дат, стили и т.д. +- `api` — взаимодействие с бэкендом: функции запросов, типы данных, мапперы и т.д. +- `model` — модель данных: схемы, интерфейсы, хранилища и бизнес-логика. +- `lib` — библиотечный код, необходимый другим модулям в этом слайсе. +- `config` — конфигурационные файлы и фиче-флаги. -### Примеры +На [странице про Слои][layers--layer-definitions] есть примеры того, как каждый из этих сегментов может использоваться на разных слоях. -| Layer | `ui` | `model` | `lib` | `api` | -| :------- | :----------- | :----------- | :----------- | :----------- | -| Shared | UI-библиотека | Обычно не используется | Утилитарные модули из нескольких связанных файлов.
Если вам нужны индивидуальные вспомогательные функции, обратите внимание на библиотеки утилит, например, [`lodash-es`][ext--lodash]. | Примитивный API-клиент с дополнительными функциями, такими как аутентификация или кэширование. | -| Entities | Скелет бизнес-сущности со слотами для интерактивных элементов | Хранилище объектов этой сущности, а также функции для обработки этих объектов.
Этот сегмент лучше всего подходит для хранения данных с сервера. Если вы используете [TanStack Query][ext--tanstack-query] или другие методы неявного хранения, вы можете опустить этот сегмент. | Функции над объектами этой сущности, не связанные с хранением данных | API-методы, использующие API-клиент из Shared для упрощения коммуникации с бэкендом | -| Features | Интерактивные элементы, позволяющие пользователям использовать эту функцию | Бизнес-логика и хранилище инфраструктурных данных, если требуется (например, текущая тема приложения). Здесь лежит код, который непосредственно создает пользу для пользователя | Инфраструктурный код, который позволяет сегменту `model` более кратко описать бизнес-логику | API-методы, представляющие эту функцию на бэкенде.
Может объединять API-методы из Entities. -| Widgets | Композиция Entities и Features в самодостаточные блоки интерфейса.
Также может содержать ограничители ошибок и состояния загрузки. | Хранилище инфраструктурных данных, если требуется | Не-бизнес-взаимодействия (например, жесты) и прочий код, необходимый для функционирования этого блока на странице | Обычно не используется, но может содержать загрузчики данных в контексте вложенного роутинга (например, [Remix][ext--remix]) | -| Pages | Композиция Entities, Features и Widgets в полноценные страницы.
Также может содержать ограничители ошибок и состояния загрузки. | Обычно не используется | Не-бизнес-взаимодействия (например, жесты) и прочий код, необходимый для создания полноценного пользовательского опыта на этой странице | Загрузчики данных для фреймворков, ориентированных на SSR (рендеринг на сервере) | +Вы также можете создавать свои собственные сегменты. Наиболее распространенные места для кастомных сегментов — это слои App и Shared, где слайсы не имеют смысла. -[ref--public-api]: /docs/reference/public-api +Убедитесь, что название этих сегментов описывает, для чего нужно его содержимое, а не чем оно является. Например, `components`, `hooks` и `types` — плохие названия сегментов, потому что они не так полезны, когда вы ищете код. -[ext--lodash]: https://www.npmjs.com/package/lodash-es -[ext--tanstack-query]: https://tanstack.com/query/latest -[ext--remix]: https://remix.run +[layers--layer-definitions]: /docs/reference/layers#layer-definitions +[layers--import-rule]: /docs/reference/layers#import-rule-on-layers +[ref-public-api]: /docs/reference/public-api diff --git a/i18n/ru/docusaurus-theme-classic/footer.json b/i18n/ru/docusaurus-theme-classic/footer.json index 218431369f..72631c8514 100644 --- a/i18n/ru/docusaurus-theme-classic/footer.json +++ b/i18n/ru/docusaurus-theme-classic/footer.json @@ -16,12 +16,12 @@ "description": "The label of footer link with label=Документация linking to /docs" }, "link.item.label.Community": { - "message": "Сообщество", - "description": "The label of the footer link with label=Community linking to /community" + "message": "Сообщество", + "description": "The label of the footer link with label=Community linking to /community" }, "link.item.label.Help": { - "message": "Помощь", - "description": "The label of the footer link with label=Help linking to /nav" + "message": "Помощь", + "description": "The label of the footer link with label=Help linking to /nav" }, "link.item.label.Discussions": { "message": "Обсуждения", @@ -31,10 +31,6 @@ "message": "Лицензия", "description": "The label of footer link with label=License linking to LICENSE" }, - "link.item.label.Privacy": { - "message": "Конфиденциальность", - "description": "The label of footer link with label=Privacy linking to /docs/privacy" - }, "link.item.label.Contribution Guide (RU)": { "message": "Гайдлайны по контрибьютингу (RU)", "description": "The label of footer link with label=Contribution Guide (RU) linking to CONTRIBUTING.md" @@ -64,7 +60,7 @@ "description": "The label of footer link with label=GitHub linking to https://github.com/feature-sliced" }, "copyright": { - "message": "Copyright © 2018-2023 Feature-Sliced Design", + "message": "Copyright © 2018-2025 Feature-Sliced Design", "description": "The footer copyright" } } diff --git a/i18n/uz/code.json b/i18n/uz/code.json index 6c9d48336a..3364620055 100644 --- a/i18n/uz/code.json +++ b/i18n/uz/code.json @@ -1,366 +1,410 @@ { - "pages.home.features.title": { - "message": "Afzalliklari", - "description": "Features" - }, - "pages.home.features.logic.title": { - "message": "Aniq biznes mantiq", - "description": "Feature title" - }, - "pages.home.features.logic.description": { - "message": "Arxitekturani o'rganish oson, chunki u domen modullaridan iborat", - "description": "Feature description" - }, - "pages.home.features.adaptability.title": { - "message": "Moslashuvchanlik", - "description": "Feature title" - }, - "pages.home.features.adaptability.description": { - "message": "Arxitektura komponentlarini moslashuvchan tarzda almashtirish, yangi sharoitlarda qo'shish mumkin", - "description": "Feature description" - }, - "pages.home.features.debt.title": { - "message": "Texnik qarz", - "description": "Feature title" - }, - "pages.home.features.debt.description": { - "message": "Har bir modul nojo'ya ta'sirlarsiz mustaqil ravishda o'zgartirilishi/qayta yozilishi mumkin", - "description": "Feature description" - }, - "pages.home.features.shared.title": { - "message": "Aniq qayta foydalanish mumkinligi", - "description": "Feature title" - }, - "pages.home.features.shared.description": { - "message": "DRY va lokal moslashtirish o'rtasidagi muvozanatni saqlaydi", - "description": "Feature description" - }, - "pages.home.concepts.title": { - "message": "Tushunchalar", - "description": "Concepts" - }, - "pages.home.concepts.public.title": { - "message": "Ommaviy API", - "description": "Concept title" - }, - "pages.home.concepts.public.description": { - "message": "Har bir modul yuqori darajadagi umumiy API deklaratsiyasiga ega bo'lishi kerak", - "description": "Concept description" - }, - "pages.home.concepts.isolation.title": { - "message": "Izolyatsiya", - "description": "Concept title" - }, - "pages.home.concepts.isolation.description": { - "message": "Modul to'g'ridan-to'g'ri bir xil qatlamdagi boshqa modullarga yoki uning ustidagi qatlamlarga bog'liq bo'lmasligi kerak.", - "description": "Concept description" - }, - "pages.home.concepts.needs.title": { - "message": "Ehtiyojlarni tushunish", - "description": "Concept title" - }, - "pages.home.concepts.needs.description": { - "message": "Biznes va foydalanuvchi ehtiyojlariga e'tibor qarating", - "description": "Concept description" - }, - "pages.home.scheme.title": { - "message": "Sxema", - "description": "Scheme" - }, - "pages.home.companies.using": { - "message": "Metodologiyadan foydalanadigan kompaniyalar", - "description": "Companies using methodology" - }, - "pages.home.companies.add_me": { - "message": "Sizning kompaniyangizda metodologiya qo'llaniladimi?", - "description": "Methodology is used in your company?" - }, - "pages.home.companies.tell_us": { - "message": "Habar bering", - "description": "Tell us" - }, - "pages.examples.title": { - "message": "Namunalar", - "description": "Page title" - }, - "pages.examples.subtitle": { - "message": "FSD yordamida yaratilgan veb-saytlar ro'yxati", - "description": "Page subtitle" - }, - "pages.examples.add_me.title": { - "message": "Namuna qoshish", - "description": "Request to add example" - }, - "pages.examples.repo.title": { - "message": "Repezitoriyalar", - "description": "Examples repository label" - }, - "pages.examples.versions": { - "message": "Shuningdek, versiyalar ro'yxatiga qarang", - "description": "Versions reminder" - }, - "pages.versions.title": { - "message": "Feature-Sliced Design versiyalari", - "description": "Feature-Sliced Design versions" - }, - "pages.versions.current": { - "message": "Bu yerda siz joriy nashr etilgan versiya uchun hujjatlarni topishingiz mumkin", - "description": "Description for current version" - }, - "pages.versions.legacy": { - "message": "Bu yerda {of} ning eski versiyalari uchun hujjatlarni topishingiz mumkin", - "description": "Description for legacy version" - }, - "pages.nav.title": { - "message": "🧭 Navigatsiya", - "description": "NavPage title" - }, - "pages.nav.legacy.title": { - "message": "Eskirgan havolalar", - "description": "NavPage section=legacy title" - }, - "pages.nav.legacy.details": { - "message": "Hujjatlarni qayta tuzilgandan so'ng, maqolalarda ba'zi havolalar o'zgartirildi. Quyida siz izlayotgan sahifani topishingiz mumkin.", - "description": "NavPage section=legacy details" - }, - "pages.nav.legacy.subdetails": { - "message": "Lekin moslik uchun eski havolalardan qayta yo'naltirishlar mavjud", - "description": "NavPage section=legacy subdetails" - }, - "features.cookie-consent.alert": { - "message": "🍰 Biz tahlil qilish uchun cookie-fayllardan foydalanamiz", - "description": "Cookie Consent alert" - }, - "features.cookie-consent.accept": { - "message": "Yaxshi", - "description": "Cookie Consent accept label" - }, - "features.cookie-consent.reason": { - "message": "nimaga?", - "description": "Cookie Consent reason label" - }, - "features.feedback-badge.label": { - "message": "Hujjatlar bo'yicha fikr-mulohazalaringizni baham ko'ring 🤙", - "description": "Feedback share button label" - }, - "features.feedback-badge.url": { - "message": "https://forms.gle/7p4anU2shHAzmfqc8", - "description": "Feedback share form url" - }, - "features.feedback-doc.thanks": { - "message": "Baham ko'rganingiz uchun rahmat!", - "description": "DocFeedback block=Thanks" - }, - "features.feedback-doc.title": { - "message": "Sahifa foydali boldimi?", - "description": "DocFeedback block=Title" - }, - "features.feedback-doc.subtitle": { - "message": "Sizning fikr-mulohazalaringiz hujjatlarni yaxshilashga yordam beradi", - "description": "DocFeedback block=Subtitle" - }, - "features.hero.tagline": { - "message": "Front-end loyihalari uchun arxitektura metodologiyasi", - "description": "Architectural methodology for frontend projects" - }, - "features.hero.get_started": { - "message": "Tanishib chiqish", - "description": "Get Started" - }, - "features.hero.examples": { - "message": "Namunalar", - "description": "Examples" - }, - "features.hero.previous": { - "message": "Oldingi versiya", - "description": "Previous version" - }, - "shared.wip.title": { - "message": "Maqola yozilish jarayonida", - "description": "Admonition title" - }, - "shared.wip.subtitle": { - "message": "Uning yaratilishini tezlashtirish uchun siz:", - "description": "Admonition subtitle" - }, - "shared.wip.var.feedback.base": { - "message": "📢 Fikr-mulohaza ulashing ", - "description": "Variant for contribute (base)" - }, - "shared.wip.var.feedback.link": { - "message": "chiptada (sharhlar/reaksiya emoji)", - "description": "Variant for contribute (link)" - }, - "shared.wip.var.material.base": { - "message": "💬 Mavzu boyicha to'plamlarni chiptaga yig'ish ", - "description": "Variant for contribute (base)" - }, - "shared.wip.var.material.link": { - "message": "suhbatdan olingan material", - "description": "Variant for contribute (link)" - }, - "shared.wip.var.contribute.base": { - "message": "⚒️ Hissa qo‘shish ", - "description": "Variant for contribute (base)" - }, - "shared.wip.var.contribute.link": { - "message": "har qanday boshqa yo'l bilan", - "description": "Variant for contribute (link)" - }, - "theme.NotFound.title": { - "message": "Sahifa topilmadi", - "description": "The title of the 404 page" - }, - "theme.NotFound.p1": { - "message": "Afsuski, siz hohlagan sahifa topilmadi", - "description": "The first paragraph of the 404 page" - }, - "theme.NotFound.p2": { - "message": "Iltimos, ushbu havolaga kirgan sayt egasiga havola ishlamayotganligi haqida xabar bering.", - "description": "The 2nd paragraph of the 404 page" - }, - "theme.AnnouncementBar.closeButtonAriaLabel": { - "message": "Yopish", - "description": "The ARIA label for close button of announcement bar" - }, - "theme.blog.paginator.navAriaLabel": { - "message": "Bloglar roʻyxati sahifalariga navigatsiya", - "description": "The ARIA label for the blog pagination" - }, - "theme.blog.paginator.newerEntries": { - "message": "Keyingi yozmalar", - "description": "The label used to navigate to the newer blog posts page (previous page)" - }, - "theme.blog.paginator.olderEntries": { - "message": "Oldingi yozmalar", - "description": "The label used to navigate to the older blog posts page (next page)" - }, - "theme.blog.post.readingTime.plurals": { - "message": "{readingTime} min. o'qish", - "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" - }, - "theme.tags.tagsListLabel": { - "message": "Teglar:", - "description": "The label alongside a tag list" - }, - "theme.blog.post.readMore": { - "message": "Ko'proq o'qish", - "description": "The label used in blog post item excerpts to link to full blog posts" - }, - "theme.blog.post.paginator.navAriaLabel": { - "message": "Blog postlari sahifalarida navigatsiya", - "description": "The ARIA label for the blog posts pagination" - }, - "theme.blog.post.paginator.newerPost": { - "message": "Keyingi post", - "description": "The blog post button label to navigate to the newer/previous post" - }, - "theme.blog.post.paginator.olderPost": { - "message": "Oldingi post", - "description": "The blog post button label to navigate to the older/next post" - }, - "theme.tags.tagsPageTitle": { - "message": "Teglar", - "description": "The title of the tag list page" - }, - "theme.blog.post.plurals": { - "message": "Bir post|{count} ta post", - "description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" - }, - "theme.blog.tagTitle": { - "message": "{nPosts} \"{tagName}\" bilan", - "description": "The title of the page for a blog tag" - }, - "theme.tags.tagsPageLink": { - "message": "Hamma teglarni ko'rish", - "description": "The label of the link targeting the tag list page" - }, - "theme.CodeBlock.copyButtonAriaLabel": { - "message": "Buferga nusxalash", - "description": "The ARIA label for copy code blocks button" - }, - "theme.CodeBlock.copied": { - "message": "Nuxsalandi", - "description": "The copied button label on code blocks" - }, - "theme.CodeBlock.copy": { - "message": "Nusxalash", - "description": "The copy button label on code blocks" - }, - "theme.docs.sidebar.expandButtonTitle": { - "message": "Yon panelni kengaytirish", - "description": "The ARIA label and title attribute for expand button of doc sidebar" - }, - "theme.docs.sidebar.expandButtonAriaLabel": { - "message": "Yon panelni kengaytirish", - "description": "The ARIA label and title attribute for expand button of doc sidebar" - }, - "theme.docs.paginator.navAriaLabel": { - "message": "Hujjat sahifalarida navigatsiya", - "description": "The ARIA label for the docs pagination" - }, - "theme.docs.paginator.previous": { - "message": "Oldingi sahifa", - "description": "The label used to navigate to the previous doc" - }, - "theme.docs.paginator.next": { - "message": "Keyingi sahifa", - "description": "The label used to navigate to the next doc" - }, - "theme.docs.sidebar.collapseButtonTitle": { - "message": "Yon panelni yig‘ish", - "description": "The title attribute for collapse button of doc sidebar" - }, - "theme.docs.sidebar.collapseButtonAriaLabel": { - "message": "Yon panelni yig‘ish", - "description": "The title attribute for collapse button of doc sidebar" - }, - "theme.docs.sidebar.responsiveCloseButtonLabel": { - "message": "Menyuni yopish", - "description": "The ARIA label for close button of mobile doc sidebar" - }, - "theme.docs.sidebar.responsiveOpenButtonLabel": { - "message": "Menyuni ochish", - "description": "The ARIA label for open button of mobile doc sidebar" - }, - "theme.docs.versions.unreleasedVersionLabel": { - "message": "Bu {siteTitle} {versionLabel} ning kelajakdagi versiyasi uchun hujjatdir.", - "description": "The label used to tell the user that he's browsing an unreleased doc version" - }, - "theme.docs.versions.unmaintainedVersionLabel": { - "message": "Bu endi faol saqlanmaydigan {siteTitle} {versionLabel} uchun hujjatlar.", - "description": "The label used to tell the user that he's browsing an unmaintained doc version" - }, - "theme.docs.versions.latestVersionSuggestionLabel": { - "message": "Eng yangi hujjatlar uchun {latestVersionLink} ({versionLabel}) ga qarang.", - "description": "The label userd to tell the user that he's browsing an unmaintained doc version" - }, - "theme.docs.versions.latestVersionLinkLabel": { - "message": "so'ngi versiya", - "description": "The label used for the latest version suggestion link label" - }, - "theme.common.editThisPage": { - "message": "Sahifani tahrirlash", - "description": "The link label to edit the current page" - }, - "theme.common.headingLinkTitle": { - "message": "Sarlavhaga to'g'ridan-to'g'ri havola", - "description": "Title for link to heading" - }, - "theme.lastUpdated.atDate": { - "message": " {date}", - "description": "The words used to describe on which date a page has been last updated" - }, - "theme.lastUpdated.byUser": { - "message": " {user} dan", - "description": "The words used to describe by who the page has been last updated" - }, - "theme.lastUpdated.lastUpdatedAtBy": { - "message": "Oxirgi yangilangan{atDate}{byUser}", - "description": "The sentence used to display when a page has been last updated, and by who" - }, - "theme.common.skipToMainContent": { - "message": "Asosiy tarkibga o'tish", - "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation" - } + "pages.home.features.title": { + "message": "Afzalliklari", + "description": "Features" + }, + "pages.home.features.logic.title": { + "message": "Aniq biznes mantiq", + "description": "Feature title" + }, + "pages.home.features.logic.description": { + "message": "Arxitekturani o'rganish oson, chunki u domen modullaridan iborat", + "description": "Feature description" + }, + "pages.home.features.adaptability.title": { + "message": "Moslashuvchanlik", + "description": "Feature title" + }, + "pages.home.features.adaptability.description": { + "message": "Arxitektura komponentlarini moslashuvchan tarzda almashtirish, yangi sharoitlarda qo'shish mumkin", + "description": "Feature description" + }, + "pages.home.features.debt.title": { + "message": "Texnik qarz", + "description": "Feature title" + }, + "pages.home.features.debt.description": { + "message": "Har bir modul nojo'ya ta'sirlarsiz mustaqil ravishda o'zgartirilishi/qayta yozilishi mumkin", + "description": "Feature description" + }, + "pages.home.features.shared.title": { + "message": "Aniq qayta foydalanish mumkinligi", + "description": "Feature title" + }, + "pages.home.features.shared.description": { + "message": "DRY va lokal moslashtirish o'rtasidagi muvozanatni saqlaydi", + "description": "Feature description" + }, + "pages.home.concepts.title": { + "message": "Tushunchalar", + "description": "Concepts" + }, + "pages.home.concepts.public.title": { + "message": "Ommaviy API", + "description": "Concept title" + }, + "pages.home.concepts.public.description": { + "message": "Har bir modul yuqori darajadagi umumiy API deklaratsiyasiga ega bo'lishi kerak", + "description": "Concept description" + }, + "pages.home.concepts.isolation.title": { + "message": "Izolyatsiya", + "description": "Concept title" + }, + "pages.home.concepts.isolation.description": { + "message": "Modul to'g'ridan-to'g'ri bir xil qatlamdagi boshqa modullarga yoki uning ustidagi qatlamlarga bog'liq bo'lmasligi kerak.", + "description": "Concept description" + }, + "pages.home.concepts.needs.title": { + "message": "Ehtiyojlarni tushunish", + "description": "Concept title" + }, + "pages.home.concepts.needs.description": { + "message": "Biznes va foydalanuvchi ehtiyojlariga e'tibor qarating", + "description": "Concept description" + }, + "pages.home.scheme.title": { + "message": "Sxema", + "description": "Scheme" + }, + "pages.home.companies.using": { + "message": "Metodologiyadan foydalanadigan kompaniyalar", + "description": "Companies using methodology" + }, + "pages.home.companies.add_me": { + "message": "Sizning kompaniyangizda metodologiya qo'llaniladimi?", + "description": "Methodology is used in your company?" + }, + "pages.home.companies.tell_us": { + "message": "Habar bering", + "description": "Tell us" + }, + "pages.examples.title": { + "message": "Namunalar", + "description": "Page title" + }, + "pages.examples.subtitle": { + "message": "FSD yordamida yaratilgan veb-saytlar ro'yxati", + "description": "Page subtitle" + }, + "pages.examples.add_me.title": { + "message": "Namuna qoshish", + "description": "Request to add example" + }, + "pages.examples.repo.title": { + "message": "Repezitoriyalar", + "description": "Examples repository label" + }, + "pages.examples.versions": { + "message": "Shuningdek, versiyalar ro'yxatiga qarang", + "description": "Versions reminder" + }, + "pages.versions.title": { + "message": "Feature-Sliced Design versiyalari", + "description": "Feature-Sliced Design versions" + }, + "pages.versions.current": { + "message": "Bu yerda siz joriy nashr etilgan versiya uchun hujjatlarni topishingiz mumkin", + "description": "Description for current version" + }, + "pages.versions.legacy": { + "message": "Bu yerda {of} ning eski versiyalari uchun hujjatlarni topishingiz mumkin", + "description": "Description for legacy version" + }, + "pages.nav.title": { + "message": "🧭 Navigatsiya", + "description": "NavPage title" + }, + "pages.nav.legacy.title": { + "message": "Eskirgan havolalar", + "description": "NavPage section=legacy title" + }, + "pages.nav.legacy.details": { + "message": "Hujjatlarni qayta tuzilgandan so'ng, maqolalarda ba'zi havolalar o'zgartirildi. Quyida siz izlayotgan sahifani topishingiz mumkin.", + "description": "NavPage section=legacy details" + }, + "pages.nav.legacy.subdetails": { + "message": "Lekin moslik uchun eski havolalardan qayta yo'naltirishlar mavjud", + "description": "NavPage section=legacy subdetails" + }, + "features.feedback-badge.label": { + "message": "Hujjatlar bo'yicha fikr-mulohazalaringizni baham ko'ring 🤙", + "description": "Feedback share button label" + }, + "features.feedback-badge.url": { + "message": "https://forms.gle/7p4anU2shHAzmfqc8", + "description": "Feedback share form url" + }, + "features.feedback-doc.thanks": { + "message": "Baham ko'rganingiz uchun rahmat!", + "description": "DocFeedback block=Thanks" + }, + "features.feedback-doc.title": { + "message": "Sahifa foydali bo'ldimi?", + "description": "DocFeedback block=Title" + }, + "features.feedback-doc.subtitle": { + "message": "Sizning fikringiz bizga dastur dokumentatsiyasini yaxshilashda yordam beradi", + "description": "DocFeedback block=Subtitle" + }, + "features.feedback-doc.button-text": { + "message": "Fikr-mulohaza qoldirish", + "description": "The text on a floating button to leave feedback about the docs" + }, + "features.feedback-doc.email-placeholder": { + "message": "Sizning e-mailingizni qoldiring (ixtiyoriy)", + "description": "The placeholder for email input" + }, + "features.feedback-doc.error-message": { + "message": "Iltimos, keyinroq urinib ko'ring.", + "description": "The error message displayed when feedback form submission fails" + }, + "features.feedback-doc.modal-title-error-403": { + "message": "URL so'rovi bu proyekt uchun PushFeedbackda ko'rsatilgan URL bilan mos kelmadi.", + "description": "The title of the modal displayed when the feedback form submission fails with 403 error" + }, + "features.feedback-doc.modal-title-error-404": { + "message": "Biz PushFeedbackda berilgan proyekt identifikatorini topolmadik.", + "description": "The title of the modal displayed when the feedback form submission fails with 404 error" + }, + "features.feedback-doc.message-placeholder": { + "message": "Fikringizni shu yerga yozing…", + "description": "The placeholder for message input" + }, + "features.feedback-doc.modal-title": { + "message": "Fikringizni ulashing", + "description": "The title of the modal displayed when the feedback form is opened" + }, + "features.feedback-doc.modal-title-error": { + "message": "Xatolik!", + "description": "The title of the modal displayed when the feedback form submission fails" + }, + "features.feedback-doc.modal-title-success": { + "message": "Fikringiz uchun rahmat!", + "description": "The title of the modal displayed when the feedback form submission is successful" + }, + "features.feedback-doc.rating-placeholder": { + "message": "Ushbu sahifa foydali bo'ldimi?", + "description": "The placeholder for rating input" + }, + "features.feedback-doc.rating-stars-placeholder": { + "message": "Ushbu sahifani qanday baholay olasiz", + "description": "The placeholder for rating stars input" + }, + "features.feedback-doc.screenshot-button-text": { + "message": "Ekran surati olish", + "description": "The text on a button to take a screenshot" + }, + "features.feedback-doc.screenshot-topbar-text": { + "message": "Sahifadagi elementni tanlang", + "description": "The text displayed in the top bar of the screenshot tool" + }, + "features.feedback-doc.send-button-text": { + "message": "Yuborish", + "description": "The text on a button to send feedback" + }, + "features.hero.tagline": { + "message": "Front-end loyihalari uchun arxitektura metodologiyasi", + "description": "Architectural methodology for frontend projects" + }, + "features.hero.get_started": { + "message": "Tanishib chiqish", + "description": "Get Started" + }, + "features.hero.examples": { + "message": "Namunalar", + "description": "Examples" + }, + "features.hero.previous": { + "message": "Oldingi versiya", + "description": "Previous version" + }, + "shared.wip.title": { + "message": "Maqola yozilish jarayonida", + "description": "Admonition title" + }, + "shared.wip.subtitle": { + "message": "Uning yaratilishini tezlashtirish uchun siz:", + "description": "Admonition subtitle" + }, + "shared.wip.var.feedback.base": { + "message": "📢 Fikr-mulohaza ulashing ", + "description": "Variant for contribute (base)" + }, + "shared.wip.var.feedback.link": { + "message": "chiptada (sharhlar/reaksiya emoji)", + "description": "Variant for contribute (link)" + }, + "shared.wip.var.material.base": { + "message": "💬 Mavzu boyicha to'plamlarni chiptaga yig'ish ", + "description": "Variant for contribute (base)" + }, + "shared.wip.var.material.link": { + "message": "suhbatdan olingan material", + "description": "Variant for contribute (link)" + }, + "shared.wip.var.contribute.base": { + "message": "⚒️ Hissa qo‘shish ", + "description": "Variant for contribute (base)" + }, + "shared.wip.var.contribute.link": { + "message": "har qanday boshqa yo'l bilan", + "description": "Variant for contribute (link)" + }, + "theme.NotFound.title": { + "message": "Sahifa topilmadi", + "description": "The title of the 404 page" + }, + "theme.NotFound.p1": { + "message": "Afsuski, siz hohlagan sahifa topilmadi", + "description": "The first paragraph of the 404 page" + }, + "theme.NotFound.p2": { + "message": "Iltimos, ushbu havolaga kirgan sayt egasiga havola ishlamayotganligi haqida xabar bering.", + "description": "The 2nd paragraph of the 404 page" + }, + "theme.AnnouncementBar.closeButtonAriaLabel": { + "message": "Yopish", + "description": "The ARIA label for close button of announcement bar" + }, + "theme.blog.paginator.navAriaLabel": { + "message": "Bloglar roʻyxati sahifalariga navigatsiya", + "description": "The ARIA label for the blog pagination" + }, + "theme.blog.paginator.newerEntries": { + "message": "Keyingi yozmalar", + "description": "The label used to navigate to the newer blog posts page (previous page)" + }, + "theme.blog.paginator.olderEntries": { + "message": "Oldingi yozmalar", + "description": "The label used to navigate to the older blog posts page (next page)" + }, + "theme.blog.post.readingTime.plurals": { + "message": "{readingTime} min. o'qish", + "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.tags.tagsListLabel": { + "message": "Teglar:", + "description": "The label alongside a tag list" + }, + "theme.blog.post.readMore": { + "message": "Ko'proq o'qish", + "description": "The label used in blog post item excerpts to link to full blog posts" + }, + "theme.blog.post.paginator.navAriaLabel": { + "message": "Blog postlari sahifalarida navigatsiya", + "description": "The ARIA label for the blog posts pagination" + }, + "theme.blog.post.paginator.newerPost": { + "message": "Keyingi post", + "description": "The blog post button label to navigate to the newer/previous post" + }, + "theme.blog.post.paginator.olderPost": { + "message": "Oldingi post", + "description": "The blog post button label to navigate to the older/next post" + }, + "theme.tags.tagsPageTitle": { + "message": "Teglar", + "description": "The title of the tag list page" + }, + "theme.blog.post.plurals": { + "message": "Bir post|{count} ta post", + "description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.blog.tagTitle": { + "message": "{nPosts} \"{tagName}\" bilan", + "description": "The title of the page for a blog tag" + }, + "theme.tags.tagsPageLink": { + "message": "Hamma teglarni ko'rish", + "description": "The label of the link targeting the tag list page" + }, + "theme.CodeBlock.copyButtonAriaLabel": { + "message": "Buferga nusxalash", + "description": "The ARIA label for copy code blocks button" + }, + "theme.CodeBlock.copied": { + "message": "Nuxsalandi", + "description": "The copied button label on code blocks" + }, + "theme.CodeBlock.copy": { + "message": "Nusxalash", + "description": "The copy button label on code blocks" + }, + "theme.docs.sidebar.expandButtonTitle": { + "message": "Yon panelni kengaytirish", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.docs.sidebar.expandButtonAriaLabel": { + "message": "Yon panelni kengaytirish", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.docs.paginator.navAriaLabel": { + "message": "Hujjat sahifalarida navigatsiya", + "description": "The ARIA label for the docs pagination" + }, + "theme.docs.paginator.previous": { + "message": "Oldingi sahifa", + "description": "The label used to navigate to the previous doc" + }, + "theme.docs.paginator.next": { + "message": "Keyingi sahifa", + "description": "The label used to navigate to the next doc" + }, + "theme.docs.sidebar.collapseButtonTitle": { + "message": "Yon panelni yig‘ish", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.collapseButtonAriaLabel": { + "message": "Yon panelni yig‘ish", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.responsiveCloseButtonLabel": { + "message": "Menyuni yopish", + "description": "The ARIA label for close button of mobile doc sidebar" + }, + "theme.docs.sidebar.responsiveOpenButtonLabel": { + "message": "Menyuni ochish", + "description": "The ARIA label for open button of mobile doc sidebar" + }, + "theme.docs.versions.unreleasedVersionLabel": { + "message": "Bu {siteTitle} {versionLabel} ning kelajakdagi versiyasi uchun hujjatdir.", + "description": "The label used to tell the user that he's browsing an unreleased doc version" + }, + "theme.docs.versions.unmaintainedVersionLabel": { + "message": "Bu endi faol saqlanmaydigan {siteTitle} {versionLabel} uchun hujjatlar.", + "description": "The label used to tell the user that he's browsing an unmaintained doc version" + }, + "theme.docs.versions.latestVersionSuggestionLabel": { + "message": "Eng yangi hujjatlar uchun {latestVersionLink} ({versionLabel}) ga qarang.", + "description": "The label userd to tell the user that he's browsing an unmaintained doc version" + }, + "theme.docs.versions.latestVersionLinkLabel": { + "message": "so'ngi versiya", + "description": "The label used for the latest version suggestion link label" + }, + "theme.common.editThisPage": { + "message": "Sahifani tahrirlash", + "description": "The link label to edit the current page" + }, + "theme.common.headingLinkTitle": { + "message": "Sarlavhaga to'g'ridan-to'g'ri havola", + "description": "Title for link to heading" + }, + "theme.lastUpdated.atDate": { + "message": " {date}", + "description": "The words used to describe on which date a page has been last updated" + }, + "theme.lastUpdated.byUser": { + "message": " {user} dan", + "description": "The words used to describe by who the page has been last updated" + }, + "theme.lastUpdated.lastUpdatedAtBy": { + "message": "Oxirgi yangilangan{atDate}{byUser}", + "description": "The sentence used to display when a page has been last updated, and by who" + }, + "theme.common.skipToMainContent": { + "message": "Asosiy tarkibga o'tish", + "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation" + } } diff --git a/i18n/uz/docusaurus-plugin-content-docs/current.json b/i18n/uz/docusaurus-plugin-content-docs/current.json index 8d1bb5d865..e8aeebd580 100644 --- a/i18n/uz/docusaurus-plugin-content-docs/current.json +++ b/i18n/uz/docusaurus-plugin-content-docs/current.json @@ -1,38 +1,38 @@ { - "version.label": { - "message": "v2.0.0 🍰", - "description": "The label for version current" - }, - "sidebar.getstartedSidebar.category.Tutorials": { - "message": "Darsliklar", - "description": "The label for category Tutorials in sidebar getstartedSidebar" - }, - "sidebar.aboutSidebar.category.Alternatives": { - "message": "Alternativlar", - "description": "The label for category Alternatives in sidebar aboutSidebar" - }, - "sidebar.aboutSidebar.category.Promote": { - "message": "Rag'batlantirish", - "description": "The label for category Promote in sidebar aboutSidebar" - }, - "sidebar.guidesSidebar.category.Examples": { - "message": "Namunalar", - "description": "The label for category Examples in sidebar guidesSidebar" - }, - "sidebar.guidesSidebar.category.Migration": { - "message": "Migratsiya", - "description": "The label for category Migration in sidebar guidesSidebar" - }, - "sidebar.guidesSidebar.category.Tech": { - "message": "Texnologiya", - "description": "The label for category Tech in sidebar guidesSidebar" - }, - "sidebar.conceptsSidebar.category.Issues": { - "message": "Muammolar", - "description": "The label for category Issues in sidebar conceptsSidebar" - }, - "sidebar.referenceSidebar.category.Layer": { - "message": "Qatlam", - "description": "The label for category Layer in sidebar referenceSidebar" - } + "version.label": { + "message": "v2.1", + "description": "The label for version current" + }, + "sidebar.getstartedSidebar.category.Tutorials": { + "message": "Darsliklar", + "description": "The label for category Tutorials in sidebar getstartedSidebar" + }, + "sidebar.aboutSidebar.category.Alternatives": { + "message": "Alternativlar", + "description": "The label for category Alternatives in sidebar aboutSidebar" + }, + "sidebar.aboutSidebar.category.Promote": { + "message": "Rag'batlantirish", + "description": "The label for category Promote in sidebar aboutSidebar" + }, + "sidebar.guidesSidebar.category.Examples": { + "message": "Namunalar", + "description": "The label for category Examples in sidebar guidesSidebar" + }, + "sidebar.guidesSidebar.category.Migration": { + "message": "Migratsiya", + "description": "The label for category Migration in sidebar guidesSidebar" + }, + "sidebar.guidesSidebar.category.Tech": { + "message": "Texnologiya", + "description": "The label for category Tech in sidebar guidesSidebar" + }, + "sidebar.conceptsSidebar.category.Issues": { + "message": "Muammolar", + "description": "The label for category Issues in sidebar conceptsSidebar" + }, + "sidebar.referenceSidebar.category.Layer": { + "message": "Qatlam", + "description": "The label for category Layer in sidebar referenceSidebar" + } } diff --git a/i18n/uz/docusaurus-plugin-content-docs/current/branding.md b/i18n/uz/docusaurus-plugin-content-docs/current/branding.md index 3e9ce4264c..20833f08e4 100644 --- a/i18n/uz/docusaurus-plugin-content-docs/current/branding.md +++ b/i18n/uz/docusaurus-plugin-content-docs/current/branding.md @@ -1,3 +1,5 @@ +import useBaseUrl from "@docusaurus/useBaseUrl"; + # Brending bo'yicha tavsiyalar FSD ning vizual identifikatsiyasi uning asosiy tushunchalariga asoslanadi: `Layered`, `Sliced self-contained parts`, `Parts & Compose`, `Segmented`. @@ -42,30 +44,30 @@ FSD har xil konteksda bir nechta logotipga ega, lekin **primary** dan foydalanis primary
(#29BEDC, #517AED) - logo-primary + logo-primary Aksariyat hollarda afzal ko'riladi flat
(#3193FF) - logo-flat + logo-flat Bir rangli kontekst uchun monochrome
(#FFF) - logo-monocrhome + logo-monochrome Oq qora kontekst uchun square
(#3193FF) - logo-square + logo-square Kvadrat o'lchamlar uchun ## Bannerlar & Sxemalar {#banners--schemes} -banner-primary -banner-monochrome +banner-primary +banner-monochrome ## Social Preview @@ -79,4 +81,3 @@ Ish davom etmoqda... - [Muhokama (github)](https://github.com/feature-sliced/documentation/discussions/399) - [Ma'lumotnomalar bilan rebrending tarixi (figma)](https://www.figma.com/file/RPphccpoeasVB0lMpZwPVR/FSD-Brand?node-id=0%3A1) -- [Rebrending demosi](https://rebrand-sliced.netlify.app/en/) diff --git a/i18n/uz/docusaurus-plugin-content-docs/current/get-started/overview.mdx b/i18n/uz/docusaurus-plugin-content-docs/current/get-started/overview.mdx new file mode 100644 index 0000000000..dbbd315263 --- /dev/null +++ b/i18n/uz/docusaurus-plugin-content-docs/current/get-started/overview.mdx @@ -0,0 +1,144 @@ +--- +sidebar_position: 1 +--- + +# Qisqacha + +**Feature-Sliced Design** (FSD) bu front-end ilovalarining tuzilishini shakllantirish uchun arxitektura metodologiyasi. Oddiy qilib aytganda, bu kodni tartibga solish uchun qoidalar va konvensiyalar to'plami. Bu metodologiyaning asosiy maqsadi - loyihani yanada tushunarli va o'zgaruvchan biznes talablariga moslashuvchan qilishdir. + +FSD nafaqat konvensiyalar to'plami, balki vositalar to'plamini ham o'z ichiga oladi. Bizda [linter][ext-steiger] sizning loyihangizning arxitekturasini tekshirish uchun, [papka generatorlari][ext-tools] CLI yoki IDE lar orqali, shuningdek, ko'plab [misollar][examples] jamlanmasiga ega. + +## Menga to'g'ri keladimi? {#is-it-right-for-me} + +FSD har qanday hajmdagi jamoalarda joriy qilinishi mumkin. Sizga to'g'ri keladi agar: + +- Siz **frontend** qilayotgan bo'lsangiz (veb, mobil, kompyuter dasturlari, h.k.) +- Siz **ilova** qurayotgan bo'lsangiz, kutubxona emas + +Shu bilan birga, bu yerda qaysi dasturlash tili, UI(foydalanuvchi interfeysi) freymvorki, yoki holat boshqaruvchisi(state manager)ni ishlatish bo'yicha hech qanday cheklov yo'q. Shuningdek, FSD ni bosqichma-bosqich joriy qilishingiz, monorepo(yagona repozitoriya)larda ishlatishingiz va ilovangizni modullarga bo'lib, FSD ni ularning har birida alohida amalga oshirishingiz mumkin. + +Agar sizda allaqachon arxitektura mavjud bo‘lsa va FSDga o‘tishni o‘ylayotgan bo‘lsangiz, avvalo joriy arxitektura jamoangizga muammo tug‘dirayotganiga ishonch hosil qiling. Masalan, agar loyihangiz juda katta va o‘zaro juda bog‘lanib ketgan bo‘lsa, bu yangi xususiyatlarni samarali joriy etishni qiyinlashtirishi mumkin. Yoki jamoangizga ko‘plab yangi a’zolar qo‘shilishi kutilayotgan bo‘lsa, FSDga o‘tish foydali bo‘lishi mumkin. Agar hozirgi arxitektura yaxshi ishlayotgan bo‘lsa, ehtimol uni o‘zgartirishga hojat yo‘q. Ammo agar migratsiya qilishga qaror qilsangiz, yo‘riqnoma uchun [Migratsiya][migration] sahifasini ko'rib chiqishingiz mumkin. +## Asosiy misollar {#basic-example} + +Bu yerda FSD qo'llanilgan oddiy loyiha: + +- `📁 app` +- `📁 pages` +- `📁 shared` + +Bu yuqori darajadagi papkalar ular _layerlar_ deyiladi. Keling chuqurroq qaraymiz: + +- `📂 app` + - `📁 routes` + - `📁 analytics` +- `📂 pages` + - `📁 home` + - `📂 article-reader` + - `📁 ui` + - `📁 api` + - `📁 settings` +- `📂 shared` + - `📁 ui` + - `📁 api` + +`📂 pages` ichidagi papkalar _slice(bo'lak)lar_ deyiladi. Ular layer ni domenlar bo'yicha bo'lishadi(bu holatda, sahifalar bo'yicha) + +`📂 app`, `📂 shared`, va `📂 pages/article-reader` ichidagilar _segment(bo'lim)lar_ deyiladi, va ular slicelar(yoki layerlar), ya'ni kodning vazifasiga qarab ajratiladi. + +## Tushunchalar {#concepts} + +Layerlar, slicelar va segmentlar quyidagi iyerarxiyani hosil qiladi: + +
+ ![FSD konsepsiyalari iyearxiyasi, quyida tasvirlangan](/img/visual_schema.jpg) + +
+

Tepadagi rasmda: uchta ustunlar, chapdan o'ngga qarab mos ravishda "Layerlar", "Slicelar" va "Segmentlar" deb belgilangan.

+

"Layers" ustuni yuqoridan pastga qarab joylashtirilgan etti bo‘limni o‘z ichiga oladi: "app", "processes", "pages", "widgets", "features", "entities" va "shared". "Processes" bo‘limi ustidan chizib tashlangan. "Entities" bo‘limi ikkinchi ustun — "Slicelar" bilan bog‘langan bo‘lib, bu ikkinchi ustunning "entities" tarkibida ekanligini bildiradi.

+

"Slices" ustuni yuqoridan pastga qarab joylashtirilgan uch bo‘limni o‘z ichiga oladi: "user", "post" va "comment". "Post" bo‘limi uchinchi ustun — "Segmentlar" bilan bog‘langan bo‘lib, bu uchinchi ustunning "post" tarkibida ekanligini bildiradi.

+

"Segments" ustuni yuqoridan pastga qarab joylashtirilgan uchta bo'limni o'z ichiga oladi: "ui", "model" va "api".

+
+
+ +### Layerlar {#layers} + +Layerlar barcha FSD loyihalarida standartlashtirilgan. Siz ularning barchasidan foydalanishingiz shart emas, lekin ularning nomlari muhim. Hozirda yettita layer mavjud(tepadan pastga qarab): + +1. **App** — Ilovaning ishlashi uchun zarur bo'lgan hamma narsa — marshrutlash(routing), dastur boshlanish nuqtasi(entrypoint), global stillar, provayderlar. +2. **Processes** (eskirgan) — Murakkab sahifalararo ssenariylar(ya'ni foydalanuvchi bir sahifada amal bajarsa, boshqa sahifada natija ko‘rinishi yoki turli sahifalar o‘rtasida state(holat) uzatilishi). +3. **Pages** — To‘liq sahifalar yoki ichma-ich marshrutlashdagi(routing) sahifaning katta qismlari. +4. **Widgets** — Katta, mustaqil funksionallik yoki UI bo‘laklari, odatda butun bir foydalanish holatini ta’minlaydi. +5. **Features** — _Qayta foydalaniladigan_ butun mahsulot xususiyatlarining implementatsiyalari, ya’ni foydalanuvchiga biznes qiymatini keltiradigan harakatlar. +6. **Entities** — Loyihada ishlaydigan biznes obyekti, masalan, `user` yoki `product`. +7. **Shared** — Loyihaning yoki biznesning aniq xususiyatlariga bog‘liq bo‘lmagan, lekin qayta ishlatiladigan funksionallik. + +:::warning + +**App** va **Shared** layerlari boshqa layerlardan farqli o‘laroq slicelarga ega emas va to‘g‘ridan-to‘g‘ri segmentlarga bo‘linadi. + +Biroq, boshqa barcha layerlar — **Entities**, **Features**, **Widgets** va **Pages** avval slicelar yaratishni talab qiladi, shundan keyingina ularning ichida segments yaratiladi. + +::: +Layerlar bilan ishlashning asosiy tamoyili shundaki, bitta layerdagi modullar faqat o‘zidan quyi joylashgan layerlardagi modullarni bilishi va ulardan import qilishi mumkin. + +### Slicelar {#slices} + +Keyingi tushuncha slicelar, ya’ni kodni biznes domenlari bo‘yicha bo‘lish usuli. Ularning nomlarini o‘zingiz tanlashingiz va xohlaganingizcha ko‘paytirishingiz mumkin. Slices kod bazangizni yanada tushunarli qiladi, chunki bir-biriga bog‘liq modullar bir joyda saqlanadi. + +Slicelar bir xil layer ichida boshqa slicelardan foydalana olmaydi. Bu esa yuqori cohesion([uyg'unlik][cohesion]) va low coupling([past bog‘liqlik][low-coupling]) tamoyillariga rioya qilishga yordam beradi. + +### Segmentlar {#segments} + +Slicelar, shuningdek, App va Shared layerlari segmentlardan tashkil topadi. Segmentlar kodni uning maqsadiga qarab guruhlaydi. Segment nomlari standart bilan cheklanmagan, ammo eng keng tarqalgan maqsadlar uchun bir nechta an’anaviy nomlar mavjud: + +- `ui` — UI(foydalanuvchi interfeysi) ni aks ettiruvchi barcha narsalar: UI(foydalanuvchi interfeysi) komponentlar, sana formatlagichlar, stillar va boshqalar. +- `api` — backend bilan munosabat: so'rov funksiyalari, ma'lumot turlari, mapperlar va boshqalar. +- `model` — ma'lumot modeli: sxemalar, interfeyslar, holat saqlovchi obyektlar (stores) va biznes mantiq(business logic). +- `lib` — Ushbu sliceda joylashgan boshqa modullarga kerak kutubxona kodi. +- `config` — konfiguratsiya fayllari va feature flags(yoqib o'chiriladigan xususiyat bayroqlari) + +Odatda, ushbu segmentlar ko‘pchilik layerlar uchun yetarli bo‘ladi. Siz faqat Shared yoki App layerlarida o‘z segmentlaringizni yaratishingiz mumkin, lekin bu qat’iy qoida emas. + +## Afzalliklar {#advantages} + +- **Bir xillik** + Tuzilma standartlashtirilganligi sababli loyihalar yanada bir xil ko'rinishga ega bo‘ladi, bu esa jamoaga yangi a’zolarni jalb qilish jarayonini osonlashtiradi. + +- **O'zgarishlar va refaktoring(kodni qayta tuzish) jarayonida barqarorlik** + Bitta layerdagi modul boshqa shu layerdagi yoki undan yuqoridagi modullardan foydalana olmaydi. + Bu esa ilovaning boshqa qismlariga kutilmagan ta’sir ko‘rsatmasdan izolyatsiyalangan o‘zgarishlar kiritish imkonini beradi. + +- **Qayta foydalaniladigan mantiq(logic)ni tartibli boshqarish** + Layerga qarab, kodni keng miqyosda qayta ishlatish yoki faqat ma'lum bir joyda ishlatish mumkin. + Bu esa **DRY** tamoyilini ushlab turish bilan birga, loyihaning qulayligini ham saqlab qolishga yordam beradi. + +- **Biznes va foydalanuvchi ehtiyojlariga yo‘naltirilganlik.** + Ilova biznes domenlarga bo‘lingan va nomlashda biznesga oid terminologiya(business language) dan foydalanish tavsiya etiladi, bu esa sizga loyiha tarkibidagi boshqa qismlarni to‘liq tushunmasdan ham mahsulot ustida ishlash imkonini beradi. + +## Bosqichma-bosqich joriy etish {#incremental-adoption} + +Agar mavjud kod bazangizni FSD ga o'tkazmoqchi bo'lsangiz, quyidagi strategiyani tavsiya qilamiz. Biz buni bizning tajribamizda foydali deb topdik. + +1. Avval App va Shared layerlarini modul-ma-modul shakllantirib, mustahkam asos yarating. + +2. Mavjud UI komponentlarini Widgets va Pages ga umumiy tartibda joylashtiring, hatto ular FSD qoidalarini buzadigan bog‘liqliklarga ega bo‘lsa ham. + +3. Import qoidabuzarliklarini bosqichma-bosqich tuzatishni boshlang va shu bilan birga Entities, ehtimol Features ham ajratib chiqing. + +Refaktoring jarayonida yoki loyihaning faqat ayrim qismlarini o‘zgartirayotganda yangi yirik Entities qo‘shmaslik tavsiya etiladi. + +## Keyingi qadamlar {#next-steps} + +- **FSD haqida chuqur tushuncha hosil qilmoqchimisiz?** [Bu yerda][tutorial]. +- **Misollar bilan o'rganishni afzal ko'rasizmi?** Bizda juda ko'plab [Misollar][examples] bo'limi mavjud. +- **Savolingiz bormi?** Bizning [Telegram chat][ext-telegram] ga qo'shiling va yordam oling. + +[tutorial]: /docs/get-started/tutorial +[examples]: /examples +[migration]: /docs/guides/migration/from-custom +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools +[ext-telegram]: https://t.me/feature_sliced +[low-coupling]: https://en.wikipedia.org/wiki/Loose_coupling +[cohesion]: https://en.wikipedia.org/wiki/Cohesion_(computer_science) + diff --git a/i18n/uz/docusaurus-plugin-content-docs/current/privacy.md b/i18n/uz/docusaurus-plugin-content-docs/current/privacy.md deleted file mode 100644 index 12f7977b51..0000000000 --- a/i18n/uz/docusaurus-plugin-content-docs/current/privacy.md +++ /dev/null @@ -1,17 +0,0 @@ -# Maxfiylik - -**Hujjatlardan foydalanish bo'yicha ma'lumotlarni to'plash uchun cookie-fayllardan foydalanamiz (faqatgina keyingi tahlillar va saytni yaxshilash maqsadida)** - -Ko'rsatkichlarni yig'ish uchun yagona plaginlar [@docusaurus/plugin-google-analytics](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-google-analytics) va [docusaurus-plugin-hotjar](https://github.com/symblai/docusaurus-plugin-hotjar). Biz faqat [Google Analytics](https://analytics.google.com/) orqali taqdim etilgan ma'lumotlarni tahlil qilamiz, fikr-mulohazalarni yig'ish va foydalanuvchi tajribangizni yaxshilash uchun esa [Hotjar](https://www.hotjar.com/) dan. - -:::warning NOTE -Ushbu sahifa qonuniy jihatdan to'liq emas, shuning uchun boshqa tafsilotlar uchun rasmiy veb-saytlar [Google Analytics](https://analytics.google.com/) va [Hotjar](https://www.hotjar.com/) murojaat qiling. -::: - -**Kimning materiallarga ruhsati bor?** - -- Core-team metodologiyasi -- Xizmatdan foydalanish tahlilini biz Google Analytics ga suyanamiz [(ko'proq korish)](https://www.google.com/analytics/terms/us.html) -- Biz o'zaro aloqalar bo'yicha fikr-mulohazalarni yig'ishda Hotjar dan foydalaniz [(ko'proq korish)](https://help.hotjar.com/hc/en-us/categories/360003405813) - -![feature-sliced-banner](/img/banner.jpg) diff --git a/i18n/uz/docusaurus-plugin-content-docs/current/reference/index.mdx b/i18n/uz/docusaurus-plugin-content-docs/current/reference/index.mdx index fd3743f114..9bc362620a 100644 --- a/i18n/uz/docusaurus-plugin-content-docs/current/reference/index.mdx +++ b/i18n/uz/docusaurus-plugin-content-docs/current/reference/index.mdx @@ -25,12 +25,6 @@ import { ApiOutlined, GroupOutlined, AppstoreOutlined, NodeIndexOutlined } from to="/docs/reference/slices-segments" Icon={AppstoreOutlined} /> - -Дерево файловой системы с одной корневой папкой src и семью подпапками: app, processes, pages, widgets, features, entities, shared. Папка processes отображена немного тускло. +Дерево файловой системы с одной корневой папкой src и семью подпапками: app, processes, pages, widgets, features, entities, shared. Папка processes отображена немного тускло. +Дерево файловой системы с одной корневой папкой src и семью подпапками: app, processes, pages, widgets, features, entities, shared. Папка processes отображена немного тускло. 1. App (Ilova) 2. Processes (Jarayonlar, eski qatlam) @@ -27,7 +29,7 @@ Hammasi bo'lib **7 ta qatlam** mavjud bo'lib, ular eng katta mas'uliyat va  Loyihangizdagi barcha qatlamlardan foydalanish shart emas - faqat loyihangizga foyda keltiradigan qatlamlarni qo'shing. -## Qatlamlar uchun import qoidasi +## Qatlamlar uchun import qoidasi {#import-rule-on-layers} Qatlamlar _slices_ - kuchli bog'langan modullar guruhidan iborat. Xususiyatlarga ega Dilimlangan dizayn past ulanishni saqlaydi, shuning uchun bo'limlar orasidagi bog'liqliklar **qatlamlar uchun import qoidasi** bilan boshqariladi: @@ -187,4 +189,4 @@ Bu qatlam odatda Shared kabi boʻlaklarni oʻz ichiga olmaydi, aksincha u toʻg * Analitika initsializatsiyasi [ext--remix]: https://remix.run -[ext--sova-utility-dump]: https://sova.dev/ru/why-utils-and-helpers-is-a-dump/ +[ext--sova-utility-dump]: https://sergeysova.com/ru/why-utils-and-helpers-is-a-dump/ diff --git a/i18n/uz/docusaurus-theme-classic/footer.json b/i18n/uz/docusaurus-theme-classic/footer.json index 38840392b2..624f003c45 100644 --- a/i18n/uz/docusaurus-theme-classic/footer.json +++ b/i18n/uz/docusaurus-theme-classic/footer.json @@ -1,70 +1,66 @@ { - "link.title.Specs": { - "message": "Spetsifikatsiya", - "description": "The title of the footer links column with title=Specs in the footer" - }, - "link.title.Community": { - "message": "Jamiyat", - "description": "The title of the footer links column with title=Community in the footer" - }, - "link.title.More": { - "message": "Yana", - "description": "The title of the footer links column with title=More in the footer" - }, - "link.item.label.Documentation": { - "message": "Hujjatlar", - "description": "The label of footer link with label=Документация linking to /docs" - }, - "link.item.label.Community": { - "message": "Jamiyat", - "description": "The label of the footer link with label=Community linking to /community" - }, - "link.item.label.Help": { - "message": "Yordam", - "description": "The label of the footer link with label=Help linking to /nav" - }, - "link.item.label.Discussions": { - "message": "Munozaralar", - "description": "The label of footer link with label=Обсуждения linking to https://github.com/feature-sliced/documentation/discussions" - }, - "link.item.label.License": { - "message": "Litsenziya", - "description": "The label of footer link with label=License linking to LICENSE" - }, - "link.item.label.Privacy": { - "message": "Foydalanuvchi shartnomasi", - "description": "The label of footer link with label=Privacy linking to /docs/privacy" - }, - "link.item.label.Contribution Guide (RU)": { - "message": "Hissa qoʻshish boʻyicha qoʻllanma (RU)", - "description": "The label of footer link with label=Contribution Guide (RU) linking to CONTRIBUTING.md" - }, - "link.item.label.Discord": { - "message": "Discord", - "description": "The label of footer link with label=Discord linking to https://discord.com/invite/S8MzWTUsmp" - }, - "link.item.label.Telegram": { - "message": "Telegram", - "description": "The label of footer link with label=Telegram linking to https://t.me/feature_sliced" - }, - "link.item.label.Twitter": { - "message": "Twitter", - "description": "The label of footer link with label=Twitter linking to https://twitter.com/feature_sliced" - }, - "link.item.label.Open Collective": { - "message": "Open Collective", - "description": "The label of footer link with label=Open Collective linking to https://opencollective.com/feature-sliced" - }, - "link.item.label.YouTube": { - "message": "YouTube", - "description": "The label of footer link with label=YouTube linking to https://www.youtube.com/c/FeatureSlicedDesign" - }, - "link.item.label.GitHub": { - "message": "GitHub", - "description": "The label of footer link with label=GitHub linking to https://github.com/feature-sliced" - }, - "copyright": { - "message": "Copyright © 2018-2023 Feature-Sliced Design", - "description": "The footer copyright" - } + "link.title.Specs": { + "message": "Spetsifikatsiya", + "description": "The title of the footer links column with title=Specs in the footer" + }, + "link.title.Community": { + "message": "Jamiyat", + "description": "The title of the footer links column with title=Community in the footer" + }, + "link.title.More": { + "message": "Yana", + "description": "The title of the footer links column with title=More in the footer" + }, + "link.item.label.Documentation": { + "message": "Hujjatlar", + "description": "The label of footer link with label=Документация linking to /docs" + }, + "link.item.label.Community": { + "message": "Jamiyat", + "description": "The label of the footer link with label=Community linking to /community" + }, + "link.item.label.Help": { + "message": "Yordam", + "description": "The label of the footer link with label=Help linking to /nav" + }, + "link.item.label.Discussions": { + "message": "Munozaralar", + "description": "The label of footer link with label=Обсуждения linking to https://github.com/feature-sliced/documentation/discussions" + }, + "link.item.label.License": { + "message": "Litsenziya", + "description": "The label of footer link with label=License linking to LICENSE" + }, + "link.item.label.Contribution Guide (RU)": { + "message": "Hissa qoʻshish boʻyicha qoʻllanma (RU)", + "description": "The label of footer link with label=Contribution Guide (RU) linking to CONTRIBUTING.md" + }, + "link.item.label.Discord": { + "message": "Discord", + "description": "The label of footer link with label=Discord linking to https://discord.com/invite/S8MzWTUsmp" + }, + "link.item.label.Telegram": { + "message": "Telegram", + "description": "The label of footer link with label=Telegram linking to https://t.me/feature_sliced" + }, + "link.item.label.Twitter": { + "message": "Twitter", + "description": "The label of footer link with label=Twitter linking to https://twitter.com/feature_sliced" + }, + "link.item.label.Open Collective": { + "message": "Open Collective", + "description": "The label of footer link with label=Open Collective linking to https://opencollective.com/feature-sliced" + }, + "link.item.label.YouTube": { + "message": "YouTube", + "description": "The label of footer link with label=YouTube linking to https://www.youtube.com/c/FeatureSlicedDesign" + }, + "link.item.label.GitHub": { + "message": "GitHub", + "description": "The label of footer link with label=GitHub linking to https://github.com/feature-sliced" + }, + "copyright": { + "message": "Copyright © 2018-2025 Feature-Sliced Design", + "description": "The footer copyright" + } } diff --git a/i18n/uz/docusaurus-theme-classic/navbar.json b/i18n/uz/docusaurus-theme-classic/navbar.json index 6944072c36..ba2a4eae2f 100644 --- a/i18n/uz/docusaurus-theme-classic/navbar.json +++ b/i18n/uz/docusaurus-theme-classic/navbar.json @@ -1,50 +1,50 @@ { - "title": { - "message": "", - "description": "The title in the navbar" - }, - "item.label.🛠 Examples": { - "message": "🛠 Namunalar", - "description": "Navbar item with label Examples" - }, - "item.label.📖 Docs": { - "message": "📖 Hujjatlar", - "description": "Navbar item with label Docs" - }, - "item.label.🔎 Intro": { - "message": "🔎 Kirish", - "description": "Navbar item with label Intro" - }, - "item.label.🚀 Get Started": { - "message": "🚀 Boshlash", - "description": "Navbar item with label Get Started" - }, - "item.label.🧩 Concepts": { - "message": "🧩 Tushunchalar", - "description": "Navbar item with label Concepts" - }, - "item.label.🎯 Guides": { - "message": "🎯 Qo'llanmalar", - "description": "Navbar item with label Guides" - }, - "item.label.📚 Reference": { - "message": "📚 Malumot", - "description": "Navbar item with label Reference" - }, - "item.label.🍰 About": { - "message": "🍰 Metodologiya haqida", - "description": "Navbar item with label About" - }, - "item.label.💫 Community": { - "message": "💫 Jamiyat", - "description": "Navbar item with label Community" - }, - "item.label.❔ Help": { - "message": "❔ Yordam", - "description": "Navbar item with label Help" - }, - "item.label.📝 Blog": { - "message": "📝 Blog", - "description": "Navbar item with label Blog" - } + "title": { + "message": "", + "description": "The title in the navbar" + }, + "item.label.🛠 Examples": { + "message": "🛠 Namunalar", + "description": "Navbar item with label Examples" + }, + "item.label.📖 Docs": { + "message": "📖 Hujjatlar", + "description": "Navbar item with label Docs" + }, + "item.label.🔎 Intro": { + "message": "🔎 Kirish", + "description": "Navbar item with label Intro" + }, + "item.label.🚀 Get Started": { + "message": "🚀 Boshlash", + "description": "Navbar item with label Get Started" + }, + "item.label.🧩 Concepts": { + "message": "🧩 Tushunchalar", + "description": "Navbar item with label Concepts" + }, + "item.label.🎯 Guides": { + "message": "🎯 Qo'llanmalar", + "description": "Navbar item with label Guides" + }, + "item.label.📚 Reference": { + "message": "📚 Malumot", + "description": "Navbar item with label Reference" + }, + "item.label.🍰 About": { + "message": "🍰 Metodologiya haqida", + "description": "Navbar item with label About" + }, + "item.label.💫 Community": { + "message": "💫 Jamiyat", + "description": "Navbar item with label Community" + }, + "item.label.❔ Help": { + "message": "❔ Yordam", + "description": "Navbar item with label Help" + }, + "item.label.📝 Blog": { + "message": "📝 Blog", + "description": "Navbar item with label Blog" + } } diff --git a/package.json b/package.json index 0182c4cd35..d1659c8a16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@feature-sliced/documentation", - "version": "2.0.0", + "version": "2.1.0", "private": true, "scripts": { "docusaurus": "docusaurus", @@ -8,11 +8,13 @@ "start:ru": "docusaurus start --locale ru", "start:en": "docusaurus start --locale en", "start:uz": "docusaurus start --locale uz", + "start:kr": "docusaurus start --locale kr", + "start:ja": "docusaurus start --locale ja", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "test": "pnpm run test:lint && pnpm run build", - "test:lint": "eslint \"./**/*.{ts,tsx,js,jsx}\" && stylelint ./**/*.scss", - "test:lint:fix": "eslint --fix \"./**/*.{ts,tsx,js,jsx}\" && stylelint **/*.scss --fix", + "test:lint": "eslint --cache \"./**/*.{ts,tsx,js,jsx}\" && stylelint --cache ./**/*.scss && prettier --check --cache .", + "test:lint:fix": "eslint --cache --fix \"./**/*.{ts,tsx,js,jsx}\" && stylelint --cache **/*.scss --fix && prettier --write --cache .", "deploy": "docusaurus deploy", "clear": "docusaurus clear", "serve": "docusaurus serve", @@ -22,35 +24,35 @@ "contributors:generate": "all-contributors generate" }, "engines": { - "node": ">= 16.14" + "node": ">= 20.0" }, "dependencies": { - "@ant-design/icons": "^4.6.2", - "@docusaurus/core": "^2.4.1", - "@docusaurus/plugin-client-redirects": "^2.4.1", - "@docusaurus/plugin-content-docs": "^2.4.1", - "@docusaurus/plugin-ideal-image": "^2.4.1", - "@docusaurus/preset-classic": "^2.4.1", - "@fontsource/overpass": "^4.5.4", - "@fontsource/ubuntu": "^4.5.4", - "@mdx-js/react": "^1.6.21", - "@svgr/webpack": "^5.5.0", - "@types/lodash-es": "^4.17.6", - "clsx": "^1.1.1", - "docusaurus-plugin-hotjar": "^0.0.2", - "dotenv": "^10.0.0", + "@ant-design/icons": "^5.5.1", + "@docusaurus/core": "^3.7.0", + "@docusaurus/faster": "^3.7.0", + "@docusaurus/plugin-client-redirects": "^3.7.0", + "@docusaurus/plugin-content-docs": "^3.7.0", + "@docusaurus/plugin-ideal-image": "^3.7.0", + "@docusaurus/preset-classic": "^3.7.0", + "@fontsource-variable/overpass": "^5.1.1", + "@mdx-js/react": "^3.1.0", + "@svgr/webpack": "^8.1.0", + "@types/lodash-es": "^4.17.12", + "clsx": "^2.1.1", + "dotenv": "^16.4.5", "file-loader": "^6.2.0", - "js-cookie": "^3.0.1", "lodash-es": "^4.17.21", - "picocolors": "^1.0.0", + "picocolors": "^1.1.1", "plugin-image-zoom": "^1.2.0", - "react": "^17.0.1", - "react-cookie-consent": "^6.4.1", - "react-dom": "^17.0.1", - "react-fast-marquee": "^1.3.5", + "prism-react-renderer": "^2.4.0", + "pushfeedback": "^0.1.49", + "pushfeedback-react": "^0.1.50", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-fast-marquee": "^1.6.5", "sha1": "^1.1.1", - "sharp": "^0.29.3", - "superstruct": "^0.15.3", + "sharp": "^0.33.5", + "superstruct": "^1.0.4", "text-to-svg": "^3.1.5", "url-loader": "^4.1.1" }, @@ -67,27 +69,40 @@ ] }, "devDependencies": { - "@babel/eslint-parser": "^7.21.3", - "@docusaurus/module-type-aliases": "^2.4.1", + "@babel/eslint-parser": "^7.25.1", + "@docusaurus/module-type-aliases": "^3.7.0", + "@docusaurus/theme-classic": "^3.7.0", + "@docusaurus/tsconfig": "^3.7.0", + "@docusaurus/types": "^3.7.0", "@eslint-kit/eslint-config-base": "4.1.0", "@eslint-kit/eslint-config-patch": "^1.0.0", - "@eslint-kit/eslint-config-prettier": "3.0.0", "@eslint-kit/eslint-config-react": "^3.0.0", - "@tsconfig/docusaurus": "^1.0.6", - "@typescript-eslint/eslint-plugin": "^5.38.0", - "@typescript-eslint/parser": "^5.38.0", - "all-contributors-cli": "^6.20.0", - "docusaurus-plugin-sass": "^0.2.1", + "@types/node": "^22.9.0", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "all-contributors-cli": "^6.26.1", + "docusaurus-plugin-sass": "^0.2.5", "eslint": "^7.32.0", + "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-alias": "1.1.2", - "prettier": "2.3.0", - "sass": "^1.43.4", - "sass-loader": "10.2.0", - "stylelint": "^13.13.1", - "stylelint-config-recess-order": "^2.4.0", - "stylelint-config-recommended": "^5.0.0", - "stylelint-config-standard": "^22.0.0", - "typescript": "^4.9.5" + "prettier": "^3.3.3", + "sass": "^1.81.0", + "stylelint": "^16.10.0", + "stylelint-config-recess-order": "^5.1.1", + "stylelint-config-recommended": "^14.0.1", + "stylelint-config-standard-scss": "^13.1.0", + "typescript": "^5.6.3" }, - "packageManager": "pnpm@8.3.1" + "packageManager": "pnpm@10.6.3+sha512.bb45e34d50a9a76e858a95837301bfb6bd6d35aea2c5d52094fa497a467c43f5c440103ce2511e9e0a2f89c3d6071baac3358fc68ac6fb75e2ceb3d2736065e6", + "pnpm": { + "onlyBuiltDependencies": [ + "@parcel/watcher", + "@swc/core", + "core-js", + "core-js-pure", + "sharp" + ] + } } diff --git a/plugins/docusaurus-plugin-og/config.js b/plugins/docusaurus-plugin-og/config.js index ee9cd8ac0c..fee870816e 100644 --- a/plugins/docusaurus-plugin-og/config.js +++ b/plugins/docusaurus-plugin-og/config.js @@ -5,7 +5,9 @@ const { objectFromBuffer, Logger } = require("./utils"); async function getConfig(configPath, encode = "utf-8") { try { - const config = objectFromBuffer(await readFile(resolve(configPath, "config.json"), encode)); + const config = objectFromBuffer( + await readFile(resolve(configPath, "config.json"), encode), + ); if (!validateConfig(config)) { Logger.err("Config validation error"); diff --git a/plugins/docusaurus-plugin-og/font.js b/plugins/docusaurus-plugin-og/font.js index 2b4a16ad32..58495ed925 100644 --- a/plugins/docusaurus-plugin-og/font.js +++ b/plugins/docusaurus-plugin-og/font.js @@ -7,7 +7,9 @@ function createFontsMapFromTemplates(templates) { if (!fonts.has(template.params.font)) { fonts.set( template.params.font, - textToSVG.loadSync(resolve(template.path, template.name, template.params.font)), + textToSVG.loadSync( + resolve(template.path, template.name, template.params.font), + ), ); } }); diff --git a/plugins/docusaurus-plugin-og/index.js b/plugins/docusaurus-plugin-og/index.js index 47b42661c0..8b0e33757a 100644 --- a/plugins/docusaurus-plugin-og/index.js +++ b/plugins/docusaurus-plugin-og/index.js @@ -29,7 +29,8 @@ module.exports = function (_, { templatesDir }) { (plugin) => plugin.name === "docusaurus-plugin-content-docs", ); - if (!docsPlugin) throw new Error("Docusaurus Doc plugin not found."); + if (!docsPlugin) + throw new Error("Docusaurus Doc plugin not found."); const previewOutputDir = resolve(outDir, config.outputDir); @@ -46,7 +47,12 @@ module.exports = function (_, { templatesDir }) { docsVersions.forEach((version) => { const { docs } = version; docs.forEach((document) => { - generateImageFromDoc(initData, document, i18n.currentLocale, previewOutputDir); + generateImageFromDoc( + initData, + document, + i18n.currentLocale, + previewOutputDir, + ); }); }); }, @@ -83,7 +89,9 @@ async function generateImageFromDoc(initData, doc, locale, outputDir) { const templateName = getTemplateNameByRules(id, config.rules); - const template = templates.find((template) => template.name === templateName); + const template = templates.find( + (template) => template.name === templateName, + ); const previewImage = await images.get(getTemplateImageId(template)).clone(); diff --git a/plugins/docusaurus-plugin-og/layout.js b/plugins/docusaurus-plugin-og/layout.js index 0d4ca790e7..df49a75085 100644 --- a/plugins/docusaurus-plugin-og/layout.js +++ b/plugins/docusaurus-plugin-og/layout.js @@ -17,7 +17,12 @@ function createLayoutLayers(doc, layout, previewFont, textWidthLimit) { return { input: Buffer.from( - createSVGText(previewFont, doc[layer.name], layoutOptions, textWidthLimit), + createSVGText( + previewFont, + doc[layer.name], + layoutOptions, + textWidthLimit, + ), ), top: layer.top, left: layer.left, diff --git a/plugins/docusaurus-plugin-og/rules.js b/plugins/docusaurus-plugin-og/rules.js index 896fb61913..14682afef5 100644 --- a/plugins/docusaurus-plugin-og/rules.js +++ b/plugins/docusaurus-plugin-og/rules.js @@ -1,5 +1,7 @@ function getTemplateNameByRules(path, rules) { - const filteredRules = rules.filter((rule) => new RegExp(rule.pattern).test(path)); + const filteredRules = rules.filter((rule) => + new RegExp(rule.pattern).test(path), + ); const sortedRules = filteredRules.sort((a, b) => b.priority - a.priority); return sortedRules[0]?.name || "basic"; } diff --git a/plugins/docusaurus-plugin-og/template.js b/plugins/docusaurus-plugin-og/template.js index 531a921a82..0e60bae8c3 100644 --- a/plugins/docusaurus-plugin-og/template.js +++ b/plugins/docusaurus-plugin-og/template.js @@ -8,7 +8,9 @@ const dirIgnore = ["config.json"]; async function getTemplates(templatesDir, encode = "utf8") { try { const allDirFiles = await readdir(templatesDir); - const templatesDirNames = allDirFiles.filter((fileName) => !dirIgnore.includes(fileName)); + const templatesDirNames = allDirFiles.filter( + (fileName) => !dirIgnore.includes(fileName), + ); const templates = await Promise.all( templatesDirNames.map(async (templateName) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2fa92462d7..7dae385d42 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,2971 +1,8345 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -dependencies: - '@ant-design/icons': - specifier: ^4.6.2 - version: 4.6.2(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/core': - specifier: ^2.4.1 - version: 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/plugin-client-redirects': - specifier: ^2.4.1 - version: 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/plugin-content-docs': - specifier: ^2.4.1 - version: 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/plugin-ideal-image': - specifier: ^2.4.1 - version: 2.4.1(eslint@7.32.0)(prop-types@15.8.1)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/preset-classic': - specifier: ^2.4.1 - version: 2.4.1(@algolia/client-search@4.19.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(search-insights@2.8.1)(typescript@4.9.5) - '@fontsource/overpass': - specifier: ^4.5.4 - version: 4.5.4 - '@fontsource/ubuntu': - specifier: ^4.5.4 - version: 4.5.4 - '@mdx-js/react': - specifier: ^1.6.21 - version: 1.6.22(react@17.0.2) - '@svgr/webpack': - specifier: ^5.5.0 - version: 5.5.0 - '@types/lodash-es': - specifier: ^4.17.6 - version: 4.17.6 - clsx: - specifier: ^1.1.1 - version: 1.1.1 - docusaurus-plugin-hotjar: - specifier: ^0.0.2 - version: 0.0.2 - dotenv: - specifier: ^10.0.0 - version: 10.0.0 - file-loader: - specifier: ^6.2.0 - version: 6.2.0(webpack@5.88.2) - js-cookie: - specifier: ^3.0.1 - version: 3.0.1 - lodash-es: - specifier: ^4.17.21 - version: 4.17.21 - picocolors: - specifier: ^1.0.0 - version: 1.0.0 - plugin-image-zoom: - specifier: ^1.2.0 - version: 1.2.0 - react: - specifier: ^17.0.1 - version: 17.0.2 - react-cookie-consent: - specifier: ^6.4.1 - version: 6.4.1(react@17.0.2) - react-dom: - specifier: ^17.0.1 - version: 17.0.2(react@17.0.2) - react-fast-marquee: - specifier: ^1.3.5 - version: 1.3.5(react-dom@17.0.2)(react@17.0.2) - sha1: - specifier: ^1.1.1 - version: 1.1.1 - sharp: - specifier: ^0.29.3 - version: 0.29.3 - superstruct: - specifier: ^0.15.3 - version: 0.15.3 - text-to-svg: - specifier: ^3.1.5 - version: 3.1.5 - url-loader: - specifier: ^4.1.1 - version: 4.1.1(file-loader@6.2.0)(webpack@5.88.2) - -devDependencies: - '@babel/eslint-parser': - specifier: ^7.21.3 - version: 7.21.3(@babel/core@7.22.11)(eslint@7.32.0) - '@docusaurus/module-type-aliases': - specifier: ^2.4.1 - version: 2.4.1(react-dom@17.0.2)(react@17.0.2) - '@eslint-kit/eslint-config-base': - specifier: 4.1.0 - version: 4.1.0(@typescript-eslint/parser@5.38.0)(eslint@7.32.0) - '@eslint-kit/eslint-config-patch': - specifier: ^1.0.0 - version: 1.0.0(eslint@7.32.0) - '@eslint-kit/eslint-config-prettier': - specifier: 3.0.0 - version: 3.0.0(eslint@7.32.0)(prettier@2.3.0) - '@eslint-kit/eslint-config-react': - specifier: ^3.0.0 - version: 3.0.0(eslint@7.32.0) - '@tsconfig/docusaurus': - specifier: ^1.0.6 - version: 1.0.6 - '@typescript-eslint/eslint-plugin': - specifier: ^5.38.0 - version: 5.38.0(@typescript-eslint/parser@5.38.0)(eslint@7.32.0)(typescript@4.9.5) - '@typescript-eslint/parser': - specifier: ^5.38.0 - version: 5.38.0(eslint@7.32.0)(typescript@4.9.5) - all-contributors-cli: - specifier: ^6.20.0 - version: 6.20.0 - docusaurus-plugin-sass: - specifier: ^0.2.1 - version: 0.2.1(@docusaurus/core@2.4.1)(sass@1.43.4)(webpack@5.88.2) - eslint: - specifier: ^7.32.0 - version: 7.32.0 - eslint-import-resolver-alias: - specifier: 1.1.2 - version: 1.1.2(eslint-plugin-import@2.28.1) - prettier: - specifier: 2.3.0 - version: 2.3.0 - sass: - specifier: ^1.43.4 - version: 1.43.4 - sass-loader: - specifier: 10.2.0 - version: 10.2.0(sass@1.43.4)(webpack@5.88.2) - stylelint: - specifier: ^13.13.1 - version: 13.13.1 - stylelint-config-recess-order: - specifier: ^2.4.0 - version: 2.4.0(stylelint@13.13.1) - stylelint-config-recommended: - specifier: ^5.0.0 - version: 5.0.0(stylelint@13.13.1) - stylelint-config-standard: - specifier: ^22.0.0 - version: 22.0.0(stylelint@13.13.1) - typescript: - specifier: ^4.9.5 - version: 4.9.5 +importers: + + .: + dependencies: + '@ant-design/icons': + specifier: ^5.5.1 + version: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/core': + specifier: ^3.7.0 + version: 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/faster': + specifier: ^3.7.0 + version: 3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + '@docusaurus/plugin-client-redirects': + specifier: ^3.7.0 + version: 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/plugin-content-docs': + specifier: ^3.7.0 + version: 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/plugin-ideal-image': + specifier: ^3.7.0 + version: 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/preset-classic': + specifier: ^3.7.0 + version: 3.7.0(@algolia/client-search@5.21.0)(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(@types/react@18.3.18)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.8.2) + '@fontsource-variable/overpass': + specifier: ^5.1.1 + version: 5.2.5 + '@mdx-js/react': + specifier: ^3.1.0 + version: 3.1.0(@types/react@18.3.18)(react@18.3.1) + '@svgr/webpack': + specifier: ^8.1.0 + version: 8.1.0(typescript@5.8.2) + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + dotenv: + specifier: ^16.4.5 + version: 16.4.7 + file-loader: + specifier: ^6.2.0 + version: 6.2.0(webpack@5.98.0(@swc/core@1.11.9)) + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 + picocolors: + specifier: ^1.1.1 + version: 1.1.1 + plugin-image-zoom: + specifier: ^1.2.0 + version: 1.2.0 + prism-react-renderer: + specifier: ^2.4.0 + version: 2.4.1(react@18.3.1) + pushfeedback: + specifier: ^0.1.49 + version: 0.1.52 + pushfeedback-react: + specifier: ^0.1.50 + version: 0.1.50 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + react-fast-marquee: + specifier: ^1.6.5 + version: 1.6.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + sha1: + specifier: ^1.1.1 + version: 1.1.1 + sharp: + specifier: ^0.33.5 + version: 0.33.5 + superstruct: + specifier: ^1.0.4 + version: 1.0.4 + text-to-svg: + specifier: ^3.1.5 + version: 3.1.5 + url-loader: + specifier: ^4.1.1 + version: 4.1.1(file-loader@6.2.0(webpack@5.98.0(@swc/core@1.11.9)))(webpack@5.98.0(@swc/core@1.11.9)) + devDependencies: + '@babel/eslint-parser': + specifier: ^7.25.1 + version: 7.26.10(@babel/core@7.26.10)(eslint@7.32.0) + '@docusaurus/module-type-aliases': + specifier: ^3.7.0 + version: 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-classic': + specifier: ^3.7.0 + version: 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@swc/core@1.11.9)(@types/react@18.3.18)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/tsconfig': + specifier: ^3.7.0 + version: 3.7.0 + '@docusaurus/types': + specifier: ^3.7.0 + version: 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@eslint-kit/eslint-config-base': + specifier: 4.1.0 + version: 4.1.0(@typescript-eslint/parser@6.21.0(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0) + '@eslint-kit/eslint-config-patch': + specifier: ^1.0.0 + version: 1.0.0(eslint@7.32.0) + '@eslint-kit/eslint-config-react': + specifier: ^3.0.0 + version: 3.0.0(eslint@7.32.0) + '@types/node': + specifier: ^22.9.0 + version: 22.13.10 + '@types/react': + specifier: ^18.3.12 + version: 18.3.18 + '@types/react-dom': + specifier: ^18.3.1 + version: 18.3.5(@types/react@18.3.18) + '@typescript-eslint/eslint-plugin': + specifier: ^6.21.0 + version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0)(typescript@5.8.2) + '@typescript-eslint/parser': + specifier: ^6.21.0 + version: 6.21.0(eslint@7.32.0)(typescript@5.8.2) + all-contributors-cli: + specifier: ^6.26.1 + version: 6.26.1 + docusaurus-plugin-sass: + specifier: ^0.2.5 + version: 0.2.6(@docusaurus/core@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2))(sass@1.85.1)(webpack@5.98.0(@swc/core@1.11.9)) + eslint: + specifier: ^7.32.0 + version: 7.32.0 + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@7.32.0) + eslint-import-resolver-alias: + specifier: 1.1.2 + version: 1.1.2(eslint-plugin-import@2.23.2(@typescript-eslint/parser@6.21.0(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0)) + prettier: + specifier: ^3.3.3 + version: 3.5.3 + sass: + specifier: ^1.81.0 + version: 1.85.1 + stylelint: + specifier: ^16.10.0 + version: 16.16.0(typescript@5.8.2) + stylelint-config-recess-order: + specifier: ^5.1.1 + version: 5.1.1(stylelint@16.16.0(typescript@5.8.2)) + stylelint-config-recommended: + specifier: ^14.0.1 + version: 14.0.1(stylelint@16.16.0(typescript@5.8.2)) + stylelint-config-standard-scss: + specifier: ^13.1.0 + version: 13.1.0(postcss@8.5.3)(stylelint@16.16.0(typescript@5.8.2)) + typescript: + specifier: ^5.6.3 + version: 5.8.2 packages: - /@algolia/autocomplete-core@1.9.3(@algolia/client-search@4.19.1)(algoliasearch@4.19.1)(search-insights@2.8.1): - resolution: {integrity: sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==} - dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(@algolia/client-search@4.19.1)(algoliasearch@4.19.1)(search-insights@2.8.1) - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.19.1)(algoliasearch@4.19.1) - transitivePeerDependencies: - - '@algolia/client-search' - - algoliasearch - - search-insights - dev: false + '@algolia/autocomplete-core@1.17.9': + resolution: {integrity: sha512-O7BxrpLDPJWWHv/DLA9DRFWs+iY1uOJZkqUwjS5HSZAGcl0hIVCQ97LTLewiZmZ402JYUrun+8NqFP+hCknlbQ==} - /@algolia/autocomplete-plugin-algolia-insights@1.9.3(@algolia/client-search@4.19.1)(algoliasearch@4.19.1)(search-insights@2.8.1): - resolution: {integrity: sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==} + '@algolia/autocomplete-plugin-algolia-insights@1.17.9': + resolution: {integrity: sha512-u1fEHkCbWF92DBeB/KHeMacsjsoI0wFhjZtlCq2ddZbAehshbZST6Hs0Avkc0s+4UyBGbMDnSuXHLuvRWK5iDQ==} peerDependencies: search-insights: '>= 1 < 3' - dependencies: - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.19.1)(algoliasearch@4.19.1) - search-insights: 2.8.1 - transitivePeerDependencies: - - '@algolia/client-search' - - algoliasearch - dev: false - /@algolia/autocomplete-preset-algolia@1.9.3(@algolia/client-search@4.19.1)(algoliasearch@4.19.1): - resolution: {integrity: sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==} + '@algolia/autocomplete-preset-algolia@1.17.9': + resolution: {integrity: sha512-Na1OuceSJeg8j7ZWn5ssMu/Ax3amtOwk76u4h5J4eK2Nx2KB5qt0Z4cOapCsxot9VcEN11ADV5aUSlQF4RhGjQ==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - dependencies: - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.19.1)(algoliasearch@4.19.1) - '@algolia/client-search': 4.19.1 - algoliasearch: 4.19.1 - dev: false - /@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.19.1)(algoliasearch@4.19.1): - resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==} + '@algolia/autocomplete-shared@1.17.9': + resolution: {integrity: sha512-iDf05JDQ7I0b7JEA/9IektxN/80a2MZ1ToohfmNS3rfeuQnIKI3IJlIafD0xu4StbtQTghx9T3Maa97ytkXenQ==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - dependencies: - '@algolia/client-search': 4.19.1 - algoliasearch: 4.19.1 - dev: false - - /@algolia/cache-browser-local-storage@4.19.1: - resolution: {integrity: sha512-FYAZWcGsFTTaSAwj9Std8UML3Bu8dyWDncM7Ls8g+58UOe4XYdlgzXWbrIgjaguP63pCCbMoExKr61B+ztK3tw==} - dependencies: - '@algolia/cache-common': 4.19.1 - dev: false - /@algolia/cache-common@4.19.1: - resolution: {integrity: sha512-XGghi3l0qA38HiqdoUY+wvGyBsGvKZ6U3vTiMBT4hArhP3fOGLXpIINgMiiGjTe4FVlTa5a/7Zf2bwlIHfRqqg==} - dev: false + '@algolia/client-abtesting@5.21.0': + resolution: {integrity: sha512-I239aSmXa3pXDhp3AWGaIfesqJBNFA7drUM8SIfNxMIzvQXUnHRf4rW1o77QXLI/nIClNsb8KOLaB62gO9LnlQ==} + engines: {node: '>= 14.0.0'} - /@algolia/cache-in-memory@4.19.1: - resolution: {integrity: sha512-+PDWL+XALGvIginigzu8oU6eWw+o76Z8zHbBovWYcrtWOEtinbl7a7UTt3x3lthv+wNuFr/YD1Gf+B+A9V8n5w==} - dependencies: - '@algolia/cache-common': 4.19.1 - dev: false + '@algolia/client-analytics@5.21.0': + resolution: {integrity: sha512-OxoUfeG9G4VE4gS7B4q65KkHzdGsQsDwxQfR5J9uKB8poSGuNlHJWsF3ABqCkc5VliAR0m8KMjsQ9o/kOpEGnQ==} + engines: {node: '>= 14.0.0'} - /@algolia/client-account@4.19.1: - resolution: {integrity: sha512-Oy0ritA2k7AMxQ2JwNpfaEcgXEDgeyKu0V7E7xt/ZJRdXfEpZcwp9TOg4TJHC7Ia62gIeT2Y/ynzsxccPw92GA==} - dependencies: - '@algolia/client-common': 4.19.1 - '@algolia/client-search': 4.19.1 - '@algolia/transporter': 4.19.1 - dev: false + '@algolia/client-common@5.21.0': + resolution: {integrity: sha512-iHLgDQFyZNe9M16vipbx6FGOA8NoMswHrfom/QlCGoyh7ntjGvfMb+J2Ss8rRsAlOWluv8h923Ku3QVaB0oWDQ==} + engines: {node: '>= 14.0.0'} - /@algolia/client-analytics@4.19.1: - resolution: {integrity: sha512-5QCq2zmgdZLIQhHqwl55ZvKVpLM3DNWjFI4T+bHr3rGu23ew2bLO4YtyxaZeChmDb85jUdPDouDlCumGfk6wOg==} - dependencies: - '@algolia/client-common': 4.19.1 - '@algolia/client-search': 4.19.1 - '@algolia/requester-common': 4.19.1 - '@algolia/transporter': 4.19.1 - dev: false + '@algolia/client-insights@5.21.0': + resolution: {integrity: sha512-y7XBO9Iwb75FLDl95AYcWSLIViJTpR5SUUCyKsYhpP9DgyUqWbISqDLXc96TS9shj+H+7VsTKA9cJK8NUfVN6g==} + engines: {node: '>= 14.0.0'} - /@algolia/client-common@4.19.1: - resolution: {integrity: sha512-3kAIVqTcPrjfS389KQvKzliC559x+BDRxtWamVJt8IVp7LGnjq+aVAXg4Xogkur1MUrScTZ59/AaUd5EdpyXgA==} - dependencies: - '@algolia/requester-common': 4.19.1 - '@algolia/transporter': 4.19.1 - dev: false + '@algolia/client-personalization@5.21.0': + resolution: {integrity: sha512-6KU658lD9Tss4oCX6c/O15tNZxw7vR+WAUG95YtZzYG/KGJHTpy2uckqbMmC2cEK4a86FAq4pH5azSJ7cGMjuw==} + engines: {node: '>= 14.0.0'} - /@algolia/client-personalization@4.19.1: - resolution: {integrity: sha512-8CWz4/H5FA+krm9HMw2HUQenizC/DxUtsI5oYC0Jxxyce1vsr8cb1aEiSJArQT6IzMynrERif1RVWLac1m36xw==} - dependencies: - '@algolia/client-common': 4.19.1 - '@algolia/requester-common': 4.19.1 - '@algolia/transporter': 4.19.1 - dev: false + '@algolia/client-query-suggestions@5.21.0': + resolution: {integrity: sha512-pG6MyVh1v0X+uwrKHn3U+suHdgJ2C+gug+UGkNHfMELHMsEoWIAQhxMBOFg7hCnWBFjQnuq6qhM3X9X5QO3d9Q==} + engines: {node: '>= 14.0.0'} - /@algolia/client-search@4.19.1: - resolution: {integrity: sha512-mBecfMFS4N+yK/p0ZbK53vrZbL6OtWMk8YmnOv1i0LXx4pelY8TFhqKoTit3NPVPwoSNN0vdSN9dTu1xr1XOVw==} - dependencies: - '@algolia/client-common': 4.19.1 - '@algolia/requester-common': 4.19.1 - '@algolia/transporter': 4.19.1 - dev: false + '@algolia/client-search@5.21.0': + resolution: {integrity: sha512-nZfgJH4njBK98tFCmCW1VX/ExH4bNOl9DSboxeXGgvhoL0fG1+4DDr/mrLe21OggVCQqHwXBMh6fFInvBeyhiQ==} + engines: {node: '>= 14.0.0'} - /@algolia/events@4.0.1: + '@algolia/events@4.0.1': resolution: {integrity: sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==} - dev: false - /@algolia/logger-common@4.19.1: - resolution: {integrity: sha512-i6pLPZW/+/YXKis8gpmSiNk1lOmYCmRI6+x6d2Qk1OdfvX051nRVdalRbEcVTpSQX6FQAoyeaui0cUfLYW5Elw==} - dev: false + '@algolia/ingestion@1.21.0': + resolution: {integrity: sha512-k6MZxLbZphGN5uRri9J/krQQBjUrqNcScPh985XXEFXbSCRvOPKVtjjLdVjGVHXXPOQgKrIZHxIdRNbHS+wVuA==} + engines: {node: '>= 14.0.0'} - /@algolia/logger-console@4.19.1: - resolution: {integrity: sha512-jj72k9GKb9W0c7TyC3cuZtTr0CngLBLmc8trzZlXdfvQiigpUdvTi1KoWIb2ZMcRBG7Tl8hSb81zEY3zI2RlXg==} - dependencies: - '@algolia/logger-common': 4.19.1 - dev: false + '@algolia/monitoring@1.21.0': + resolution: {integrity: sha512-FiW5nnmyHvaGdorqLClw3PM6keXexAMiwbwJ9xzQr4LcNefLG3ln82NafRPgJO/z0dETAOKjds5aSmEFMiITHQ==} + engines: {node: '>= 14.0.0'} - /@algolia/requester-browser-xhr@4.19.1: - resolution: {integrity: sha512-09K/+t7lptsweRTueHnSnmPqIxbHMowejAkn9XIcJMLdseS3zl8ObnS5GWea86mu3vy4+8H+ZBKkUN82Zsq/zg==} - dependencies: - '@algolia/requester-common': 4.19.1 - dev: false + '@algolia/recommend@5.21.0': + resolution: {integrity: sha512-+JXavbbliaLmah5QNgc/TDW/+r0ALa+rGhg5Y7+pF6GpNnzO0L+nlUaDNE8QbiJfz54F9BkwFUnJJeRJAuzTFw==} + engines: {node: '>= 14.0.0'} - /@algolia/requester-common@4.19.1: - resolution: {integrity: sha512-BisRkcWVxrDzF1YPhAckmi2CFYK+jdMT60q10d7z3PX+w6fPPukxHRnZwooiTUrzFe50UBmLItGizWHP5bDzVQ==} - dev: false + '@algolia/requester-browser-xhr@5.21.0': + resolution: {integrity: sha512-Iw+Yj5hOmo/iixHS94vEAQ3zi5GPpJywhfxn1el/zWo4AvPIte/+1h9Ywgw/+3M7YBj4jgAkScxjxQCxzLBsjA==} + engines: {node: '>= 14.0.0'} - /@algolia/requester-node-http@4.19.1: - resolution: {integrity: sha512-6DK52DHviBHTG2BK/Vv2GIlEw7i+vxm7ypZW0Z7vybGCNDeWzADx+/TmxjkES2h15+FZOqVf/Ja677gePsVItA==} - dependencies: - '@algolia/requester-common': 4.19.1 - dev: false + '@algolia/requester-fetch@5.21.0': + resolution: {integrity: sha512-Z00SRLlIFj3SjYVfsd9Yd3kB3dUwQFAkQG18NunWP7cix2ezXpJqA+xAoEf9vc4QZHdxU3Gm8gHAtRiM2iVaTQ==} + engines: {node: '>= 14.0.0'} - /@algolia/transporter@4.19.1: - resolution: {integrity: sha512-nkpvPWbpuzxo1flEYqNIbGz7xhfhGOKGAZS7tzC+TELgEmi7z99qRyTfNSUlW7LZmB3ACdnqAo+9A9KFBENviQ==} - dependencies: - '@algolia/cache-common': 4.19.1 - '@algolia/logger-common': 4.19.1 - '@algolia/requester-common': 4.19.1 - dev: false + '@algolia/requester-node-http@5.21.0': + resolution: {integrity: sha512-WqU0VumUILrIeVYCTGZlyyZoC/tbvhiyPxfGRRO1cSjxN558bnJLlR2BvS0SJ5b75dRNK7HDvtXo2QoP9eLfiA==} + engines: {node: '>= 14.0.0'} - /@ampproject/remapping@2.2.0: - resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.1.1 - '@jridgewell/trace-mapping': 0.3.15 - /@ampproject/remapping@2.2.1: - resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.19 + '@ant-design/colors@7.2.0': + resolution: {integrity: sha512-bjTObSnZ9C/O8MB/B4OUtd/q9COomuJAR2SYfhxLyHvCKn4EKwCN3e+fWGMo7H5InAyV0wL17jdE9ALrdOW/6A==} - /@ant-design/colors@6.0.0: - resolution: {integrity: sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==} - dependencies: - '@ctrl/tinycolor': 3.4.0 - dev: false + '@ant-design/fast-color@2.0.6': + resolution: {integrity: sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==} + engines: {node: '>=8.x'} - /@ant-design/icons-svg@4.1.0: - resolution: {integrity: sha512-Fi03PfuUqRs76aI3UWYpP864lkrfPo0hluwGqh7NJdLhvH4iRDc3jbJqZIvRDLHKbXrvAfPPV3+zjUccfFvWOQ==} - dev: false + '@ant-design/icons-svg@4.4.2': + resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==} - /@ant-design/icons@4.6.2(react-dom@17.0.2)(react@17.0.2): - resolution: {integrity: sha512-QsBG2BxBYU/rxr2eb8b2cZ4rPKAPBpzAR+0v6rrZLp/lnyvflLH3tw1vregK+M7aJauGWjIGNdFmUfpAOtw25A==} + '@ant-design/icons@5.6.1': + resolution: {integrity: sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==} engines: {node: '>=8'} peerDependencies: react: '>=16.0.0' - dependencies: - '@ant-design/colors': 6.0.0 - '@ant-design/icons-svg': 4.1.0 - '@babel/runtime': 7.19.0 - classnames: 2.3.1 - rc-util: 5.12.2(react-dom@17.0.2)(react@17.0.2) - react: 17.0.2 - transitivePeerDependencies: - - react-dom - dev: false + react-dom: '>=16.0.0' - /@babel/code-frame@7.12.11: + '@babel/code-frame@7.12.11': resolution: {integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==} - dependencies: - '@babel/highlight': 7.18.6 - /@babel/code-frame@7.18.6: - resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.18.6 - /@babel/code-frame@7.22.13: - resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} + '@babel/compat-data@7.26.8': + resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.22.13 - chalk: 2.4.2 - /@babel/compat-data@7.19.1: - resolution: {integrity: sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg==} + '@babel/core@7.26.10': + resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} engines: {node: '>=6.9.0'} - /@babel/compat-data@7.22.9: - resolution: {integrity: sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==} + '@babel/eslint-parser@7.26.10': + resolution: {integrity: sha512-QsfQZr4AiLpKqn7fz+j7SN+f43z2DZCgGyYbNJ2vJOqKfG4E6MZer1+jqGZqKJaxq/gdO2DC/nUu45+pOL5p2Q==} + engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} + peerDependencies: + '@babel/core': ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 + + '@babel/generator@7.26.10': + resolution: {integrity: sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==} engines: {node: '>=6.9.0'} - /@babel/core@7.12.9: - resolution: {integrity: sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==} + '@babel/helper-annotate-as-pure@7.25.9': + resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.22.13 - '@babel/generator': 7.22.10 - '@babel/helper-module-transforms': 7.22.9(@babel/core@7.12.9) - '@babel/helpers': 7.22.11 - '@babel/parser': 7.22.14 - '@babel/template': 7.22.5 - '@babel/traverse': 7.22.11 - '@babel/types': 7.22.11 - convert-source-map: 1.9.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - lodash: 4.17.21 - resolve: 1.22.4 - semver: 5.7.2 - source-map: 0.5.7 - transitivePeerDependencies: - - supports-color - /@babel/core@7.19.1: - resolution: {integrity: sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw==} + '@babel/helper-compilation-targets@7.26.5': + resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.0 - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.19.0 - '@babel/helper-compilation-targets': 7.19.1(@babel/core@7.19.1) - '@babel/helper-module-transforms': 7.19.0 - '@babel/helpers': 7.19.0 - '@babel/parser': 7.19.1 - '@babel/template': 7.18.10 - '@babel/traverse': 7.19.1 - '@babel/types': 7.19.0 - convert-source-map: 1.8.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.1 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - /@babel/core@7.22.11: - resolution: {integrity: sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ==} + '@babel/helper-create-class-features-plugin@7.26.9': + resolution: {integrity: sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==} engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.22.13 - '@babel/generator': 7.22.10 - '@babel/helper-compilation-targets': 7.22.10 - '@babel/helper-module-transforms': 7.22.9(@babel/core@7.22.11) - '@babel/helpers': 7.22.11 - '@babel/parser': 7.22.14 - '@babel/template': 7.22.5 - '@babel/traverse': 7.22.11 - '@babel/types': 7.22.11 - convert-source-map: 1.9.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color + peerDependencies: + '@babel/core': ^7.0.0 - /@babel/eslint-parser@7.21.3(@babel/core@7.19.1)(eslint@7.32.0): - resolution: {integrity: sha512-kfhmPimwo6k4P8zxNs8+T7yR44q1LdpsZdE1NkCsVlfiuTPRfnGgjaF8Qgug9q9Pou17u6wneYF0lDCZJATMFg==} - engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} + '@babel/helper-create-regexp-features-plugin@7.26.3': + resolution: {integrity: sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': '>=7.11.0' - eslint: ^7.5.0 || ^8.0.0 - dependencies: - '@babel/core': 7.19.1 - '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 - eslint: 7.32.0 - eslint-visitor-keys: 2.1.0 - semver: 6.3.0 - dev: true + '@babel/core': ^7.0.0 - /@babel/eslint-parser@7.21.3(@babel/core@7.22.11)(eslint@7.32.0): - resolution: {integrity: sha512-kfhmPimwo6k4P8zxNs8+T7yR44q1LdpsZdE1NkCsVlfiuTPRfnGgjaF8Qgug9q9Pou17u6wneYF0lDCZJATMFg==} - engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} + '@babel/helper-define-polyfill-provider@0.6.3': + resolution: {integrity: sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==} peerDependencies: - '@babel/core': '>=7.11.0' - eslint: ^7.5.0 || ^8.0.0 - dependencies: - '@babel/core': 7.22.11 - '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 - eslint: 7.32.0 - eslint-visitor-keys: 2.1.0 - semver: 6.3.0 - dev: true + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - /@babel/generator@7.19.0: - resolution: {integrity: sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==} + '@babel/helper-member-expression-to-functions@7.25.9': + resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.0 - '@jridgewell/gen-mapping': 0.3.2 - jsesc: 2.5.2 - /@babel/generator@7.22.10: - resolution: {integrity: sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==} + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.11 - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.19 - jsesc: 2.5.2 - /@babel/helper-annotate-as-pure@7.18.6: - resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.0 - dev: false + peerDependencies: + '@babel/core': ^7.0.0 - /@babel/helper-annotate-as-pure@7.22.5: - resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} + '@babel/helper-optimise-call-expression@7.25.9': + resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.11 - /@babel/helper-builder-binary-assignment-operator-visitor@7.18.9: - resolution: {integrity: sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==} + '@babel/helper-plugin-utils@7.26.5': + resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-explode-assignable-expression': 7.18.6 - '@babel/types': 7.19.0 - dev: false - /@babel/helper-builder-binary-assignment-operator-visitor@7.22.10: - resolution: {integrity: sha512-Av0qubwDQxC56DoUReVDeLfMEjYYSN1nZrTUrWkXd7hpU73ymRANkbuDm3yni9npkn+RXy9nNbEJZEzXr7xrfQ==} + '@babel/helper-remap-async-to-generator@7.25.9': + resolution: {integrity: sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.11 + peerDependencies: + '@babel/core': ^7.0.0 - /@babel/helper-compilation-targets@7.19.1(@babel/core@7.19.1): - resolution: {integrity: sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg==} + '@babel/helper-replace-supers@7.26.5': + resolution: {integrity: sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.19.1 - '@babel/core': 7.19.1 - '@babel/helper-validator-option': 7.18.6 - browserslist: 4.21.4 - semver: 6.3.0 - /@babel/helper-compilation-targets@7.22.10: - resolution: {integrity: sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==} + '@babel/helper-skip-transparent-expression-wrappers@7.25.9': + resolution: {integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/compat-data': 7.22.9 - '@babel/helper-validator-option': 7.22.5 - browserslist: 4.21.10 - lru-cache: 5.1.1 - semver: 6.3.1 - /@babel/helper-create-class-features-plugin@7.19.0(@babel/core@7.19.1): - resolution: {integrity: sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==} + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 - '@babel/helper-member-expression-to-functions': 7.18.9 - '@babel/helper-optimise-call-expression': 7.18.6 - '@babel/helper-replace-supers': 7.19.1 - '@babel/helper-split-export-declaration': 7.18.6 - transitivePeerDependencies: - - supports-color - dev: false - /@babel/helper-create-class-features-plugin@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-y1grdYL4WzmUDBRGK0pDbIoFd7UZKoDurDzWEoNMYoj1EL+foGRQNyPWDcC+YyegN5y1DUsFFmzjGijB3nSVAQ==} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.25.9': + resolution: {integrity: sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.26.10': + resolution: {integrity: sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.25.9': + resolution: {integrity: sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.10': + resolution: {integrity: sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9': + resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-function-name': 7.22.5 - '@babel/helper-member-expression-to-functions': 7.22.5 - '@babel/helper-optimise-call-expression': 7.22.5 - '@babel/helper-replace-supers': 7.22.9(@babel/core@7.22.11) - '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - semver: 6.3.1 - /@babel/helper-create-regexp-features-plugin@7.19.0(@babel/core@7.19.1): - resolution: {integrity: sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==} + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9': + resolution: {integrity: sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-annotate-as-pure': 7.18.6 - regexpu-core: 5.2.1 - dev: false - /@babel/helper-create-regexp-features-plugin@7.22.9(@babel/core@7.22.11): - resolution: {integrity: sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==} + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9': + resolution: {integrity: sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-annotate-as-pure': 7.22.5 - regexpu-core: 5.3.2 - semver: 6.3.1 - /@babel/helper-define-polyfill-provider@0.3.3(@babel/core@7.19.1): - resolution: {integrity: sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==} + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9': + resolution: {integrity: sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.4.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-compilation-targets': 7.19.1(@babel/core@7.19.1) - '@babel/helper-plugin-utils': 7.19.0 - debug: 4.3.4 - lodash.debounce: 4.0.8 - resolve: 1.22.1 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - dev: false + '@babel/core': ^7.13.0 - /@babel/helper-define-polyfill-provider@0.4.2(@babel/core@7.22.11): - resolution: {integrity: sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw==} + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9': + resolution: {integrity: sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-compilation-targets': 7.22.10 - '@babel/helper-plugin-utils': 7.22.5 - debug: 4.3.4 - lodash.debounce: 4.0.8 - resolve: 1.22.4 - transitivePeerDependencies: - - supports-color + '@babel/core': ^7.0.0 - /@babel/helper-environment-visitor@7.18.9: - resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-environment-visitor@7.22.5: - resolution: {integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==} - engines: {node: '>=6.9.0'} + '@babel/plugin-syntax-dynamic-import@7.8.3': + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-explode-assignable-expression@7.18.6: - resolution: {integrity: sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==} + '@babel/plugin-syntax-import-assertions@7.26.0': + resolution: {integrity: sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.0 - dev: false + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-function-name@7.19.0: - resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==} + '@babel/plugin-syntax-import-attributes@7.26.0': + resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.18.10 - '@babel/types': 7.19.0 + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-function-name@7.22.5: - resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==} + '@babel/plugin-syntax-jsx@7.25.9': + resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.22.5 - '@babel/types': 7.22.11 + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-hoist-variables@7.18.6: - resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} + '@babel/plugin-syntax-typescript@7.25.9': + resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.0 + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-hoist-variables@7.22.5: - resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.11 + peerDependencies: + '@babel/core': ^7.0.0 - /@babel/helper-member-expression-to-functions@7.18.9: - resolution: {integrity: sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==} + '@babel/plugin-transform-arrow-functions@7.25.9': + resolution: {integrity: sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.0 - dev: false + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-member-expression-to-functions@7.22.5: - resolution: {integrity: sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==} + '@babel/plugin-transform-async-generator-functions@7.26.8': + resolution: {integrity: sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.11 + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-module-imports@7.18.6: - resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} + '@babel/plugin-transform-async-to-generator@7.25.9': + resolution: {integrity: sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.0 + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-module-imports@7.22.5: - resolution: {integrity: sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==} + '@babel/plugin-transform-block-scoped-functions@7.26.5': + resolution: {integrity: sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.11 + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-module-transforms@7.19.0: - resolution: {integrity: sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==} + '@babel/plugin-transform-block-scoping@7.25.9': + resolution: {integrity: sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-module-imports': 7.18.6 - '@babel/helper-simple-access': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/helper-validator-identifier': 7.19.1 - '@babel/template': 7.18.10 - '@babel/traverse': 7.19.1 - '@babel/types': 7.19.0 - transitivePeerDependencies: - - supports-color + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-module-transforms@7.22.9(@babel/core@7.12.9): - resolution: {integrity: sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==} + '@babel/plugin-transform-class-properties@7.25.9': + resolution: {integrity: sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.12.9 - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-module-imports': 7.22.5 - '@babel/helper-simple-access': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/helper-validator-identifier': 7.22.5 + '@babel/core': ^7.0.0-0 - /@babel/helper-module-transforms@7.22.9(@babel/core@7.22.11): - resolution: {integrity: sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==} + '@babel/plugin-transform-class-static-block@7.26.0': + resolution: {integrity: sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-module-imports': 7.22.5 - '@babel/helper-simple-access': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/helper-validator-identifier': 7.22.5 + '@babel/core': ^7.12.0 - /@babel/helper-optimise-call-expression@7.18.6: - resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} + '@babel/plugin-transform-classes@7.25.9': + resolution: {integrity: sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.0 - dev: false + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-optimise-call-expression@7.22.5: - resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} + '@babel/plugin-transform-computed-properties@7.25.9': + resolution: {integrity: sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.11 + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-plugin-utils@7.10.4: - resolution: {integrity: sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==} + '@babel/plugin-transform-destructuring@7.25.9': + resolution: {integrity: sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-plugin-utils@7.19.0: - resolution: {integrity: sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==} + '@babel/plugin-transform-dotall-regex@7.25.9': + resolution: {integrity: sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-plugin-utils@7.22.5: - resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} + '@babel/plugin-transform-duplicate-keys@7.25.9': + resolution: {integrity: sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-remap-async-to-generator@7.18.9(@babel/core@7.19.1): - resolution: {integrity: sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==} + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9': + resolution: {integrity: sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-wrap-function': 7.19.0 - '@babel/types': 7.19.0 - transitivePeerDependencies: - - supports-color - dev: false - /@babel/helper-remap-async-to-generator@7.22.9(@babel/core@7.22.11): - resolution: {integrity: sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ==} + '@babel/plugin-transform-dynamic-import@7.25.9': + resolution: {integrity: sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-wrap-function': 7.22.10 + '@babel/core': ^7.0.0-0 - /@babel/helper-replace-supers@7.19.1: - resolution: {integrity: sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==} + '@babel/plugin-transform-exponentiation-operator@7.26.3': + resolution: {integrity: sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-member-expression-to-functions': 7.18.9 - '@babel/helper-optimise-call-expression': 7.18.6 - '@babel/traverse': 7.19.1 - '@babel/types': 7.19.0 - transitivePeerDependencies: - - supports-color - dev: false + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-replace-supers@7.22.9(@babel/core@7.22.11): - resolution: {integrity: sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==} + '@babel/plugin-transform-export-namespace-from@7.25.9': + resolution: {integrity: sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-member-expression-to-functions': 7.22.5 - '@babel/helper-optimise-call-expression': 7.22.5 + '@babel/core': ^7.0.0-0 - /@babel/helper-simple-access@7.18.6: - resolution: {integrity: sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==} + '@babel/plugin-transform-for-of@7.26.9': + resolution: {integrity: sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.0 + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-simple-access@7.22.5: - resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} + '@babel/plugin-transform-function-name@7.25.9': + resolution: {integrity: sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.11 + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-skip-transparent-expression-wrappers@7.18.9: - resolution: {integrity: sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==} + '@babel/plugin-transform-json-strings@7.25.9': + resolution: {integrity: sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.0 - dev: false + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-skip-transparent-expression-wrappers@7.22.5: - resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} + '@babel/plugin-transform-literals@7.25.9': + resolution: {integrity: sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.11 + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-split-export-declaration@7.18.6: - resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} + '@babel/plugin-transform-logical-assignment-operators@7.25.9': + resolution: {integrity: sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.0 + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-split-export-declaration@7.22.6: - resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} + '@babel/plugin-transform-member-expression-literals@7.25.9': + resolution: {integrity: sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.11 + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-string-parser@7.18.10: - resolution: {integrity: sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==} + '@babel/plugin-transform-modules-amd@7.25.9': + resolution: {integrity: sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-string-parser@7.22.5: - resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} + '@babel/plugin-transform-modules-commonjs@7.26.3': + resolution: {integrity: sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-validator-identifier@7.19.1: - resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + '@babel/plugin-transform-modules-systemjs@7.25.9': + resolution: {integrity: sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-validator-identifier@7.22.5: - resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} + '@babel/plugin-transform-modules-umd@7.25.9': + resolution: {integrity: sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-validator-option@7.18.6: - resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9': + resolution: {integrity: sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 - /@babel/helper-validator-option@7.22.5: - resolution: {integrity: sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==} + '@babel/plugin-transform-new-target@7.25.9': + resolution: {integrity: sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-wrap-function@7.19.0: - resolution: {integrity: sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==} + '@babel/plugin-transform-nullish-coalescing-operator@7.26.6': + resolution: {integrity: sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-function-name': 7.19.0 - '@babel/template': 7.18.10 - '@babel/traverse': 7.19.1 - '@babel/types': 7.19.0 - transitivePeerDependencies: - - supports-color - dev: false + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helper-wrap-function@7.22.10: - resolution: {integrity: sha512-OnMhjWjuGYtdoO3FmsEFWvBStBAe2QOgwOLsLNDjN+aaiMD8InJk1/O3HSD8lkqTjCgg5YI34Tz15KNNA3p+nQ==} + '@babel/plugin-transform-numeric-separator@7.25.9': + resolution: {integrity: sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-function-name': 7.22.5 - '@babel/template': 7.22.5 - '@babel/types': 7.22.11 + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helpers@7.19.0: - resolution: {integrity: sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==} + '@babel/plugin-transform-object-rest-spread@7.25.9': + resolution: {integrity: sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.18.10 - '@babel/traverse': 7.19.1 - '@babel/types': 7.19.0 - transitivePeerDependencies: - - supports-color + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/helpers@7.22.11: - resolution: {integrity: sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==} + '@babel/plugin-transform-object-super@7.25.9': + resolution: {integrity: sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.22.5 - '@babel/traverse': 7.22.11 - '@babel/types': 7.22.11 - transitivePeerDependencies: - - supports-color + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/highlight@7.18.6: - resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} + '@babel/plugin-transform-optional-catch-binding@7.25.9': + resolution: {integrity: sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.19.1 - chalk: 2.4.2 - js-tokens: 4.0.0 + peerDependencies: + '@babel/core': ^7.0.0-0 - /@babel/highlight@7.22.13: - resolution: {integrity: sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.22.5 - chalk: 2.4.2 - js-tokens: 4.0.0 - - /@babel/parser@7.19.1: - resolution: {integrity: sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.19.0 - - /@babel/parser@7.22.14: - resolution: {integrity: sha512-1KucTHgOvaw/LzCVrEOAyXkr9rQlp0A1HiHRYnSUE9dmb8PvPW7o5sscg+5169r54n3vGlbx6GevTE/Iw/P3AQ==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.22.11 - - /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false - - /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - - /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.18.9(@babel/core@7.19.1): - resolution: {integrity: sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==} + '@babel/plugin-transform-optional-chaining@7.25.9': + resolution: {integrity: sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.13.0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/helper-skip-transparent-expression-wrappers': 7.18.9 - '@babel/plugin-proposal-optional-chaining': 7.18.9(@babel/core@7.19.1) - dev: false + '@babel/core': ^7.0.0-0 - /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==} + '@babel/plugin-transform-parameters@7.25.9': + resolution: {integrity: sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.13.0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-transform-optional-chaining': 7.22.12(@babel/core@7.22.11) + '@babel/core': ^7.0.0-0 - /@babel/plugin-proposal-async-generator-functions@7.19.1(@babel/core@7.19.1): - resolution: {integrity: sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q==} + '@babel/plugin-transform-private-methods@7.25.9': + resolution: {integrity: sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead. peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.19.1) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.19.1) - transitivePeerDependencies: - - supports-color - dev: false - /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} + '@babel/plugin-transform-private-property-in-object@7.25.9': + resolution: {integrity: sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-create-class-features-plugin': 7.19.0(@babel/core@7.19.1) - '@babel/helper-plugin-utils': 7.19.0 - transitivePeerDependencies: - - supports-color - dev: false - /@babel/plugin-proposal-class-static-block@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==} + '@babel/plugin-transform-property-literals@7.25.9': + resolution: {integrity: sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-static-block instead. peerDependencies: - '@babel/core': ^7.12.0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-create-class-features-plugin': 7.19.0(@babel/core@7.19.1) - '@babel/helper-plugin-utils': 7.19.0 - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.19.1) - transitivePeerDependencies: - - supports-color - dev: false + '@babel/core': ^7.0.0-0 - /@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==} + '@babel/plugin-transform-react-constant-elements@7.25.9': + resolution: {integrity: sha512-Ncw2JFsJVuvfRsa2lSHiC55kETQVLSnsYGQ1JDDwkUeWGTL/8Tom8aLTnlqgoeuopWrbbGndrc9AlLYrIosrow==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-dynamic-import instead. peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.19.1) - dev: false - /@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.19.1): - resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==} + '@babel/plugin-transform-react-display-name@7.25.9': + resolution: {integrity: sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead. peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.19.1) - dev: false - /@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} + '@babel/plugin-transform-react-jsx-development@7.25.9': + resolution: {integrity: sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-json-strings instead. peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.19.1) - dev: false - /@babel/plugin-proposal-logical-assignment-operators@7.18.9(@babel/core@7.19.1): - resolution: {integrity: sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==} + '@babel/plugin-transform-react-jsx@7.25.9': + resolution: {integrity: sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead. peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.19.1) - dev: false - /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} + '@babel/plugin-transform-react-pure-annotations@7.25.9': + resolution: {integrity: sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead. peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.19.1) - dev: false - /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} + '@babel/plugin-transform-regenerator@7.25.9': + resolution: {integrity: sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead. peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.19.1) - dev: false - /@babel/plugin-proposal-object-rest-spread@7.12.1(@babel/core@7.12.9): - resolution: {integrity: sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead. + '@babel/plugin-transform-regexp-modifiers@7.26.0': + resolution: {integrity: sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.12.9 - '@babel/helper-plugin-utils': 7.10.4 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.12.9) - '@babel/plugin-transform-parameters': 7.22.5(@babel/core@7.12.9) + '@babel/core': ^7.0.0 - /@babel/plugin-proposal-object-rest-spread@7.18.9(@babel/core@7.19.1): - resolution: {integrity: sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==} + '@babel/plugin-transform-reserved-words@7.25.9': + resolution: {integrity: sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead. peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/compat-data': 7.19.1 - '@babel/core': 7.19.1 - '@babel/helper-compilation-targets': 7.19.1(@babel/core@7.19.1) - '@babel/helper-plugin-utils': 7.19.0 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.19.1) - '@babel/plugin-transform-parameters': 7.18.8(@babel/core@7.19.1) - dev: false - /@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} + '@babel/plugin-transform-runtime@7.26.10': + resolution: {integrity: sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead. peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.19.1) - dev: false - /@babel/plugin-proposal-optional-chaining@7.18.9(@babel/core@7.19.1): - resolution: {integrity: sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==} + '@babel/plugin-transform-shorthand-properties@7.25.9': + resolution: {integrity: sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead. peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/helper-skip-transparent-expression-wrappers': 7.18.9 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.19.1) - dev: false - /@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} + '@babel/plugin-transform-spread@7.25.9': + resolution: {integrity: sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead. peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-create-class-features-plugin': 7.19.0(@babel/core@7.19.1) - '@babel/helper-plugin-utils': 7.19.0 - transitivePeerDependencies: - - supports-color - dev: false - /@babel/plugin-proposal-private-property-in-object@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==} + '@babel/plugin-transform-sticky-regex@7.25.9': + resolution: {integrity: sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead. peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-create-class-features-plugin': 7.19.0(@babel/core@7.19.1) - '@babel/helper-plugin-utils': 7.19.0 - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.19.1) - transitivePeerDependencies: - - supports-color - dev: false - /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.22.11): - resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + '@babel/plugin-transform-template-literals@7.26.8': + resolution: {integrity: sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - /@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} - engines: {node: '>=4'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead. + '@babel/plugin-transform-typeof-symbol@7.26.7': + resolution: {integrity: sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==} + engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-create-regexp-features-plugin': 7.19.0(@babel/core@7.19.1) - '@babel/helper-plugin-utils': 7.19.0 - dev: false - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.19.1): - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + '@babel/plugin-transform-typescript@7.26.8': + resolution: {integrity: sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw==} + engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.22.11): - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + '@babel/plugin-transform-unicode-escapes@7.25.9': + resolution: {integrity: sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==} + engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.19.0 - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.19.1): - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + '@babel/plugin-transform-unicode-property-regex@7.25.9': + resolution: {integrity: sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==} + engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.22.11): - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + '@babel/plugin-transform-unicode-regex@7.25.9': + resolution: {integrity: sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==} + engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.19.0 - /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.19.1): - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + '@babel/plugin-transform-unicode-sets-regex@7.25.9': + resolution: {integrity: sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@babel/core': ^7.0.0 - /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.22.11): - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + '@babel/preset-env@7.26.9': + resolution: {integrity: sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.19.0 - /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.19.1): - resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.22.5 - dev: false + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.22.11): - resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + '@babel/preset-react@7.26.3': + resolution: {integrity: sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==} + engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.19.1): - resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + '@babel/preset-typescript@7.26.0': + resolution: {integrity: sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==} + engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false - /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.22.11): - resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.19.0 + '@babel/runtime-corejs3@7.26.10': + resolution: {integrity: sha512-uITFQYO68pMEYR46AHgQoyBg7KPPJDAbGn4jUTIRgCFJIp88MIBUianVOplhZDEec07bp9zIyr4Kp0FCyQzmWg==} + engines: {node: '>=6.9.0'} - /@babel/plugin-syntax-import-assertions@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==} + '@babel/runtime@7.26.10': + resolution: {integrity: sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false - /@babel/plugin-syntax-import-assertions@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==} + '@babel/template@7.26.9': + resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - /@babel/plugin-syntax-import-attributes@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==} + '@babel/traverse@7.26.10': + resolution: {integrity: sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.22.11): - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + '@babel/types@7.26.10': + resolution: {integrity: sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==} + engines: {node: '>=6.9.0'} + + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + + '@csstools/cascade-layer-name-parser@2.0.4': + resolution: {integrity: sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/color-helpers@5.0.2': + resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} + engines: {node: '>=18'} - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.19.1): - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + '@csstools/css-calc@2.1.2': + resolution: {integrity: sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.22.11): - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + '@csstools/css-color-parser@3.0.8': + resolution: {integrity: sha512-pdwotQjCCnRPuNi06jFuP68cykU1f3ZWExLe/8MQ1LOs8Xq+fTkYgd+2V8mWUWMrOn9iS2HftPVaMZDaXzGbhQ==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.19.0 + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 - /@babel/plugin-syntax-jsx@7.12.1(@babel/core@7.12.9): - resolution: {integrity: sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==} + '@csstools/css-parser-algorithms@3.0.4': + resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.12.9 - '@babel/helper-plugin-utils': 7.22.5 + '@csstools/css-tokenizer': ^3.0.3 - /@babel/plugin-syntax-jsx@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} - engines: {node: '>=6.9.0'} + '@csstools/css-tokenizer@3.0.3': + resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} + engines: {node: '>=18'} + + '@csstools/media-query-list-parser@4.0.2': + resolution: {integrity: sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 - /@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-cascade-layers@5.0.1': + resolution: {integrity: sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + postcss: ^8.4 - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.19.1): - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + '@csstools/postcss-color-function@4.0.8': + resolution: {integrity: sha512-9dUvP2qpZI6PlGQ/sob+95B3u5u7nkYt9yhZFCC7G9HBRHBxj+QxS/wUlwaMGYW0waf+NIierI8aoDTssEdRYw==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + postcss: ^8.4 - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.22.11): - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + '@csstools/postcss-color-mix-function@3.0.8': + resolution: {integrity: sha512-yuZpgWUzqZWQhEqfvtJufhl28DgO9sBwSbXbf/59gejNuvZcoUTRGQZhzhwF4ccqb53YAGB+u92z9+eSKoB4YA==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.19.0 + postcss: ^8.4 - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.19.1): - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + '@csstools/postcss-content-alt-text@2.0.4': + resolution: {integrity: sha512-YItlZUOuZJCBlRaCf8Aucc1lgN41qYGALMly0qQllrxYJhiyzlI6RxOTMUvtWk+KhS8GphMDsDhKQ7KTPfEMSw==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + postcss: ^8.4 - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.22.11): - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + '@csstools/postcss-exponential-functions@2.0.7': + resolution: {integrity: sha512-XTb6Mw0v2qXtQYRW9d9duAjDnoTbBpsngD7sRNLmYDjvwU2ebpIHplyxgOeo6jp/Kr52gkLi5VaK5RDCqzMzZQ==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.19.0 + postcss: ^8.4 - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.19.1): - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + '@csstools/postcss-font-format-keywords@4.0.0': + resolution: {integrity: sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + postcss: ^8.4 - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.22.11): - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + '@csstools/postcss-gamut-mapping@2.0.8': + resolution: {integrity: sha512-/K8u9ZyGMGPjmwCSIjgaOLKfic2RIGdFHHes84XW5LnmrvdhOTVxo255NppHi3ROEvoHPW7MplMJgjZK5Q+TxA==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.19.0 + postcss: ^8.4 - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.12.9): - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + '@csstools/postcss-gradients-interpolation-method@5.0.8': + resolution: {integrity: sha512-CoHQ/0UXrvxLovu0ZeW6c3/20hjJ/QRg6lyXm3dZLY/JgvRU6bdbQZF/Du30A4TvowfcgvIHQmP1bNXUxgDrAw==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.12.9 - '@babel/helper-plugin-utils': 7.19.0 + postcss: ^8.4 - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.19.1): - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + '@csstools/postcss-hwb-function@4.0.8': + resolution: {integrity: sha512-LpFKjX6hblpeqyych1cKmk+3FJZ19QmaJtqincySoMkbkG/w2tfbnO5oE6mlnCTXcGUJ0rCEuRHvTqKK0nHYUQ==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + postcss: ^8.4 - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.22.11): - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + '@csstools/postcss-ic-unit@4.0.0': + resolution: {integrity: sha512-9QT5TDGgx7wD3EEMN3BSUG6ckb6Eh5gSPT5kZoVtUuAonfPmLDJyPhqR4ntPpMYhUKAMVKAg3I/AgzqHMSeLhA==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.19.0 + postcss: ^8.4 - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.19.1): - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + '@csstools/postcss-initial@2.0.1': + resolution: {integrity: sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + postcss: ^8.4 - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.22.11): - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + '@csstools/postcss-is-pseudo-class@5.0.1': + resolution: {integrity: sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.19.0 + postcss: ^8.4 - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.19.1): - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + '@csstools/postcss-light-dark-function@2.0.7': + resolution: {integrity: sha512-ZZ0rwlanYKOHekyIPaU+sVm3BEHCe+Ha0/px+bmHe62n0Uc1lL34vbwrLYn6ote8PHlsqzKeTQdIejQCJ05tfw==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + postcss: ^8.4 - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.22.11): - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + '@csstools/postcss-logical-float-and-clear@3.0.0': + resolution: {integrity: sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.19.0 + postcss: ^8.4 - /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.19.1): - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-logical-overflow@2.0.0': + resolution: {integrity: sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + postcss: ^8.4 - /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.22.11): - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-logical-overscroll-behavior@2.0.0': + resolution: {integrity: sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.19.0 + postcss: ^8.4 - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.19.1): - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-logical-resize@3.0.0': + resolution: {integrity: sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + postcss: ^8.4 - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.22.11): - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-logical-viewport-units@3.0.3': + resolution: {integrity: sha512-OC1IlG/yoGJdi0Y+7duz/kU/beCwO+Gua01sD6GtOtLi7ByQUpcIqs7UE/xuRPay4cHgOMatWdnDdsIDjnWpPw==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.19.0 + postcss: ^8.4 - /@babel/plugin-syntax-typescript@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-media-minmax@2.0.7': + resolution: {integrity: sha512-LB6tIP7iBZb5CYv8iRenfBZmbaG3DWNEziOnPjGoQX5P94FBPvvTBy68b/d9NnS5PELKwFmmOYsAEIgEhDPCHA==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + postcss: ^8.4 - /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.22.11): - resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.4': + resolution: {integrity: sha512-AnGjVslHMm5xw9keusQYvjVWvuS7KWK+OJagaG0+m9QnIjZsrysD2kJP/tr/UJIyYtMCtu8OkUd+Rajb4DqtIQ==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-create-regexp-features-plugin': 7.22.9(@babel/core@7.22.11) - '@babel/helper-plugin-utils': 7.22.5 + postcss: ^8.4 - /@babel/plugin-transform-arrow-functions@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-nested-calc@4.0.0': + resolution: {integrity: sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + postcss: ^8.4 - /@babel/plugin-transform-arrow-functions@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-normalize-display-values@4.0.0': + resolution: {integrity: sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + postcss: ^8.4 - /@babel/plugin-transform-async-generator-functions@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-0pAlmeRJn6wU84zzZsEOx1JV1Jf8fqO9ok7wofIJwUnplYo247dcd24P+cMJht7ts9xkzdtB0EPHmOb7F+KzXw==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-oklab-function@4.0.8': + resolution: {integrity: sha512-+5aPsNWgxohXoYNS1f+Ys0x3Qnfehgygv3qrPyv+Y25G0yX54/WlVB+IXprqBLOXHM1gsVF+QQSjlArhygna0Q==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-remap-async-to-generator': 7.22.9(@babel/core@7.22.11) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.11) + postcss: ^8.4 - /@babel/plugin-transform-async-to-generator@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-progressive-custom-properties@4.0.0': + resolution: {integrity: sha512-XQPtROaQjomnvLUSy/bALTR5VCtTVUFwYs1SblvYgLSeTo2a/bMNwUwo2piXw5rTv/FEYiy5yPSXBqg9OKUx7Q==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-module-imports': 7.18.6 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.19.1) - transitivePeerDependencies: - - supports-color - dev: false + postcss: ^8.4 - /@babel/plugin-transform-async-to-generator@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-random-function@1.0.3': + resolution: {integrity: sha512-dbNeEEPHxAwfQJ3duRL5IPpuD77QAHtRl4bAHRs0vOVhVbHrsL7mHnwe0irYjbs9kYwhAHZBQTLBgmvufPuRkA==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-module-imports': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-remap-async-to-generator': 7.22.9(@babel/core@7.22.11) + postcss: ^8.4 - /@babel/plugin-transform-block-scoped-functions@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-relative-color-syntax@3.0.8': + resolution: {integrity: sha512-eGE31oLnJDoUysDdjS9MLxNZdtqqSxjDXMdISpLh80QMaYrKs7VINpid34tWQ+iU23Wg5x76qAzf1Q/SLLbZVg==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + postcss: ^8.4 - /@babel/plugin-transform-block-scoped-functions@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-scope-pseudo-class@4.0.1': + resolution: {integrity: sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + postcss: ^8.4 - /@babel/plugin-transform-block-scoping@7.18.9(@babel/core@7.19.1): - resolution: {integrity: sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-sign-functions@1.1.2': + resolution: {integrity: sha512-4EcAvXTUPh7n6UoZZkCzgtCf/wPzMlTNuddcKg7HG8ozfQkUcHsJ2faQKeLmjyKdYPyOUn4YA7yDPf8K/jfIxw==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + postcss: ^8.4 - /@babel/plugin-transform-block-scoping@7.22.10(@babel/core@7.22.11): - resolution: {integrity: sha512-1+kVpGAOOI1Albt6Vse7c8pHzcZQdQKW+wJH+g8mCaszOdDVwRXa/slHPqIw+oJAJANTKDMuM2cBdV0Dg618Vg==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-stepped-value-functions@4.0.7': + resolution: {integrity: sha512-rdrRCKRnWtj5FyRin0u/gLla7CIvZRw/zMGI1fVJP0Sg/m1WGicjPVHRANL++3HQtsiXKAbPrcPr+VkyGck0IA==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + postcss: ^8.4 - /@babel/plugin-transform-class-properties@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-text-decoration-shorthand@4.0.2': + resolution: {integrity: sha512-8XvCRrFNseBSAGxeaVTaNijAu+FzUvjwFXtcrynmazGb/9WUdsPCpBX+mHEHShVRq47Gy4peYAoxYs8ltUnmzA==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-create-class-features-plugin': 7.22.11(@babel/core@7.22.11) - '@babel/helper-plugin-utils': 7.22.5 + postcss: ^8.4 - /@babel/plugin-transform-class-static-block@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-trigonometric-functions@4.0.7': + resolution: {integrity: sha512-qTrZgLju3AV7Djhzuh2Bq/wjFqbcypnk0FhHjxW8DWJQcZLS1HecIus4X2/RLch1ukX7b+YYCdqbEnpIQO5ccg==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.12.0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-create-class-features-plugin': 7.22.11(@babel/core@7.22.11) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.22.11) + postcss: ^8.4 - /@babel/plugin-transform-classes@7.19.0(@babel/core@7.19.1): - resolution: {integrity: sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-unset-value@4.0.0': + resolution: {integrity: sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-compilation-targets': 7.19.1(@babel/core@7.19.1) - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 - '@babel/helper-optimise-call-expression': 7.18.6 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/helper-replace-supers': 7.19.1 - '@babel/helper-split-export-declaration': 7.18.6 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - dev: false + postcss: ^8.4 - /@babel/plugin-transform-classes@7.22.6(@babel/core@7.22.11): - resolution: {integrity: sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==} - engines: {node: '>=6.9.0'} + '@csstools/selector-resolve-nested@3.0.0': + resolution: {integrity: sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-compilation-targets': 7.22.10 - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-function-name': 7.22.5 - '@babel/helper-optimise-call-expression': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-replace-supers': 7.22.9(@babel/core@7.22.11) - '@babel/helper-split-export-declaration': 7.22.6 - globals: 11.12.0 + postcss-selector-parser: ^7.0.0 - /@babel/plugin-transform-computed-properties@7.18.9(@babel/core@7.19.1): - resolution: {integrity: sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==} - engines: {node: '>=6.9.0'} + '@csstools/selector-specificity@5.0.0': + resolution: {integrity: sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + postcss-selector-parser: ^7.0.0 - /@babel/plugin-transform-computed-properties@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==} - engines: {node: '>=6.9.0'} + '@csstools/utilities@2.0.0': + resolution: {integrity: sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@discoveryjs/json-ext@0.5.7': + resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} + engines: {node: '>=10.0.0'} + + '@docsearch/css@3.9.0': + resolution: {integrity: sha512-cQbnVbq0rrBwNAKegIac/t6a8nWoUAn8frnkLFW6YARaRmAQr5/Eoe6Ln2fqkUCZ40KpdrKbpSAmgrkviOxuWA==} + + '@docsearch/react@3.9.0': + resolution: {integrity: sha512-mb5FOZYZIkRQ6s/NWnM98k879vu5pscWqTLubLFBO87igYYT4VzVazh4h5o/zCvTIZgEt3PvsCOMOswOUo9yHQ==} + peerDependencies: + '@types/react': '>= 16.8.0 < 20.0.0' + react: '>= 16.8.0 < 20.0.0' + react-dom: '>= 16.8.0 < 20.0.0' + search-insights: '>= 1 < 3' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + search-insights: + optional: true + + '@docusaurus/babel@3.7.0': + resolution: {integrity: sha512-0H5uoJLm14S/oKV3Keihxvh8RV+vrid+6Gv+2qhuzbqHanawga8tYnsdpjEyt36ucJjqlby2/Md2ObWjA02UXQ==} + engines: {node: '>=18.0'} + + '@docusaurus/bundler@3.7.0': + resolution: {integrity: sha512-CUUT9VlSGukrCU5ctZucykvgCISivct+cby28wJwCC/fkQFgAHRp/GKv2tx38ZmXb7nacrKzFTcp++f9txUYGg==} + engines: {node: '>=18.0'} + peerDependencies: + '@docusaurus/faster': '*' + peerDependenciesMeta: + '@docusaurus/faster': + optional: true + + '@docusaurus/core@3.7.0': + resolution: {integrity: sha512-b0fUmaL+JbzDIQaamzpAFpTviiaU4cX3Qz8cuo14+HGBCwa0evEK0UYCBFY3n4cLzL8Op1BueeroUD2LYAIHbQ==} + engines: {node: '>=18.0'} + hasBin: true + peerDependencies: + '@mdx-js/react': ^3.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/cssnano-preset@3.7.0': + resolution: {integrity: sha512-X9GYgruZBSOozg4w4dzv9uOz8oK/EpPVQXkp0MM6Tsgp/nRIU9hJzJ0Pxg1aRa3xCeEQTOimZHcocQFlLwYajQ==} + engines: {node: '>=18.0'} + + '@docusaurus/faster@3.7.0': + resolution: {integrity: sha512-d+7uyOEs3SBk38i2TL79N6mFaP7J4knc5lPX/W9od+jplXZhnDdl5ZMh2u2Lg7JxGV/l33Bd7h/xwv4mr21zag==} + engines: {node: '>=18.0'} + peerDependencies: + '@docusaurus/types': '*' + + '@docusaurus/logger@3.7.0': + resolution: {integrity: sha512-z7g62X7bYxCYmeNNuO9jmzxLQG95q9QxINCwpboVcNff3SJiHJbGrarxxOVMVmAh1MsrSfxWkVGv4P41ktnFsA==} + engines: {node: '>=18.0'} + + '@docusaurus/lqip-loader@3.7.0': + resolution: {integrity: sha512-bEQ/6o9VSzpqV6OYbyoZUtrKAFJOPKdo8tBmvZCee3M+Hl4V1XAg4TY/KmlAlw6HfMdr42FuqGIy9CsFNxL3CQ==} + engines: {node: '>=18.0'} + + '@docusaurus/mdx-loader@3.7.0': + resolution: {integrity: sha512-OFBG6oMjZzc78/U3WNPSHs2W9ZJ723ewAcvVJaqS0VgyeUfmzUV8f1sv+iUHA0DtwiR5T5FjOxj6nzEE8LY6VA==} + engines: {node: '>=18.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/module-type-aliases@3.7.0': + resolution: {integrity: sha512-g7WdPqDNaqA60CmBrr0cORTrsOit77hbsTj7xE2l71YhBn79sxdm7WMK7wfhcaafkbpIh7jv5ef5TOpf1Xv9Lg==} + peerDependencies: + react: '*' + react-dom: '*' + + '@docusaurus/plugin-client-redirects@3.7.0': + resolution: {integrity: sha512-6B4XAtE5ZVKOyhPgpgMkb7LwCkN+Hgd4vOnlbwR8nCdTQhLjz8MHbGlwwvZ/cay2SPNRX5KssqKAlcHVZP2m8g==} + engines: {node: '>=18.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/plugin-content-blog@3.7.0': + resolution: {integrity: sha512-EFLgEz6tGHYWdPU0rK8tSscZwx+AsyuBW/r+tNig2kbccHYGUJmZtYN38GjAa3Fda4NU+6wqUO5kTXQSRBQD3g==} + engines: {node: '>=18.0'} + peerDependencies: + '@docusaurus/plugin-content-docs': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/plugin-content-docs@3.7.0': + resolution: {integrity: sha512-GXg5V7kC9FZE4FkUZA8oo/NrlRb06UwuICzI6tcbzj0+TVgjq/mpUXXzSgKzMS82YByi4dY2Q808njcBCyy6tQ==} + engines: {node: '>=18.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/plugin-content-pages@3.7.0': + resolution: {integrity: sha512-YJSU3tjIJf032/Aeao8SZjFOrXJbz/FACMveSMjLyMH4itQyZ2XgUIzt4y+1ISvvk5zrW4DABVT2awTCqBkx0Q==} + engines: {node: '>=18.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/plugin-debug@3.7.0': + resolution: {integrity: sha512-Qgg+IjG/z4svtbCNyTocjIwvNTNEwgRjSXXSJkKVG0oWoH0eX/HAPiu+TS1HBwRPQV+tTYPWLrUypYFepfujZA==} + engines: {node: '>=18.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/plugin-google-analytics@3.7.0': + resolution: {integrity: sha512-otIqiRV/jka6Snjf+AqB360XCeSv7lQC+DKYW+EUZf6XbuE8utz5PeUQ8VuOcD8Bk5zvT1MC4JKcd5zPfDuMWA==} + engines: {node: '>=18.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/plugin-google-gtag@3.7.0': + resolution: {integrity: sha512-M3vrMct1tY65ModbyeDaMoA+fNJTSPe5qmchhAbtqhDD/iALri0g9LrEpIOwNaoLmm6lO88sfBUADQrSRSGSWA==} + engines: {node: '>=18.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/plugin-google-tag-manager@3.7.0': + resolution: {integrity: sha512-X8U78nb8eiMiPNg3jb9zDIVuuo/rE1LjGDGu+5m5CX4UBZzjMy+klOY2fNya6x8ACyE/L3K2erO1ErheP55W/w==} + engines: {node: '>=18.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/plugin-ideal-image@3.7.0': + resolution: {integrity: sha512-1IKmXJ6I7WKxfESdCMroechuoQEo1IZzIOhQlga8m7ioHzu+sb+Egnyrau2buCYh0QJ8gZoXtscSt5TBFlzMOQ==} + engines: {node: '>=18.0'} + peerDependencies: + jimp: '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + jimp: + optional: true + + '@docusaurus/plugin-sitemap@3.7.0': + resolution: {integrity: sha512-bTRT9YLZ/8I/wYWKMQke18+PF9MV8Qub34Sku6aw/vlZ/U+kuEuRpQ8bTcNOjaTSfYsWkK4tTwDMHK2p5S86cA==} + engines: {node: '>=18.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/plugin-svgr@3.7.0': + resolution: {integrity: sha512-HByXIZTbc4GV5VAUkZ2DXtXv1Qdlnpk3IpuImwSnEzCDBkUMYcec5282hPjn6skZqB25M1TYCmWS91UbhBGxQg==} + engines: {node: '>=18.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/preset-classic@3.7.0': + resolution: {integrity: sha512-nPHj8AxDLAaQXs+O6+BwILFuhiWbjfQWrdw2tifOClQoNfuXDjfjogee6zfx6NGHWqshR23LrcN115DmkHC91Q==} + engines: {node: '>=18.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/react-loadable@6.0.0': + resolution: {integrity: sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==} + peerDependencies: + react: '*' + + '@docusaurus/responsive-loader@1.7.1': + resolution: {integrity: sha512-jAebZ43f8GVpZSrijLGHVVp7Y0OMIPRaL+HhiIWQ+f/b72lTsKLkSkOVHEzvd2psNJ9lsoiM3gt6akpak6508w==} + engines: {node: '>=12'} + peerDependencies: + jimp: '*' + sharp: '*' + peerDependenciesMeta: + jimp: + optional: true + sharp: + optional: true + + '@docusaurus/theme-classic@3.7.0': + resolution: {integrity: sha512-MnLxG39WcvLCl4eUzHr0gNcpHQfWoGqzADCly54aqCofQX6UozOS9Th4RK3ARbM9m7zIRv3qbhggI53dQtx/hQ==} + engines: {node: '>=18.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/theme-common@3.7.0': + resolution: {integrity: sha512-8eJ5X0y+gWDsURZnBfH0WabdNm8XMCXHv8ENy/3Z/oQKwaB/EHt5lP9VsTDTf36lKEp0V6DjzjFyFIB+CetL0A==} + engines: {node: '>=18.0'} + peerDependencies: + '@docusaurus/plugin-content-docs': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/theme-search-algolia@3.7.0': + resolution: {integrity: sha512-Al/j5OdzwRU1m3falm+sYy9AaB93S1XF1Lgk9Yc6amp80dNxJVplQdQTR4cYdzkGtuQqbzUA8+kaoYYO0RbK6g==} + engines: {node: '>=18.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/theme-translations@3.7.0': + resolution: {integrity: sha512-Ewq3bEraWDmienM6eaNK7fx+/lHMtGDHQyd1O+4+3EsDxxUmrzPkV7Ct3nBWTuE0MsoZr3yNwQVKjllzCMuU3g==} + engines: {node: '>=18.0'} + + '@docusaurus/tsconfig@3.7.0': + resolution: {integrity: sha512-vRsyj3yUZCjscgfgcFYjIsTcAru/4h4YH2/XAE8Rs7wWdnng98PgWKvP5ovVc4rmRpRg2WChVW0uOy2xHDvDBQ==} + + '@docusaurus/types@3.7.0': + resolution: {integrity: sha512-kOmZg5RRqJfH31m+6ZpnwVbkqMJrPOG5t0IOl4i/+3ruXyNfWzZ0lVtVrD0u4ONc/0NOsS9sWYaxxWNkH1LdLQ==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/utils-common@3.7.0': + resolution: {integrity: sha512-IZeyIfCfXy0Mevj6bWNg7DG7B8G+S6o6JVpddikZtWyxJguiQ7JYr0SIZ0qWd8pGNuMyVwriWmbWqMnK7Y5PwA==} + engines: {node: '>=18.0'} + + '@docusaurus/utils-validation@3.7.0': + resolution: {integrity: sha512-w8eiKk8mRdN+bNfeZqC4nyFoxNyI1/VExMKAzD9tqpJfLLbsa46Wfn5wcKH761g9WkKh36RtFV49iL9lh1DYBA==} + engines: {node: '>=18.0'} + + '@docusaurus/utils@3.7.0': + resolution: {integrity: sha512-e7zcB6TPnVzyUaHMJyLSArKa2AG3h9+4CfvKXKKWNx6hRs+p0a+u7HHTJBgo6KW2m+vqDnuIHK4X+bhmoghAFA==} + engines: {node: '>=18.0'} + + '@dual-bundle/import-meta-resolve@4.1.0': + resolution: {integrity: sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==} + + '@emnapi/runtime@1.3.1': + resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + + '@eslint-community/eslint-utils@4.5.1': + resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint-kit/eslint-config-base@4.1.0': + resolution: {integrity: sha512-vDIeH8WefnMGa9T3ExFZNAXyWR2TvP6ul8NizODPERwp9upJ34Q7QUfemtJjjwiSzKpaHzSz4/qlX41hhoBxSQ==} + peerDependencies: + eslint: ^5.16.0 || ^6.8.0 || ^7.2.0 + + '@eslint-kit/eslint-config-patch@1.0.0': + resolution: {integrity: sha512-b3pODxIVCfXz/q5n/C6NeB5rOjNwS+IKtMAIbi9qk4Pz9wrYgTgjaiOCxj5aEfT0gE49t7/bjvZv2tWlSe171Q==} + peerDependencies: + eslint: ^5.16.0 || ^6.8.0 || ^7.2.0 + + '@eslint-kit/eslint-config-react@3.0.0': + resolution: {integrity: sha512-DBV0TXA+OaJSqIBX9LNF/u4fkvqiWUo6NmRKt5vJYfwczzHRqONF2+UUAahAouWuF6XNFmf/cHkXLIxZDBIfRQ==} + peerDependencies: + eslint: ^5.16.0 || ^6.8.0 || ^7.2.0 + + '@eslint/eslintrc@0.4.3': + resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==} + engines: {node: ^10.12.0 || >=12.0.0} + + '@fontsource-variable/overpass@5.2.5': + resolution: {integrity: sha512-kNYPClOJbXrwCPfsKeQTiAPk2cgHbQrU/6HNh5Kj+YjK0M8c5QYBVk6fWCG9LTt/OWQV2A2GwwYrWxkcdpL3dQ==} + + '@hapi/hoek@9.3.0': + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + + '@hapi/topo@5.1.0': + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + + '@humanwhocodes/config-array@0.5.0': + resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/object-schema@1.2.1': + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + deprecated: Use @eslint/object-schema instead + + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.6': + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@keyv/serialize@1.0.3': + resolution: {integrity: sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==} + + '@leichtgewicht/ip-codec@2.0.5': + resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} + + '@mdx-js/mdx@3.1.0': + resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==} + + '@mdx-js/react@3.1.0': + resolution: {integrity: sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==} + peerDependencies: + '@types/react': '>=16' + react: '>=16' + + '@module-federation/error-codes@0.8.4': + resolution: {integrity: sha512-55LYmrDdKb4jt+qr8qE8U3al62ZANp3FhfVaNPOaAmdTh0jHdD8M3yf5HKFlr5xVkVO4eV/F/J2NCfpbh+pEXQ==} + + '@module-federation/runtime-tools@0.8.4': + resolution: {integrity: sha512-fjVOsItJ1u5YY6E9FnS56UDwZgqEQUrWFnouRiPtK123LUuqUI9FH4redZoKWlE1PB0ir1Z3tnqy8eFYzPO38Q==} + + '@module-federation/runtime@0.8.4': + resolution: {integrity: sha512-yZeZ7z2Rx4gv/0E97oLTF3V6N25vglmwXGgoeju/W2YjsFvWzVtCDI7zRRb0mJhU6+jmSM8jP1DeQGbea/AiZQ==} + + '@module-federation/sdk@0.8.4': + resolution: {integrity: sha512-waABomIjg/5m1rPDBWYG4KUhS5r7OUUY7S+avpaVIY/tkPWB3ibRDKy2dNLLAMaLKq0u+B1qIdEp4NIWkqhqpg==} + + '@module-federation/webpack-bundler-runtime@0.8.4': + resolution: {integrity: sha512-HggROJhvHPUX7uqBD/XlajGygMNM1DG0+4OAkk8MBQe4a18QzrRNzZt6XQbRTSG4OaEoyRWhQHvYD3Yps405tQ==} + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + + '@pnpm/config.env-replace@1.1.0': + resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} + engines: {node: '>=12.22.0'} + + '@pnpm/network.ca-file@1.0.2': + resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} + engines: {node: '>=12.22.0'} + + '@pnpm/npm-conf@2.3.1': + resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} + engines: {node: '>=12'} + + '@polka/url@1.0.0-next.28': + resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} + + '@rspack/binding-darwin-arm64@1.2.0-alpha.0': + resolution: {integrity: sha512-EPprIe6BrkJ9XuWL5HBXJFaH4vvt5C2kBTvyu+t5E3wacyH9A0gIDaMOEmH30Kt3zl4B07OCBC1nCiJ1sTtimw==} + cpu: [arm64] + os: [darwin] + + '@rspack/binding-darwin-x64@1.2.0-alpha.0': + resolution: {integrity: sha512-ACwdgWg0V9j0o3gs1wvhqRJ4xui82L+Fii9Fa74az7P974iWO0ZHw4QIUaO5r434+v9OWMqpyBRN1M7cBrx3GA==} + cpu: [x64] + os: [darwin] + + '@rspack/binding-linux-arm64-gnu@1.2.0-alpha.0': + resolution: {integrity: sha512-Ex9SviDikz9E36R4I5si/626FsYOJ35l1Lb+DCRUijjjsvoq4k8Shi8csyBfubR+JZ1M0uOXjJftu1Gm5z8Q0Q==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-arm64-musl@1.2.0-alpha.0': + resolution: {integrity: sha512-U320xZmTcTwQ0BR8yIzE1L4olMCqzYkT3VFjXPR6iok/Mj0xjfk/SiKhLoZml473qQrHSGaFJ321cp02zgTFJg==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-x64-gnu@1.2.0-alpha.0': + resolution: {integrity: sha512-GNur7VXJ29NtJhY8PYgv3Fv1Zxbx0XZhDUj/+7Wp40CAXRFsLgXScZIRh2U30TECYaihboZ7BD+xugv8MQPDoA==} + cpu: [x64] + os: [linux] + + '@rspack/binding-linux-x64-musl@1.2.0-alpha.0': + resolution: {integrity: sha512-0IdswzpG9+sgxvGu7KTwSeqfV0hvciaHMoZvGklfZa2txpcUqAg4ASp7uxrNaUo+G2a1fTUMOtP9351Cnl8DBg==} + cpu: [x64] + os: [linux] + + '@rspack/binding-win32-arm64-msvc@1.2.0-alpha.0': + resolution: {integrity: sha512-FcFgoWGjSrCfJwDZY5bDA2aO02l5BP7qdyW6ehjwBiMxNZyeSbGvKz3jXl5TtTHR1IgdLzi9kEJkTPYLLMiE1A==} + cpu: [arm64] + os: [win32] + + '@rspack/binding-win32-ia32-msvc@1.2.0-alpha.0': + resolution: {integrity: sha512-cZYFJw6DKCaPPz9VDJPndZ9KSp+/eedgt11Mv8OTpq+MJTUjB2HjtcjqJh8xxVcp3IuwvSMndTkC69WWt/4feA==} + cpu: [ia32] + os: [win32] + + '@rspack/binding-win32-x64-msvc@1.2.0-alpha.0': + resolution: {integrity: sha512-gfOqb/rq5716NV+Vbk5MteBhV4VhJeSoh2+dRQjdy4EN1wPZ+Uebs9ORVrT9uRjY3JrPn/5PkAHJXtgaOA9Uyg==} + cpu: [x64] + os: [win32] + + '@rspack/binding@1.2.0-alpha.0': + resolution: {integrity: sha512-rtmDScjtGUxv1zA1m3jXecuX2LsgNp4aWaAjOowHasoO1YqfHK0fMyprCiPowTjoHtpZ7Xt/tnMhii0GlGIITQ==} + + '@rspack/core@1.2.0-alpha.0': + resolution: {integrity: sha512-YiD0vFDj+PfHs3ZqJwPNhTYyVTb4xR6FpOI5WJ4jJHV4lgdErS+RChTCPhf1xeqxfuTSSnFA7UeqosLhBuNSqQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@swc/helpers': '>=0.5.1' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@rspack/lite-tapable@1.0.1': + resolution: {integrity: sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==} + engines: {node: '>=16.0.0'} + + '@rushstack/eslint-patch@1.0.6': + resolution: {integrity: sha512-Myxw//kzromB9yWgS8qYGuGVf91oBUUJpNvy5eM50sqvmKLbKjwLxohJnkWGTeeI9v9IBMtPLxz5Gc60FIfvCA==} + + '@sideway/address@4.1.5': + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + + '@sideway/formula@3.0.1': + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + + '@sideway/pinpoint@2.0.0': + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@sindresorhus/is@5.6.0': + resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} + engines: {node: '>=14.16'} + + '@slorber/react-helmet-async@1.3.0': + resolution: {integrity: sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A==} + peerDependencies: + react: ^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@slorber/react-ideal-image@0.0.14': + resolution: {integrity: sha512-ULJ1VtNg+B5puJp4ZQzEnDqYyDT9erbABoQygmAovg35ltOymLMH8jXPuxJQBVskcmaG29bTZ+++hE/PAXRgxA==} + engines: {node: '>= 8.9.0', npm: '> 3'} + peerDependencies: + react: '>=0.14.x' + react-waypoint: '>=9.0.2' + + '@slorber/remark-comment@1.0.0': + resolution: {integrity: sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==} + + '@stencil/core@2.22.3': + resolution: {integrity: sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==} + engines: {node: '>=12.10.0', npm: '>=6.0.0'} + hasBin: true + + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': + resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} + engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/template': 7.22.5 - /@babel/plugin-transform-destructuring@7.18.13(@babel/core@7.19.1): - resolution: {integrity: sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow==} - engines: {node: '>=6.9.0'} + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': + resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} + engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false - /@babel/plugin-transform-destructuring@7.22.10(@babel/core@7.22.11): - resolution: {integrity: sha512-dPJrL0VOyxqLM9sritNbMSGx/teueHF/htMKrPT7DNxccXxRDPYqlgPFFdr8u+F+qUZOkZoXue/6rL5O5GduEw==} - engines: {node: '>=6.9.0'} + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0': + resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} + engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - /@babel/plugin-transform-dotall-regex@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==} - engines: {node: '>=6.9.0'} + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': + resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} + engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-create-regexp-features-plugin': 7.19.0(@babel/core@7.19.1) - '@babel/helper-plugin-utils': 7.19.0 - dev: false - /@babel/plugin-transform-dotall-regex@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==} - engines: {node: '>=6.9.0'} + '@svgr/babel-plugin-svg-dynamic-title@8.0.0': + resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0': + resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0': + resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} + engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-svg-component@8.0.0': + resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} + engines: {node: '>=12'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-preset@8.1.0': + resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/core@8.1.0': + resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} + engines: {node: '>=14'} + + '@svgr/hast-util-to-babel-ast@8.0.0': + resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} + engines: {node: '>=14'} + + '@svgr/plugin-jsx@8.1.0': + resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + + '@svgr/plugin-svgo@8.1.0': + resolution: {integrity: sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + + '@svgr/webpack@8.1.0': + resolution: {integrity: sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==} + engines: {node: '>=14'} + + '@swc/core-darwin-arm64@1.11.9': + resolution: {integrity: sha512-moqbPCWG6SHiDMENTDYsEQJ0bFustbLtrdbDbdjnijSyhCyIcm9zKowmovE6MF8JBdOwmLxbuN1Yarq6CrPNlw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.11.9': + resolution: {integrity: sha512-/lgMo5l9q6y3jjLM3v30y6SBvuuyLsM/K94hv3hPvDf91N+YlZLw4D7KY0Qknfhj6WytoAcjOIDU6xwBRPyUWg==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.11.9': + resolution: {integrity: sha512-7bL6z/63If11IpBElQRozIGRadiy6rt3DoUyfGuFIFQKxtnZxzHuLxm1/wrCAGN9iAZxrpHxHP0VbPQvr6Mcjg==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.11.9': + resolution: {integrity: sha512-9ArpxjrNbyFTr7gG+toiGbbK2mfS+X97GIruBKPsD8CJH/yJlMknBsX3lfy9h/L119zYVnFBmZDnwsv5yW8/cw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.11.9': + resolution: {integrity: sha512-UOnunJWu7T7oNkBr4DLMwXXbldjiwi+JxmqBKrD2+BNiHGu6P5VpqDHiTGuWuLrda0TcTmeNE6gzlIVOVBo/vw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.11.9': + resolution: {integrity: sha512-HAqmCkNoNhRusBqSokyylXKsLJ/dr3dnMgBERdUrCIh47L8CKR2qEFUP6FI05sHVB85403ctWnfzBYblcarpqg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.11.9': + resolution: {integrity: sha512-THwUT2g2qSWUxhi3NGRCEdmh/q7WKl3d5jcN9mz/4jum76Tb46LB9p3oOVPBIcfnFQ9OaddExjCwLoUl0ju2pA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.11.9': + resolution: {integrity: sha512-r4SGD9lR0MM9HSIsQ72BEL3Za3XsuVj+govuXQTlK0mty5gih4L+Qgfnb9PmhjFakK3F63gZyyEr2y8Fj0mN6Q==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.11.9': + resolution: {integrity: sha512-jrEh6MDSnhwfpjRlSWd2Bk8pS5EjreQD1YbkNcnXviQf3+H0wSPmeVSktZyoIdkxAuc2suFx8mj7Yja2UXAgUg==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.11.9': + resolution: {integrity: sha512-oAwuhzr+1Bmb4As2wa3k57/WPJeyVEYRQelwEMYjPgi/h6TH+Y69jQAgKOd+ec1Yl8L5nkWTZMVA/dKDac1bAQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.11.9': + resolution: {integrity: sha512-4UQ66FwTkFDr+UzYzRNKQyHMScOrc4zJbTJHyK6dP1yVMrxi5sl0FTzNKiqoYvRZ7j8TAYgtYvvuPSW/XXvp5g==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '*' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/html-darwin-arm64@1.11.9': + resolution: {integrity: sha512-Aci42QDf2UPf3q4GrXwCDK7VXV5OMVnuz81LBHHLM3+tQwss3SwVncBupK2xNAC6M9WkM5p+J135gJXZTfpAig==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/html-darwin-x64@1.11.9': + resolution: {integrity: sha512-fbxKlrVV04o+jQ1Rfmapb7I2SYZi7k5UBdDWAACsapxCvn9uBGoI6JfoBWoZK2B/WjO2N+BK6pg75hy0Kyrwfw==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/html-linux-arm-gnueabihf@1.11.9': + resolution: {integrity: sha512-HuiMt2yzxew4BwpMfeYNNDAi+zlC5ekNoZycnJEBrYPxPMbaeichMA7bcw6CpVYppwxDDTxgNKG7YYIXxw0WWQ==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/html-linux-arm64-gnu@1.11.9': + resolution: {integrity: sha512-U18LsfLxV3JIDcoD7YIFRjdnJBtJIw9wNlgohvkfytgHgaEX1kUZzuTAx1tFzt6ouMk3dwgrD250UsJH6MkJog==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/html-linux-arm64-musl@1.11.9': + resolution: {integrity: sha512-M9qv1X3Q5cwHbm4HgTtaP6l2bjKtfUikKy9zXGm2XngbSZmJykXlV/W3qc1wa+yus7vqRyk9rO5FzWLeKHdc5A==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/html-linux-x64-gnu@1.11.9': + resolution: {integrity: sha512-bZl6ZIO7B1sbhlqEG82nNYx5pKkqS5CSMz0f6FDFtvK8EsiOjsRO7Ty1kqeHrHgjmNK7/XHOC9+5yJ2B6UZQ8A==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/html-linux-x64-musl@1.11.9': + resolution: {integrity: sha512-yDFDl0zfZmOgTpTkF0nzWoXZcBhi0AfmVerIk5JgcMX0iBZDeONoYg6eNZ1/CBjTJ01rRm2qjjDF/4bOtveBSw==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/html-win32-arm64-msvc@1.11.9': + resolution: {integrity: sha512-Rp2XnRA8UKBKzMjwQmlDgQnpgjvSrOqxTCVmQtXDGMnZFU+K9QuWOpszwgvRoTKE/9LiKOMm58QkYwShNd47qQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/html-win32-ia32-msvc@1.11.9': + resolution: {integrity: sha512-TtVsES+p28ADpmC8ploffL6W424IlylA1WybYbQqc+lVgCe4SbRNxB0VAEeXJGv6xVkipwynq8IIo4qt0B/ZxA==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/html-win32-x64-msvc@1.11.9': + resolution: {integrity: sha512-Ma7csYABJdQgwxzHaNJ3RHZSpNd07k+hBiaMosqNV2dER2rmO/C+7UKzEUciQgh4tzh9KnpJbQvkiztvWBP+gw==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/html@1.11.9': + resolution: {integrity: sha512-dZNo8+9dLt5IaFDpvnjNdjuczM3ac4jzQQ1BJLbZNpwUp/NE3MmGzeMI2J/s+oo56+gTzkLXuYyIrykJbk/OpQ==} + engines: {node: '>=14'} + + '@swc/types@0.1.19': + resolution: {integrity: sha512-WkAZaAfj44kh/UFdAQcrMP1I0nwRqpt27u+08LMBYMqmQfwwMofYoMh/48NGkMMRfC4ynpfwRbJuu8ErfNloeA==} + + '@szmarczak/http-timer@5.0.1': + resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} + engines: {node: '>=14.16'} + + '@trysound/sax@0.2.0': + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + + '@types/acorn@4.0.6': + resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} + + '@types/body-parser@1.19.5': + resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + + '@types/bonjour@3.5.13': + resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==} + + '@types/connect-history-api-fallback@1.5.4': + resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/express-serve-static-core@4.19.6': + resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} + + '@types/express-serve-static-core@5.0.6': + resolution: {integrity: sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==} + + '@types/express@4.17.21': + resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + + '@types/gtag.js@0.0.12': + resolution: {integrity: sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/history@4.7.11': + resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==} + + '@types/html-minifier-terser@6.1.0': + resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} + + '@types/http-cache-semantics@4.0.4': + resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + + '@types/http-errors@2.0.4': + resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + + '@types/http-proxy@1.17.16': + resolution: {integrity: sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.16': + resolution: {integrity: sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node-forge@1.3.11': + resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} + + '@types/node@17.0.45': + resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} + + '@types/node@22.13.10': + resolution: {integrity: sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==} + + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/prismjs@1.26.5': + resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} + + '@types/prop-types@15.7.14': + resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} + + '@types/qs@6.9.18': + resolution: {integrity: sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/react-dom@18.3.5': + resolution: {integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react-router-config@5.0.11': + resolution: {integrity: sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==} + + '@types/react-router-dom@5.3.3': + resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} + + '@types/react-router@5.1.20': + resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} + + '@types/react@18.3.18': + resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==} + + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + + '@types/sax@1.2.7': + resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} + + '@types/semver@7.5.8': + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + + '@types/send@0.17.4': + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} + + '@types/serve-index@1.9.4': + resolution: {integrity: sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==} + + '@types/serve-static@1.15.7': + resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} + + '@types/sockjs@0.3.36': + resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/ws@8.18.0': + resolution: {integrity: sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + + '@typescript-eslint/eslint-plugin@6.21.0': + resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@6.21.0': + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@6.21.0': + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/type-utils@6.21.0': + resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@6.21.0': + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@6.21.0': + resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@7.4.1: + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} + hasBin: true + + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + + address@1.2.2: + resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} + engines: {node: '>= 10.0.0'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + algoliasearch-helper@3.24.2: + resolution: {integrity: sha512-vBw/INZDfyh/THbVeDy8On8lZqd2qiUAHde5N4N1ygL4SoeLqLGJ4GHneHrDAYsjikRwTTtodEP0fiXl5MxHFQ==} + peerDependencies: + algoliasearch: '>= 3.1 < 6' + + algoliasearch@5.21.0: + resolution: {integrity: sha512-hexLq2lSO1K5SW9j21Ubc+q9Ptx7dyRTY7se19U8lhIlVMLCNXWCyQ6C22p9ez8ccX0v7QVmwkl2l1CnuGoO2Q==} + engines: {node: '>= 14.0.0'} + + all-contributors-cli@6.26.1: + resolution: {integrity: sha512-Ymgo3FJACRBEd1eE653FD1J/+uD0kqpUNYfr9zNC1Qby0LgbhDBzB3EF6uvkAbYpycStkk41J+0oo37Lc02yEw==} + engines: {node: '>=4'} + hasBin: true + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-html-community@0.0.8: + resolution: {integrity: sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==} + engines: {'0': node >= 0.8.0} + hasBin: true + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + + autoprefixer@10.4.21: + resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + b4a@1.6.7: + resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} + + babel-loader@9.2.1: + resolution: {integrity: sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==} + engines: {node: '>= 14.15.0'} + peerDependencies: + '@babel/core': ^7.12.0 + webpack: '>=5' + + babel-plugin-dynamic-import-node@2.3.3: + resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==} + + babel-plugin-polyfill-corejs2@0.4.12: + resolution: {integrity: sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.11.1: + resolution: {integrity: sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.3: + resolution: {integrity: sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@2.0.0: + resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} + + bare-events@2.5.4: + resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} + + bare-fs@4.0.1: + resolution: {integrity: sha512-ilQs4fm/l9eMfWY2dY0WCIUplSUp7U0CT1vrqMg1MUdeZl4fypu5UP0XcDBK5WBQPJAKP1b7XEodISmekH/CEg==} + engines: {bare: '>=1.7.0'} + + bare-os@3.6.0: + resolution: {integrity: sha512-BUrFS5TqSBdA0LwHop4OjPJwisqxGy6JsWVqV6qaFoe965qqtaKfDzHY5T2YA1gUL0ZeeQeA+4BBc1FJTcHiPw==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + + bare-stream@2.6.5: + resolution: {integrity: sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==} + peerDependencies: + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + + base64-arraybuffer@1.0.2: + resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} + engines: {node: '>= 0.6.0'} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + batch@0.6.1: + resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} + + big.js@5.2.2: + resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + bonjour-service@1.3.0: + resolution: {integrity: sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + boxen@6.2.1: + resolution: {integrity: sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + boxen@7.1.1: + resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==} + engines: {node: '>=14.16'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + + bytes@3.0.0: + resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} + engines: {node: '>= 0.8'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cacheable-lookup@7.0.0: + resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} + engines: {node: '>=14.16'} + + cacheable-request@10.2.14: + resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==} + engines: {node: '>=14.16'} + + cacheable@1.8.9: + resolution: {integrity: sha512-FicwAUyWnrtnd4QqYAoRlNs44/a1jTL7XDKqm5gJ90wz1DQPlC7U2Rd1Tydpv+E7WAr4sQHuw8Q8M3nZMAyecQ==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camel-case@4.1.2: + resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + camelcase@7.0.1: + resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} + engines: {node: '>=14.16'} + + caniuse-api@3.0.0: + resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} + + caniuse-lite@1.0.30001704: + resolution: {integrity: sha512-+L2IgBbV6gXB4ETf0keSvLr7JUrRVbIaB/lrQ1+z8mRcQiisG5k+lG6O4n6Y5q6f5EuNfaYXKgymucphlEXQew==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.4.1: + resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + + charenc@0.0.2: + resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + + cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + + cheerio@1.0.0-rc.12: + resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} + engines: {node: '>= 6'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + + clean-css@5.3.3: + resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} + engines: {node: '>= 10.0'} + + clean-regexp@1.0.0: + resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} + engines: {node: '>=4'} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + + cli-width@3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + + clone-deep@4.0.1: + resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} + engines: {node: '>=6'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + combine-promises@1.2.0: + resolution: {integrity: sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==} + engines: {node: '>=10'} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@5.1.0: + resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} + engines: {node: '>= 6'} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + common-path-prefix@3.0.0: + resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} + + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.8.0: + resolution: {integrity: sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==} + engines: {node: '>= 0.8.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + + configstore@6.0.0: + resolution: {integrity: sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==} + engines: {node: '>=12'} + + connect-history-api-fallback@2.0.0: + resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} + engines: {node: '>=0.8'} + + consola@3.4.0: + resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} + engines: {node: ^14.18.0 || >=16.10.0} + + consolidated-events@2.0.2: + resolution: {integrity: sha512-2/uRVMdRypf5z/TW/ncD/66l75P5hH2vM/GR8Jf8HLc2xnfJtmina6F6du8+v4Z2vTrMo7jC+W1tmEEuuELgkQ==} + + contains-path@1.0.0: + resolution: {integrity: sha512-nsTUCGt1AyU3dHIfoE/vgbaU6GMV/FVZgVGpg4x4DnWggpelA9qGqaR9z7SbTM0O17F09F1Oo1bsEmCu+bl0fg==} + engines: {node: '>=0.10.0'} + + content-disposition@0.5.2: + resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} + engines: {node: '>= 0.6'} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + + copy-text-to-clipboard@3.2.0: + resolution: {integrity: sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==} + engines: {node: '>=12'} + + copy-webpack-plugin@11.0.0: + resolution: {integrity: sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==} + engines: {node: '>= 14.15.0'} + peerDependencies: + webpack: ^5.1.0 + + core-js-compat@3.41.0: + resolution: {integrity: sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==} + + core-js-pure@3.41.0: + resolution: {integrity: sha512-71Gzp96T9YPk63aUvE5Q5qP+DryB4ZloUZPSOebGM88VNw8VNfvdA7z6kGA8iGOTEzAomsRidp4jXSmUIJsL+Q==} + + core-js@3.41.0: + resolution: {integrity: sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cosmiconfig@6.0.0: + resolution: {integrity: sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==} + engines: {node: '>=8'} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cosmiconfig@9.0.0: + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crypt@0.0.2: + resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} + + crypto-random-string@4.0.0: + resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} + engines: {node: '>=12'} + + css-blank-pseudo@7.0.1: + resolution: {integrity: sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + css-declaration-sorter@7.2.0: + resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.0.9 + + css-functions-list@3.2.3: + resolution: {integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==} + engines: {node: '>=12 || >=16'} + + css-has-pseudo@7.0.2: + resolution: {integrity: sha512-nzol/h+E0bId46Kn2dQH5VElaknX2Sr0hFuB/1EomdC7j+OISt2ZzK7EHX9DZDY53WbIVAR7FYKSO2XnSf07MQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + css-line-break@2.1.0: + resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==} + + css-loader@6.11.0: + resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==} + engines: {node: '>= 12.13.0'} + peerDependencies: + '@rspack/core': 0.x || 1.x + webpack: ^5.0.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + webpack: + optional: true + + css-minimizer-webpack-plugin@5.0.1: + resolution: {integrity: sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==} + engines: {node: '>= 14.15.0'} + peerDependencies: + '@parcel/css': '*' + '@swc/css': '*' + clean-css: '*' + csso: '*' + esbuild: '*' + lightningcss: '*' + webpack: ^5.0.0 + peerDependenciesMeta: + '@parcel/css': + optional: true + '@swc/css': + optional: true + clean-css: + optional: true + csso: + optional: true + esbuild: + optional: true + lightningcss: + optional: true + + css-prefers-color-scheme@10.0.0: + resolution: {integrity: sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + css-select@4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + + css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + + cssdb@8.2.4: + resolution: {integrity: sha512-3KSCVkjZJe/QxicVXnbyYSY26WsFc1YoMY7jep1ZKWMEVc7jEm6V2Xq2r+MX8WKQIuB7ofGbnr5iVI+aZpoSzg==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + cssnano-preset-advanced@6.1.2: + resolution: {integrity: sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + cssnano-preset-default@6.1.2: + resolution: {integrity: sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + cssnano-utils@4.0.2: + resolution: {integrity: sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + cssnano@6.1.2: + resolution: {integrity: sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debounce@1.2.1: + resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decode-named-character-reference@1.1.0: + resolution: {integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-gateway@6.0.3: + resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==} + engines: {node: '>= 10'} + + defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + del@6.1.1: + resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} + engines: {node: '>=10'} + + depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + + detect-port-alt@1.1.6: + resolution: {integrity: sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==} + engines: {node: '>= 4.2.1'} + hasBin: true + + detect-port@1.6.1: + resolution: {integrity: sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==} + engines: {node: '>= 4.0.0'} + hasBin: true + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dns-packet@5.6.1: + resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==} + engines: {node: '>=6'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + docusaurus-plugin-sass@0.2.6: + resolution: {integrity: sha512-2hKQQDkrufMong9upKoG/kSHJhuwd+FA3iAe/qzS/BmWpbIpe7XKmq5wlz4J5CJaOPu4x+iDJbgAxZqcoQf0kg==} + peerDependencies: + '@docusaurus/core': ^2.0.0-beta || ^3.0.0-alpha + sass: ^1.30.0 + + dom-converter@0.2.0: + resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==} + + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + + dot-prop@6.0.1: + resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} + engines: {node: '>=10'} + + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.119: + resolution: {integrity: sha512-Ku4NMzUjz3e3Vweh7PhApPrZSS4fyiCIbcIrG9eKrriYVLmbMepETR/v6SU7xPm98QTqMSYiCwfO89QNjXLkbQ==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + emojilib@2.4.0: + resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} + + emojis-list@3.0.0: + resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} + engines: {node: '>= 4'} + + emoticon@4.1.0: + resolution: {integrity: sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + + enhanced-resolve@5.18.1: + resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} + engines: {node: '>=10.13.0'} + + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-abstract@1.23.9: + resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-goat@4.0.0: + resolution: {integrity: sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==} + engines: {node: '>=12'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-config-prettier@9.1.0: + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-import-resolver-alias@1.1.2: + resolution: {integrity: sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==} + engines: {node: '>= 4'} + peerDependencies: + eslint-plugin-import: '>=1.4.0' + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-module-utils@2.12.0: + resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.23.2: + resolution: {integrity: sha512-LmNoRptHBxOP+nb0PIKz1y6OSzCJlB+0g0IGS3XV4KaKk2q4szqQ6s6F1utVf5ZRkxk/QOTjdxe7v4VjS99Bsg==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-react-hooks@4.2.0: + resolution: {integrity: sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + eslint-plugin-react@7.23.2: + resolution: {integrity: sha512-AfjgFQB+nYszudkxRkTFu0UR1zEQig0ArVMPloKhxwlwkzaw/fBiH0QWcBBhZONlXqQC51+nfqFrkn4EzHcGBw==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 + + eslint-plugin-sonarjs@0.7.0: + resolution: {integrity: sha512-vi6zGkd5Eznc32AQlleWUOMrMeDiUih9wR7nPPfrDCyVRmwYNHIBRPZGv1EgXwELwaPghCSvoAoHoR7uSbBF/Q==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 + + eslint-plugin-unicorn@32.0.1: + resolution: {integrity: sha512-LaZ9utnXtOJjnoDkpm+nQsONUUmyRR0WD6PGROSdQRRW3LRmgK/ZP8wxjW+Ai+2uolKTtuJzLx2mvbIeIoLqpg==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=7.23.0' + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-template-visitor@2.3.2: + resolution: {integrity: sha512-3ydhqFpuV7x1M9EK52BPNj6V0Kwu0KKkcIAfpUhwHbR8ocRln/oUHgfxQupY8O1h4Qv/POHDumb/BwwNfxbtnA==} + peerDependencies: + eslint: '>=7.0.0' + + eslint-utils@2.1.0: + resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} + engines: {node: '>=6'} + + eslint-visitor-keys@1.3.0: + resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} + engines: {node: '>=4'} + + eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@7.32.0: + resolution: {integrity: sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==} + engines: {node: ^10.12.0 || >=12.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@7.3.1: + resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==} + engines: {node: ^10.12.0 || >=12.0.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-value-to-estree@3.3.2: + resolution: {integrity: sha512-hYH1aSvQI63Cvq3T3loaem6LW4u72F187zW4FHpTrReJSm6W66vYTFNO1vH/chmcOulp1HlAj1pxn8Ag0oXI5Q==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eta@2.2.0: + resolution: {integrity: sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==} + engines: {node: '>=6.0.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eval@0.1.8: + resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} + engines: {node: '>= 0.8'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} + engines: {node: '>= 0.10.0'} + + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-uri@3.0.6: + resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} + + fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fault@2.0.1: + resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} + + faye-websocket@0.11.4: + resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} + engines: {node: '>=0.8.0'} + + feed@4.2.2: + resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==} + engines: {node: '>=0.4.0'} + + figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + + file-entry-cache@10.0.7: + resolution: {integrity: sha512-txsf5fu3anp2ff3+gOJJzRImtrtm/oa9tYLN0iTuINZ++EyVR/nRrg2fKYwvG/pXDofcrvvb0scEbX3NyW/COw==} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + file-loader@6.2.0: + resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==} + engines: {node: '>= 10.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + + filesize@8.0.7: + resolution: {integrity: sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==} + engines: {node: '>= 0.4.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + engines: {node: '>= 0.8'} + + find-cache-dir@4.0.0: + resolution: {integrity: sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==} + engines: {node: '>=14.16'} + + find-up@2.1.0: + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} + engines: {node: '>=4'} + + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + find-up@6.3.0: + resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flat-cache@6.1.7: + resolution: {integrity: sha512-qwZ4xf1v1m7Rc9XiORly31YaChvKt6oNVHuqqZcoED/7O+ToyNVGobKsIAopY9ODcWpEDKEBAbrSOCBHtNQvew==} + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + fork-ts-checker-webpack-plugin@6.5.3: + resolution: {integrity: sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==} + engines: {node: '>=10', yarn: '>=1.0.0'} + peerDependencies: + eslint: '>= 6' + typescript: '>= 2.7' + vue-template-compiler: '*' + webpack: '>= 4' + peerDependenciesMeta: + eslint: + optional: true + vue-template-compiler: + optional: true + + form-data-encoder@2.1.4: + resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} + engines: {node: '>= 14.17'} + + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs-extra@11.3.0: + resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} + engines: {node: '>=14.14'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + + fs-monkey@1.0.6: + resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functional-red-black-tree@1.0.1: + resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-own-enumerable-property-symbols@3.0.2: + resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + github-slugger@1.5.0: + resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + global-dirs@3.0.1: + resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} + engines: {node: '>=10'} + + global-modules@2.0.0: + resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} + engines: {node: '>=6'} + + global-prefix@3.0.0: + resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} + engines: {node: '>=6'} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + globjoin@0.1.4: + resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + got@12.6.1: + resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==} + engines: {node: '>=14.16'} + + graceful-fs@4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + gray-matter@4.0.3: + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} + engines: {node: '>=6.0'} + + gzip-size@6.0.0: + resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} + engines: {node: '>=10'} + + handle-thing@2.0.1: + resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + has-yarn@3.0.0: + resolution: {integrity: sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + has@1.0.4: + resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} + engines: {node: '>= 0.4.0'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-to-parse5@8.0.0: + resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + history@4.10.1: + resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==} + + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + + hookified@1.8.1: + resolution: {integrity: sha512-GrO2l93P8xCWBSTBX9l2BxI78VU/MAAYag+pG8curS3aBGy0++ZlxrQ7PdUOUVMbn5BwkGb6+eRrnf43ipnFEA==} + + hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + + hpack.js@2.1.6: + resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==} + + html-entities@2.5.2: + resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + html-minifier-terser@6.1.0: + resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} + engines: {node: '>=12'} + hasBin: true + + html-minifier-terser@7.2.0: + resolution: {integrity: sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==} + engines: {node: ^14.13.1 || >=16.0.0} + hasBin: true + + html-tags@3.3.1: + resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} + engines: {node: '>=8'} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + html-webpack-plugin@5.6.3: + resolution: {integrity: sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==} + engines: {node: '>=10.13.0'} + peerDependencies: + '@rspack/core': 0.x || 1.x + webpack: ^5.20.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + webpack: + optional: true + + html2canvas@1.4.1: + resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==} + engines: {node: '>=8.0.0'} + + htmlparser2@6.1.0: + resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==} + + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + + http-cache-semantics@4.1.1: + resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + + http-deceiver@1.2.7: + resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==} + + http-errors@1.6.3: + resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} + engines: {node: '>= 0.6'} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + http-parser-js@0.5.9: + resolution: {integrity: sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==} + + http-proxy-middleware@2.0.7: + resolution: {integrity: sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/express': ^4.17.13 + peerDependenciesMeta: + '@types/express': + optional: true + + http-proxy@1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} + engines: {node: '>=8.0.0'} + + http2-wrapper@2.2.1: + resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} + engines: {node: '>=10.19.0'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + icss-utils@5.1.0: + resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@4.0.6: + resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} + engines: {node: '>= 4'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.3: + resolution: {integrity: sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==} + engines: {node: '>= 4'} + + image-size@1.2.0: + resolution: {integrity: sha512-4S8fwbO6w3GeCVN6OPtA9I5IGKkcDMPcKndtUlpJuCwu7JLjtj7JZpwqLuyY2nrmQT3AWsCJLSKPsc2mPBSl3w==} + engines: {node: '>=16.x'} + hasBin: true + + immer@9.0.21: + resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} + + immutable@5.0.3: + resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-lazy@4.0.0: + resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} + engines: {node: '>=8'} + + import-modules@2.1.0: + resolution: {integrity: sha512-8HEWcnkbGpovH9yInoisxaSoIg9Brbul+Ju3Kqe2UsYDUBJD/iQjSgEj0zPcTDPKfPp2fs5xlv1i+JSye/m1/A==} + engines: {node: '>=8'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + infima@0.2.0-alpha.45: + resolution: {integrity: sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==} + engines: {node: '>=12'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ini@2.0.0: + resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} + engines: {node: '>=10'} + + inline-style-parser@0.2.4: + resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} + + inquirer@7.3.3: + resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} + engines: {node: '>=8.0.0'} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + interpret@1.4.0: + resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} + engines: {node: '>= 0.10'} + + invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + ipaddr.js@2.2.0: + resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} + engines: {node: '>= 10'} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-builtin-module@3.2.1: + resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} + engines: {node: '>=6'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-ci@3.0.1: + resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} + hasBin: true + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-installed-globally@0.4.0: + resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} + engines: {node: '>=10'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-npm@6.0.0: + resolution: {integrity: sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-obj@1.0.1: + resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} + engines: {node: '>=0.10.0'} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-path-cwd@2.2.0: + resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} + engines: {node: '>=6'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@3.0.0: + resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} + engines: {node: '>=10'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-regexp@1.0.0: + resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} + engines: {node: '>=0.10.0'} + + is-root@2.1.0: + resolution: {integrity: sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==} + engines: {node: '>=6'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + is-yarn-global@0.4.1: + resolution: {integrity: sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==} + engines: {node: '>=12'} + + isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + + isomorphic-rslog@0.0.6: + resolution: {integrity: sha512-HM0q6XqQ93psDlqvuViNs/Ea3hAyGDkIdVAHlrEocjjAwGrs1fZ+EdQjS9eUPacnYB7Y8SoDdSY3H8p3ce205A==} + engines: {node: '>=14.17.6'} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + joi@17.13.3: + resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-fixer@1.6.15: + resolution: {integrity: sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw==} + engines: {node: '>=10'} + + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + keyv@5.3.1: + resolution: {integrity: sha512-13hQT2q2VIwOoaJdJa7nY3J8UVbYtMTJFHnwm9LI+SaQRfUiM6Em9KZeOVTCKbMnGcRIL3NSUFpAdjZCq24nLQ==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + known-css-properties@0.35.0: + resolution: {integrity: sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==} + + latest-version@7.0.0: + resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} + engines: {node: '>=14.16'} + + launch-editor@2.10.0: + resolution: {integrity: sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-darwin-arm64@1.29.3: + resolution: {integrity: sha512-fb7raKO3pXtlNbQbiMeEu8RbBVHnpyqAoxTyTRMEWFQWmscGC2wZxoHzZ+YKAepUuKT9uIW5vL2QbFivTgprZg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.29.3: + resolution: {integrity: sha512-KF2XZ4ZdmDGGtEYmx5wpzn6u8vg7AdBHaEOvDKu8GOs7xDL/vcU2vMKtTeNe1d4dogkDdi3B9zC77jkatWBwEQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.29.3: + resolution: {integrity: sha512-VUWeVf+V1UM54jv9M4wen9vMlIAyT69Krl9XjI8SsRxz4tdNV/7QEPlW6JASev/pYdiynUCW0pwaFquDRYdxMw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.29.3: + resolution: {integrity: sha512-UhgZ/XVNfXQVEJrMIWeK1Laj8KbhjbIz7F4znUk7G4zeGw7TRoJxhb66uWrEsonn1+O45w//0i0Fu0wIovYdYg==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.29.3: + resolution: {integrity: sha512-Pqau7jtgJNmQ/esugfmAT1aCFy/Gxc92FOxI+3n+LbMHBheBnk41xHDhc0HeYlx9G0xP5tK4t0Koy3QGGNqypw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.29.3: + resolution: {integrity: sha512-dxakOk66pf7KLS7VRYFO7B8WOJLecE5OPL2YOk52eriFd/yeyxt2Km5H0BjLfElokIaR+qWi33gB8MQLrdAY3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.29.3: + resolution: {integrity: sha512-ySZTNCpbfbK8rqpKJeJR2S0g/8UqqV3QnzcuWvpI60LWxnFN91nxpSSwCbzfOXkzKfar9j5eOuOplf+klKtINg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.29.3: + resolution: {integrity: sha512-3pVZhIzW09nzi10usAXfIGTTSTYQ141dk88vGFNCgawIzayiIzZQxEcxVtIkdvlEq2YuFsL9Wcj/h61JHHzuFQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.29.3: + resolution: {integrity: sha512-VRnkAvtIkeWuoBJeGOTrZxsNp4HogXtcaaLm8agmbYtLDOhQdpgxW6NjZZjDXbvGF+eOehGulXZ3C1TiwHY4QQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.29.3: + resolution: {integrity: sha512-IszwRPu2cPnDQsZpd7/EAr0x2W7jkaWqQ1SwCVIZ/tSbZVXPLt6k8s6FkcyBjViCzvB5CW0We0QbbP7zp2aBjQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.29.3: + resolution: {integrity: sha512-GlOJwTIP6TMIlrTFsxTerwC0W6OpQpCGuX1ECRLBUVRh6fpJH3xTqjCjRgQHTb4ZXexH9rtHou1Lf03GKzmhhQ==} + engines: {node: '>= 12.0.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-json-file@4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + + loader-runner@4.3.0: + resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + engines: {node: '>=6.11.5'} + + loader-utils@2.0.4: + resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==} + engines: {node: '>=8.9.0'} + + loader-utils@3.3.1: + resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} + engines: {node: '>= 12.13.0'} + + locate-path@2.0.0: + resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} + engines: {node: '>=4'} + + locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + locate-path@7.2.0: + resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.truncate@4.4.2: + resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + + lowercase-keys@3.0.0: + resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + markdown-table@2.0.0: + resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mathml-tag-names@2.1.3: + resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} + + mdast-util-directive@3.1.0: + resolution: {integrity: sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-frontmatter@2.0.1: + resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + + mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + + mdn-data@2.18.0: + resolution: {integrity: sha512-gtCy1yim/vpHF/tq3B4Z43x3zKWpYeb4IM3d/Mf4oMYcNuoXOYEaqtoFlLHw9zd7+WNN3jNh6/WXyUrD3OIiwQ==} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + medium-zoom@1.1.0: + resolution: {integrity: sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ==} + + memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-directive@3.0.2: + resolution: {integrity: sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==} + + micromark-extension-frontmatter@2.0.0: + resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-mdx-expression@3.0.0: + resolution: {integrity: sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==} + + micromark-extension-mdx-jsx@3.0.1: + resolution: {integrity: sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-mdx-expression@2.0.2: + resolution: {integrity: sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw==} + + micromark-factory-space@1.1.0: + resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@1.2.0: + resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.2: + resolution: {integrity: sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@1.1.0: + resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@1.1.0: + resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.33.0: + resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==} + engines: {node: '>= 0.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.53.0: + resolution: {integrity: sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.18: + resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + mimic-response@4.0.0: + resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + mini-css-extract-plugin@2.9.2: + resolution: {integrity: sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^5.0.0 + + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multicast-dns@7.2.5: + resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} + hasBin: true + + multimap@1.1.0: + resolution: {integrity: sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw==} + + mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + + nanoid@3.3.9: + resolution: {integrity: sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + + node-abi@3.74.0: + resolution: {integrity: sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==} + engines: {node: '>=10'} + + node-addon-api@6.1.0: + resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-emoji@2.2.0: + resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} + engines: {node: '>=18'} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-forge@1.3.1: + resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} + engines: {node: '>= 6.13.0'} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + + normalize-path@2.1.1: + resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} + engines: {node: '>=0.10.0'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + normalize-url@8.0.1: + resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==} + engines: {node: '>=14.16'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nprogress@0.2.0: + resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + null-loader@4.0.1: + resolution: {integrity: sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==} + engines: {node: '>= 10.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + on-headers@1.0.2: + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + + opener@1.5.2: + resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} + hasBin: true + + opentype.js@0.11.0: + resolution: {integrity: sha512-Z9NkAyQi/iEKQYzCSa7/VJSqVIs33wknw8Z8po+DzuRUAqivJ+hJZ94mveg3xIeKwLreJdWTMyEO7x1K13l41Q==} + hasBin: true + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-cancelable@3.0.0: + resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} + engines: {node: '>=12.20'} + + p-limit@1.3.0: + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + p-locate@2.0.0: + resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} + engines: {node: '>=4'} + + p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + + p-try@1.0.0: + resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} + engines: {node: '>=4'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json@8.1.1: + resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==} + engines: {node: '>=14.16'} + + param-case@3.0.4: + resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-numeric-range@1.3.0: + resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} + + parse5-htmlparser2-tree-adapter@7.1.0: + resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} + + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + pascal-case@3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-is-inside@1.0.2: + resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-starts-with@1.0.0: + resolution: {integrity: sha512-CQVfSVN+llYh2D0HWG0jUGJ1ua9VbQyANgIo7cBis37FcpKiN13qh7UxttQqXtgcMZgzjpk/9RktoN6up/XLBQ==} + engines: {node: '>=4'} + + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + + path-to-regexp@1.9.0: + resolution: {integrity: sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==} + + path-to-regexp@3.3.0: + resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + + path-type@3.0.0: + resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} + engines: {node: '>=4'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pegjs@0.10.0: + resolution: {integrity: sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==} + engines: {node: '>=0.10'} + hasBin: true + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + + pify@5.0.0: + resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} + engines: {node: '>=10'} + + pkg-dir@7.0.0: + resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} + engines: {node: '>=14.16'} + + pkg-up@2.0.0: + resolution: {integrity: sha512-fjAPuiws93rm7mPUu21RdBnkeZNrbfCFCwfAhPWY+rR3zG0ubpe5cEReHOw5fIbfmsxEV/g2kSxGTATY3Bpnwg==} + engines: {node: '>=4'} + + pkg-up@3.1.0: + resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} + engines: {node: '>=8'} + + plugin-image-zoom@1.2.0: + resolution: {integrity: sha512-uVCjp4bXuli39gmBs+JQvXMtpfLL+5yWfRIKZyM41d3D9oxGBEHmRzDu9EgusIwmBrKJvF9QuOZENw/9s6G+Jw==} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-attribute-case-insensitive@7.0.1: + resolution: {integrity: sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-calc@9.0.1: + resolution: {integrity: sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.2 + + postcss-clamp@4.1.0: + resolution: {integrity: sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==} + engines: {node: '>=7.6.0'} + peerDependencies: + postcss: ^8.4.6 + + postcss-color-functional-notation@7.0.8: + resolution: {integrity: sha512-S/TpMKVKofNvsxfau/+bw+IA6cSfB6/kmzFj5szUofHOVnFFMB2WwK+Zu07BeMD8T0n+ZnTO5uXiMvAKe2dPkA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-color-hex-alpha@10.0.0: + resolution: {integrity: sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-color-rebeccapurple@10.0.0: + resolution: {integrity: sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-colormin@6.1.0: + resolution: {integrity: sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-convert-values@6.1.0: + resolution: {integrity: sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-custom-media@11.0.5: + resolution: {integrity: sha512-SQHhayVNgDvSAdX9NQ/ygcDQGEY+aSF4b/96z7QUX6mqL5yl/JgG/DywcF6fW9XbnCRE+aVYk+9/nqGuzOPWeQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-custom-properties@14.0.4: + resolution: {integrity: sha512-QnW8FCCK6q+4ierwjnmXF9Y9KF8q0JkbgVfvQEMa93x1GT8FvOiUevWCN2YLaOWyByeDX8S6VFbZEeWoAoXs2A==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-custom-selectors@8.0.4: + resolution: {integrity: sha512-ASOXqNvDCE0dAJ/5qixxPeL1aOVGHGW2JwSy7HyjWNbnWTQCl+fDc968HY1jCmZI0+BaYT5CxsOiUhavpG/7eg==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-dir-pseudo-class@9.0.1: + resolution: {integrity: sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-discard-comments@6.0.2: + resolution: {integrity: sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-discard-duplicates@6.0.3: + resolution: {integrity: sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-discard-empty@6.0.3: + resolution: {integrity: sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-discard-overridden@6.0.2: + resolution: {integrity: sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-discard-unused@6.0.5: + resolution: {integrity: sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-double-position-gradients@6.0.0: + resolution: {integrity: sha512-JkIGah3RVbdSEIrcobqj4Gzq0h53GG4uqDPsho88SgY84WnpkTpI0k50MFK/sX7XqVisZ6OqUfFnoUO6m1WWdg==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-focus-visible@10.0.1: + resolution: {integrity: sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-focus-within@9.0.1: + resolution: {integrity: sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-font-variant@5.0.0: + resolution: {integrity: sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==} + peerDependencies: + postcss: ^8.1.0 + + postcss-gap-properties@6.0.0: + resolution: {integrity: sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-image-set-function@7.0.0: + resolution: {integrity: sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-lab-function@7.0.8: + resolution: {integrity: sha512-plV21I86Hg9q8omNz13G9fhPtLopIWH06bt/Cb5cs1XnaGU2kUtEitvVd4vtQb/VqCdNUHK5swKn3QFmMRbpDg==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-loader@7.3.4: + resolution: {integrity: sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==} + engines: {node: '>= 14.15.0'} + peerDependencies: + postcss: ^7.0.0 || ^8.0.1 + webpack: ^5.0.0 + + postcss-logical@8.1.0: + resolution: {integrity: sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-media-query-parser@0.2.3: + resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} + + postcss-merge-idents@6.0.3: + resolution: {integrity: sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-merge-longhand@6.0.5: + resolution: {integrity: sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-merge-rules@6.1.1: + resolution: {integrity: sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-font-values@6.1.0: + resolution: {integrity: sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-gradients@6.0.3: + resolution: {integrity: sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-params@6.1.0: + resolution: {integrity: sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-selectors@6.0.4: + resolution: {integrity: sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-modules-extract-imports@3.1.0: + resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-local-by-default@4.2.0: + resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-scope@3.2.1: + resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-values@4.0.0: + resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-nesting@13.0.1: + resolution: {integrity: sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-normalize-charset@6.0.2: + resolution: {integrity: sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-display-values@6.0.2: + resolution: {integrity: sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-positions@6.0.2: + resolution: {integrity: sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-repeat-style@6.0.2: + resolution: {integrity: sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-string@6.0.2: + resolution: {integrity: sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-timing-functions@6.0.2: + resolution: {integrity: sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-unicode@6.1.0: + resolution: {integrity: sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-url@6.0.2: + resolution: {integrity: sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-whitespace@6.0.2: + resolution: {integrity: sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-opacity-percentage@3.0.0: + resolution: {integrity: sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-ordered-values@6.0.2: + resolution: {integrity: sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-overflow-shorthand@6.0.0: + resolution: {integrity: sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-page-break@3.0.4: + resolution: {integrity: sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==} + peerDependencies: + postcss: ^8 + + postcss-place@10.0.0: + resolution: {integrity: sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-preset-env@10.1.5: + resolution: {integrity: sha512-LQybafF/K7H+6fAs4SIkgzkSCixJy0/h0gubDIAP3Ihz+IQBRwsjyvBnAZ3JUHD+A/ITaxVRPDxn//a3Qy4pDw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-pseudo-class-any-link@10.0.1: + resolution: {integrity: sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-reduce-idents@6.0.3: + resolution: {integrity: sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-reduce-initial@6.1.0: + resolution: {integrity: sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-reduce-transforms@6.0.2: + resolution: {integrity: sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-replace-overflow-wrap@4.0.0: + resolution: {integrity: sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==} + peerDependencies: + postcss: ^8.0.3 + + postcss-resolve-nested-selector@0.1.6: + resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==} + + postcss-safe-parser@7.0.1: + resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: ^8.4.31 + + postcss-scss@4.0.9: + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.4.29 + + postcss-selector-not@8.0.1: + resolution: {integrity: sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-selector-parser@7.1.0: + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + engines: {node: '>=4'} + + postcss-sort-media-queries@5.2.0: + resolution: {integrity: sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.4.23 + + postcss-sorting@8.0.2: + resolution: {integrity: sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==} + peerDependencies: + postcss: ^8.4.20 + + postcss-svgo@6.0.3: + resolution: {integrity: sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==} + engines: {node: ^14 || ^16 || >= 18} + peerDependencies: + postcss: ^8.4.31 + + postcss-unique-selectors@6.0.4: + resolution: {integrity: sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss-zindex@6.0.2: + resolution: {integrity: sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + engines: {node: '>=14'} + hasBin: true + + pretty-error@4.0.0: + resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==} + + pretty-time@1.1.0: + resolution: {integrity: sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==} + engines: {node: '>=4'} + + prism-react-renderer@2.4.1: + resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==} + peerDependencies: + react: '>=16.0.0' + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + property-information@6.5.0: + resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + + property-information@7.0.0: + resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==} + + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pupa@3.1.0: + resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==} + engines: {node: '>=12.20'} + + pushfeedback-react@0.1.50: + resolution: {integrity: sha512-Bvtu/czdrty+ELfBoHMGO/2aqtb8roP4Hq2HImU95ohyr/8sj3jKCam33k51PLZPQ65IQ2Dg+EQtrOOqNKD/Wg==} + + pushfeedback@0.1.52: + resolution: {integrity: sha512-hYr1ZXMjPyut4sE9bBTqIE43EEg4E2HRkROH9WM355/e1k5hOdXd4F9ViadySHp5NjkDpLapdkN4iWcD3r+R/A==} + + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + queue@6.0.2: + resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} + + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + range-parser@1.2.0: + resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==} + engines: {node: '>= 0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + rc-util@5.44.4: + resolution: {integrity: sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + react-dev-utils@12.0.1: + resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=2.7' + webpack: '>=4' + peerDependenciesMeta: + typescript: + optional: true + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-error-overlay@6.1.0: + resolution: {integrity: sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==} + + react-fast-compare@3.2.2: + resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + + react-fast-marquee@1.6.5: + resolution: {integrity: sha512-swDnPqrT2XISAih0o74zQVE2wQJFMvkx+9VZXYYNSLb/CUcAzU9pNj637Ar2+hyRw6b4tP6xh4GQZip2ZCpQpg==} + peerDependencies: + react: '>= 16.8.0 || ^18.0.0' + react-dom: '>= 16.8.0 || ^18.0.0' + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-json-view-lite@1.5.0: + resolution: {integrity: sha512-nWqA1E4jKPklL2jvHWs6s+7Na0qNgw9HCP6xehdQJeg6nPBTFZgGwyko9Q0oj+jQWKTTVRS30u0toM5wiuL3iw==} + engines: {node: '>=14'} + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + + react-loadable-ssr-addon-v5-slorber@1.0.1: + resolution: {integrity: sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==} + engines: {node: '>=10.13.0'} + peerDependencies: + react-loadable: '*' + webpack: '>=4.41.1 || 5.x' + + react-router-config@5.1.1: + resolution: {integrity: sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==} + peerDependencies: + react: '>=15' + react-router: '>=5' + + react-router-dom@5.3.4: + resolution: {integrity: sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==} + peerDependencies: + react: '>=15' + + react-router@5.3.4: + resolution: {integrity: sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==} + peerDependencies: + react: '>=15' + + react-waypoint@10.3.0: + resolution: {integrity: sha512-iF1y2c1BsoXuEGz08NoahaLFIGI9gTUAAOKip96HUmylRT6DUtpgoBPjk/Y8dfcFVmfVDvUzWjNXpZyKTOV0SQ==} + peerDependencies: + react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-pkg-up@3.0.0: + resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} + engines: {node: '>=4'} + + read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + + read-pkg@3.0.0: + resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} + engines: {node: '>=4'} + + read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + reading-time@1.5.0: + resolution: {integrity: sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==} + + rechoir@0.6.2: + resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} + engines: {node: '>= 0.10'} + + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.0: + resolution: {integrity: sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==} + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + + recursive-readdir@2.2.3: + resolution: {integrity: sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==} + engines: {node: '>=6.0.0'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regenerate-unicode-properties@10.2.0: + resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regenerator-transform@0.15.2: + resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} + + regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + regexpp@3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} + + regexpu-core@6.2.0: + resolution: {integrity: sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==} + engines: {node: '>=4'} + + registry-auth-token@5.1.0: + resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==} + engines: {node: '>=14'} + + registry-url@6.0.1: + resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==} + engines: {node: '>=12'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.12.0: + resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==} + hasBin: true + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + + relateurl@0.2.7: + resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} + engines: {node: '>= 0.10'} + + remark-directive@3.0.1: + resolution: {integrity: sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==} + + remark-emoji@4.0.1: + resolution: {integrity: sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + remark-frontmatter@5.0.0: + resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-mdx@3.1.0: + resolution: {integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.1: + resolution: {integrity: sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + remove-trailing-separator@1.1.0: + resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} + + renderkid@3.0.0: + resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==} + + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + require-like@0.1.2: + resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + reserved-words@0.1.2: + resolution: {integrity: sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw==} + + resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pathname@3.0.0: + resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + responselike@3.0.0: + resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} + engines: {node: '>=14.16'} + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rtlcss@4.3.0: + resolution: {integrity: sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==} + engines: {node: '>=12.0.0'} + hasBin: true + + run-async@2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@6.6.7: + resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} + engines: {npm: '>=2.0.0'} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safe-regex@2.1.1: + resolution: {integrity: sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sass-loader@16.0.5: + resolution: {integrity: sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==} + engines: {node: '>= 18.12.0'} + peerDependencies: + '@rspack/core': 0.x || 1.x + node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + sass: ^1.3.0 + sass-embedded: '*' + webpack: ^5.0.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + node-sass: + optional: true + sass: + optional: true + sass-embedded: + optional: true + webpack: + optional: true + + sass@1.85.1: + resolution: {integrity: sha512-Uk8WpxM5v+0cMR0XjX9KfRIacmSG86RH4DCCZjLU2rFh5tyutt9siAXJ7G+YfxQ99Q6wrRMbMlVl6KqUms71ag==} + engines: {node: '>=14.0.0'} + hasBin: true + + sax@1.4.1: + resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + schema-utils@2.7.0: + resolution: {integrity: sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==} + engines: {node: '>= 8.9.0'} + + schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} + + schema-utils@4.3.0: + resolution: {integrity: sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==} + engines: {node: '>= 10.13.0'} + + search-insights@2.17.3: + resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} + + section-matter@1.0.0: + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} + engines: {node: '>=4'} + + select-hose@2.0.0: + resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} + + selfsigned@2.4.1: + resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} + engines: {node: '>=10'} + + semver-diff@4.0.0: + resolution: {integrity: sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==} + engines: {node: '>=12'} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + serve-handler@6.1.6: + resolution: {integrity: sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==} + + serve-index@1.9.1: + resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + setprototypeof@1.1.0: + resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sha1@1.1.1: + resolution: {integrity: sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==} + + shallow-clone@3.0.1: + resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} + engines: {node: '>=8'} + + shallowequal@1.1.0: + resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + + sharp@0.32.6: + resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} + engines: {node: '>=14.15.0'} + + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.2: + resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} + engines: {node: '>= 0.4'} + + shelljs@0.8.5: + resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} + engines: {node: '>=4'} + hasBin: true + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + sirv@2.0.4: + resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} + engines: {node: '>= 10'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + sitemap@7.1.2: + resolution: {integrity: sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==} + engines: {node: '>=12.0.0', npm: '>=5.6.0'} + hasBin: true + + skin-tone@2.0.0: + resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} + engines: {node: '>=8'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + + snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + + sockjs@0.3.24: + resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} + + sort-css-media-queries@2.2.0: + resolution: {integrity: sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==} + engines: {node: '>= 6.3.0'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.21: + resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} + + spdy-transport@3.0.0: + resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} + + spdy@4.0.2: + resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} + engines: {node: '>=6.0.0'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + srcset@4.0.0: + resolution: {integrity: sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==} + engines: {node: '>=12'} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + std-env@3.8.1: + resolution: {integrity: sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==} + + streamx@2.22.0: + resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.codepointat@0.2.1: + resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + stringify-object@3.3.0: + resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} + engines: {node: '>=4'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} + engines: {node: '>=0.10.0'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + style-to-js@1.1.16: + resolution: {integrity: sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==} + + style-to-object@1.0.8: + resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==} + + stylehacks@6.1.1: + resolution: {integrity: sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + stylelint-config-recess-order@5.1.1: + resolution: {integrity: sha512-eDAHWVBelzDbMbdMj15pSw0Ycykv5eLeriJdbGCp0zd44yvhgZLI+wyVHegzXp5NrstxTPSxl0fuOVKdMm0XLA==} + peerDependencies: + stylelint: '>=16' + + stylelint-config-recommended-scss@14.1.0: + resolution: {integrity: sha512-bhaMhh1u5dQqSsf6ri2GVWWQW5iUjBYgcHkh7SgDDn92ijoItC/cfO/W+fpXshgTQWhwFkP1rVcewcv4jaftRg==} + engines: {node: '>=18.12.0'} + peerDependencies: + postcss: ^8.3.3 + stylelint: ^16.6.1 + peerDependenciesMeta: + postcss: + optional: true + + stylelint-config-recommended@14.0.1: + resolution: {integrity: sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.1.0 + + stylelint-config-standard-scss@13.1.0: + resolution: {integrity: sha512-Eo5w7/XvwGHWkeGLtdm2FZLOMYoZl1omP2/jgFCXyl2x5yNz7/8vv4Tj6slHvMSSUNTaGoam/GAZ0ZhukvalfA==} + engines: {node: '>=18.12.0'} + peerDependencies: + postcss: ^8.3.3 + stylelint: ^16.3.1 + peerDependenciesMeta: + postcss: + optional: true + + stylelint-config-standard@36.0.1: + resolution: {integrity: sha512-8aX8mTzJ6cuO8mmD5yon61CWuIM4UD8Q5aBcWKGSf6kg+EC3uhB+iOywpTK4ca6ZL7B49en8yanOFtUW0qNzyw==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.1.0 + + stylelint-order@6.0.4: + resolution: {integrity: sha512-0UuKo4+s1hgQ/uAxlYU4h0o0HS4NiQDud0NAUNI0aa8FJdmYHA5ZZTFHiV5FpmE3071e9pZx5j0QpVJW5zOCUA==} + peerDependencies: + stylelint: ^14.0.0 || ^15.0.0 || ^16.0.1 + + stylelint-scss@6.11.1: + resolution: {integrity: sha512-e4rYo0UY+BIMtGeGanghrvHTjcryxgZbyFxUedp8dLFqC4P70aawNdYjRrQxbnKhu3BNr4+lt5e/53tcKXiwFA==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.0.2 + + stylelint@16.16.0: + resolution: {integrity: sha512-40X5UOb/0CEFnZVEHyN260HlSSUxPES+arrUphOumGWgXERHfwCD0kNBVILgQSij8iliYVwlc0V7M5bcLP9vPg==} + engines: {node: '>=18.12.0'} + hasBin: true + + superstruct@1.0.4: + resolution: {integrity: sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ==} + engines: {node: '>=14.0.0'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-hyperlinks@3.2.0: + resolution: {integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==} + engines: {node: '>=14.18'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + svg-parser@2.0.4: + resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + + svg-tags@1.0.0: + resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} + + svgo@3.3.2: + resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==} + engines: {node: '>=14.0.0'} + hasBin: true + + swc-loader@0.2.6: + resolution: {integrity: sha512-9Zi9UP2YmDpgmQVbyOPJClY0dwf58JDyDMQ7uRc4krmc72twNI2fvlBWHLqVekBpPc7h5NJkGVT1zNDxFrqhvg==} + peerDependencies: + '@swc/core': ^1.2.147 + webpack: '>=2' + + table@6.9.0: + resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} + engines: {node: '>=10.0.0'} + + tapable@1.1.3: + resolution: {integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==} + engines: {node: '>=6'} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + + tar-fs@2.1.2: + resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} + + tar-fs@3.0.8: + resolution: {integrity: sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + + terser-webpack-plugin@5.3.14: + resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + + terser@5.39.0: + resolution: {integrity: sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==} + engines: {node: '>=10'} + hasBin: true + + text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + + text-segmentation@1.0.3: + resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + text-to-svg@3.1.5: + resolution: {integrity: sha512-mbeGhMz9MAFaGaZGE8n4Mh/iQV/UkVnYJXhXFrv0eWkcNTgflhpHR2a8nr2ci3NU4FekIHJYKh0N0qdc6yUpfA==} + hasBin: true + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + thunky@1.1.0: + resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} + + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + + type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + + type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typedarray-to-buffer@3.1.5: + resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + + typescript@5.8.2: + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + engines: {node: '>=14.17'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + + unicode-emoji-modifier-base@1.0.0: + resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.0: + resolution: {integrity: sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.1.0: + resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + engines: {node: '>=4'} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unique-string@3.0.0: + resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} + engines: {node: '>=12'} + + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + update-notifier@6.0.2: + resolution: {integrity: sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==} + engines: {node: '>=14.16'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + url-loader@4.1.1: + resolution: {integrity: sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==} + engines: {node: '>= 10.13.0'} + peerDependencies: + file-loader: '*' + webpack: ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + file-loader: + optional: true + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utila@0.4.0: + resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} + + utility-types@3.11.0: + resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} + engines: {node: '>= 4'} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + utrie@1.0.2: + resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + v8-compile-cache@2.4.0: + resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + value-equal@1.0.1: + resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + watchpack@2.4.2: + resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} + engines: {node: '>=10.13.0'} + + wbuf@1.7.3: + resolution: {integrity: sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==} + + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webpack-bundle-analyzer@4.10.2: + resolution: {integrity: sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==} + engines: {node: '>= 10.13.0'} + hasBin: true + + webpack-dev-middleware@5.3.4: + resolution: {integrity: sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + + webpack-dev-server@4.15.2: + resolution: {integrity: sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==} + engines: {node: '>= 12.13.0'} + hasBin: true + peerDependencies: + webpack: ^4.37.0 || ^5.0.0 + webpack-cli: '*' + peerDependenciesMeta: + webpack: + optional: true + webpack-cli: + optional: true + + webpack-merge@5.10.0: + resolution: {integrity: sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==} + engines: {node: '>=10.0.0'} + + webpack-merge@6.0.1: + resolution: {integrity: sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==} + engines: {node: '>=18.0.0'} + + webpack-sources@3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} + engines: {node: '>=10.13.0'} + + webpack@5.98.0: + resolution: {integrity: sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + + webpackbar@6.0.1: + resolution: {integrity: sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==} + engines: {node: '>=14.21.3'} + peerDependencies: + webpack: 3 || 4 || 5 + + websocket-driver@0.7.4: + resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} + engines: {node: '>=0.8.0'} + + websocket-extensions@0.1.4: + resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} + engines: {node: '>=0.8.0'} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + widest-line@4.0.1: + resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} + engines: {node: '>=12'} + + wildcard@2.0.1: + resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@3.0.3: + resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} + + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.18.1: + resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xdg-basedir@5.1.0: + resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} + engines: {node: '>=12'} + + xml-js@1.6.11: + resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} + hasBin: true + + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-queue@1.2.0: + resolution: {integrity: sha512-KHBC7z61OJeaMGnF3wqNZj+GGNXOyypZviiKpQeiHirG5Ib1ImwcLBH70rbMSkKfSmUNBsdf2PwaEJtKvgmkNw==} + engines: {node: '>=12.20'} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@algolia/autocomplete-core@1.17.9(@algolia/client-search@5.21.0)(algoliasearch@5.21.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-plugin-algolia-insights': 1.17.9(@algolia/client-search@5.21.0)(algoliasearch@5.21.0)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.17.9(@algolia/client-search@5.21.0)(algoliasearch@5.21.0) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + - search-insights + + '@algolia/autocomplete-plugin-algolia-insights@1.17.9(@algolia/client-search@5.21.0)(algoliasearch@5.21.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-shared': 1.17.9(@algolia/client-search@5.21.0)(algoliasearch@5.21.0) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + + '@algolia/autocomplete-preset-algolia@1.17.9(@algolia/client-search@5.21.0)(algoliasearch@5.21.0)': + dependencies: + '@algolia/autocomplete-shared': 1.17.9(@algolia/client-search@5.21.0)(algoliasearch@5.21.0) + '@algolia/client-search': 5.21.0 + algoliasearch: 5.21.0 + + '@algolia/autocomplete-shared@1.17.9(@algolia/client-search@5.21.0)(algoliasearch@5.21.0)': + dependencies: + '@algolia/client-search': 5.21.0 + algoliasearch: 5.21.0 + + '@algolia/client-abtesting@5.21.0': + dependencies: + '@algolia/client-common': 5.21.0 + '@algolia/requester-browser-xhr': 5.21.0 + '@algolia/requester-fetch': 5.21.0 + '@algolia/requester-node-http': 5.21.0 + + '@algolia/client-analytics@5.21.0': + dependencies: + '@algolia/client-common': 5.21.0 + '@algolia/requester-browser-xhr': 5.21.0 + '@algolia/requester-fetch': 5.21.0 + '@algolia/requester-node-http': 5.21.0 + + '@algolia/client-common@5.21.0': {} + + '@algolia/client-insights@5.21.0': + dependencies: + '@algolia/client-common': 5.21.0 + '@algolia/requester-browser-xhr': 5.21.0 + '@algolia/requester-fetch': 5.21.0 + '@algolia/requester-node-http': 5.21.0 + + '@algolia/client-personalization@5.21.0': + dependencies: + '@algolia/client-common': 5.21.0 + '@algolia/requester-browser-xhr': 5.21.0 + '@algolia/requester-fetch': 5.21.0 + '@algolia/requester-node-http': 5.21.0 + + '@algolia/client-query-suggestions@5.21.0': + dependencies: + '@algolia/client-common': 5.21.0 + '@algolia/requester-browser-xhr': 5.21.0 + '@algolia/requester-fetch': 5.21.0 + '@algolia/requester-node-http': 5.21.0 + + '@algolia/client-search@5.21.0': + dependencies: + '@algolia/client-common': 5.21.0 + '@algolia/requester-browser-xhr': 5.21.0 + '@algolia/requester-fetch': 5.21.0 + '@algolia/requester-node-http': 5.21.0 + + '@algolia/events@4.0.1': {} + + '@algolia/ingestion@1.21.0': + dependencies: + '@algolia/client-common': 5.21.0 + '@algolia/requester-browser-xhr': 5.21.0 + '@algolia/requester-fetch': 5.21.0 + '@algolia/requester-node-http': 5.21.0 + + '@algolia/monitoring@1.21.0': + dependencies: + '@algolia/client-common': 5.21.0 + '@algolia/requester-browser-xhr': 5.21.0 + '@algolia/requester-fetch': 5.21.0 + '@algolia/requester-node-http': 5.21.0 + + '@algolia/recommend@5.21.0': + dependencies: + '@algolia/client-common': 5.21.0 + '@algolia/requester-browser-xhr': 5.21.0 + '@algolia/requester-fetch': 5.21.0 + '@algolia/requester-node-http': 5.21.0 + + '@algolia/requester-browser-xhr@5.21.0': + dependencies: + '@algolia/client-common': 5.21.0 + + '@algolia/requester-fetch@5.21.0': + dependencies: + '@algolia/client-common': 5.21.0 + + '@algolia/requester-node-http@5.21.0': + dependencies: + '@algolia/client-common': 5.21.0 + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@ant-design/colors@7.2.0': + dependencies: + '@ant-design/fast-color': 2.0.6 + + '@ant-design/fast-color@2.0.6': + dependencies: + '@babel/runtime': 7.26.10 + + '@ant-design/icons-svg@4.4.2': {} + + '@ant-design/icons@5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@ant-design/colors': 7.2.0 + '@ant-design/icons-svg': 4.4.2 + '@babel/runtime': 7.26.10 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@babel/code-frame@7.12.11': + dependencies: + '@babel/highlight': 7.25.9 + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.26.8': {} + + '@babel/core@7.26.10': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.10 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helpers': 7.26.10 + '@babel/parser': 7.26.10 + '@babel/template': 7.26.9 + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 + convert-source-map: 2.0.0 + debug: 4.4.0 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/eslint-parser@7.26.10(@babel/core@7.26.10)(eslint@7.32.0)': + dependencies: + '@babel/core': 7.26.10 + '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 + eslint: 7.32.0 + eslint-visitor-keys: 2.1.0 + semver: 6.3.1 + + '@babel/generator@7.26.10': + dependencies: + '@babel/parser': 7.26.10 + '@babel/types': 7.26.10 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.25.9': + dependencies: + '@babel/types': 7.26.10 + + '@babel/helper-compilation-targets@7.26.5': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.26.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10) + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/traverse': 7.26.10 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.26.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + regexpu-core: 6.2.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-plugin-utils': 7.26.5 + debug: 4.4.0 + lodash.debounce: 4.0.8 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-member-expression-to-functions@7.25.9': + dependencies: + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.26.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.25.9': + dependencies: + '@babel/types': 7.26.10 + + '@babel/helper-plugin-utils@7.26.5': {} + + '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-wrap-function': 7.25.9 + '@babel/traverse': 7.26.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.26.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/traverse': 7.26.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.25.9': + dependencies: + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helper-wrap-function@7.25.9': + dependencies: + '@babel/template': 7.26.9 + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.26.10': + dependencies: + '@babel/template': 7.26.9 + '@babel/types': 7.26.10 + + '@babel/highlight@7.25.9': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/parser@7.26.10': + dependencies: + '@babel/types': 7.26.10 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.26.10 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.26.10 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-async-generator-functions@7.26.8(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.10) + '@babel/traverse': 7.26.10 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.26.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-create-regexp-features-plugin': 7.22.9(@babel/core@7.22.11) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-duplicate-keys@7.18.9(@babel/core@7.19.1): - resolution: {integrity: sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-classes@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10) + '@babel/traverse': 7.26.10 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-duplicate-keys@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/template': 7.26.9 - /@babel/plugin-transform-dynamic-import@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.22.11) + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-exponentiation-operator@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-builder-binary-assignment-operator-visitor': 7.18.9 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-exponentiation-operator@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-builder-binary-assignment-operator-visitor': 7.22.10 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-export-namespace-from@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.22.11) + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-for-of@7.18.8(@babel/core@7.19.1): - resolution: {integrity: sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-for-of@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-exponentiation-operator@7.26.3(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-function-name@7.18.9(@babel/core@7.19.1): - resolution: {integrity: sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-compilation-targets': 7.19.1(@babel/core@7.19.1) - '@babel/helper-function-name': 7.19.0 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-function-name@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-for-of@7.26.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-compilation-targets': 7.22.10 - '@babel/helper-function-name': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-json-strings@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.11) + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.26.10 + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-literals@7.18.9(@babel/core@7.19.1): - resolution: {integrity: sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-literals@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-literals@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-logical-assignment-operators@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.11) + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-member-expression-literals@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-member-expression-literals@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-modules-amd@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-module-transforms': 7.19.0 - '@babel/helper-plugin-utils': 7.19.0 - babel-plugin-dynamic-import-node: 2.3.3 + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - dev: false - /@babel/plugin-transform-modules-amd@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-module-transforms': 7.22.9(@babel/core@7.22.11) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.26.10 + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-modules-commonjs@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-module-transforms': 7.19.0 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/helper-simple-access': 7.18.6 - babel-plugin-dynamic-import-node: 2.3.3 + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - dev: false - /@babel/plugin-transform-modules-commonjs@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-o2+bg7GDS60cJMgz9jWqRUsWkMzLCxp+jFDeDUT5sjRlAxcJWZ2ylNdI7QQ2+CH5hWu7OnN+Cv3htt7AkSf96g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-module-transforms': 7.22.9(@babel/core@7.22.11) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-simple-access': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-modules-systemjs@7.19.0(@babel/core@7.19.1): - resolution: {integrity: sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-module-transforms': 7.19.0 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/helper-validator-identifier': 7.19.1 - babel-plugin-dynamic-import-node: 2.3.3 - transitivePeerDependencies: - - supports-color - dev: false + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-modules-systemjs@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-rIqHmHoMEOhI3VkVf5jQ15l539KrwhzqcBO6wdCNWPWc/JWt9ILNYNUssbRpeq0qWns8svuw8LnMNCvWBIJ8wA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-nullish-coalescing-operator@7.26.6(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-module-transforms': 7.22.9(@babel/core@7.22.11) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-validator-identifier': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-modules-umd@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-module-transforms': 7.19.0 - '@babel/helper-plugin-utils': 7.19.0 + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.10) + + '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10) transitivePeerDependencies: - supports-color - dev: false - /@babel/plugin-transform-modules-umd@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-module-transforms': 7.22.9(@babel/core@7.22.11) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-named-capturing-groups-regex@7.19.1(@babel/core@7.19.1): - resolution: {integrity: sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-create-regexp-features-plugin': 7.19.0(@babel/core@7.19.1) - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-create-regexp-features-plugin': 7.22.9(@babel/core@7.22.11) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-new-target@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-new-target@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-nullish-coalescing-operator@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.11) + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-numeric-separator@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-constant-elements@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.11) + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-object-rest-spread@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-nX8cPFa6+UmbepISvlf5jhQyaC7ASs/7UxHmMkuJ/k5xSHvDPPaibMo+v3TXwU/Pjqhep/nFNpd3zn4YR59pnw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-display-name@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/compat-data': 7.22.9 - '@babel/core': 7.22.11 - '@babel/helper-compilation-targets': 7.22.10 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.11) - '@babel/plugin-transform-parameters': 7.22.5(@babel/core@7.22.11) + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-object-super@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-development@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/helper-replace-supers': 7.19.1 + '@babel/core': 7.26.10 + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.10) transitivePeerDependencies: - supports-color - dev: false - /@babel/plugin-transform-object-super@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-replace-supers': 7.22.9(@babel/core@7.22.11) + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10) + '@babel/types': 7.26.10 + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-optional-catch-binding@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-pure-annotations@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.11) + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-optional-chaining@7.22.12(@babel/core@7.22.11): - resolution: {integrity: sha512-7XXCVqZtyFWqjDsYDY4T45w4mlx1rf7aOgkc/Ww76xkgBiOlmjPkx36PBLHa1k1rwWvVgYMPsbuVnIamx2ZQJw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.11) + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + regenerator-transform: 0.15.2 - /@babel/plugin-transform-parameters@7.18.8(@babel/core@7.19.1): - resolution: {integrity: sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-parameters@7.22.5(@babel/core@7.12.9): - resolution: {integrity: sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.12.9 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-parameters@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-runtime@7.26.10(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.26.10) + babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.26.10) + babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.26.10) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-private-methods@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-create-class-features-plugin': 7.22.11(@babel/core@7.22.11) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-private-property-in-object@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-spread@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-create-class-features-plugin': 7.22.11(@babel/core@7.22.11) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.22.11) + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-property-literals@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-property-literals@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-template-literals@7.26.8(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-react-constant-elements@7.18.12(@babel/core@7.19.1): - resolution: {integrity: sha512-Q99U9/ttiu+LMnRU8psd23HhvwXmKWDQIpocm0JKaICcZHnw+mdQbHm6xnSy7dOl8I5PELakYtNBubNQlBXbZw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-typeof-symbol@7.26.7(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 - /@babel/plugin-transform-react-constant-elements@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-BF5SXoO+nX3h5OhlN78XbbDrBOffv+AxPP2ENaJOVqjWCgBDeOY3WcaUcddutGSfoap+5NEQ/q/4I3WZIvgkXA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-typescript@7.26.8(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-react-display-name@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/preset-env@7.26.9(@babel/core@7.26.10)': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.10) + '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.10) + '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-async-generator-functions': 7.26.8(@babel/core@7.26.10) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-block-scoped-functions': 7.26.5(@babel/core@7.26.10) + '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-exponentiation-operator': 7.26.3(@babel/core@7.26.10) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-for-of': 7.26.9(@babel/core@7.26.10) + '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.10) + '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-nullish-coalescing-operator': 7.26.6(@babel/core@7.26.10) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-template-literals': 7.26.8(@babel/core@7.26.10) + '@babel/plugin-transform-typeof-symbol': 7.26.7(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.26.10) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.10) + babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.26.10) + babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.26.10) + babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.26.10) + core-js-compat: 3.41.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-react-display-name@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/types': 7.26.10 + esutils: 2.0.3 - /@babel/plugin-transform-react-jsx-development@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/preset-react@7.26.3(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.19.1 - '@babel/plugin-transform-react-jsx': 7.19.0(@babel/core@7.19.1) - dev: false + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-transform-react-display-name': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-react-jsx-development': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-react-pure-annotations': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-react-jsx-development@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/preset-typescript@7.26.0(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 - '@babel/plugin-transform-react-jsx': 7.22.5(@babel/core@7.22.11) + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.10) + '@babel/plugin-transform-typescript': 7.26.8(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-react-jsx@7.19.0(@babel/core@7.19.1): - resolution: {integrity: sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/runtime-corejs3@7.26.10': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-module-imports': 7.18.6 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.19.1) - '@babel/types': 7.19.0 - dev: false + core-js-pure: 3.41.0 + regenerator-runtime: 0.14.1 - /@babel/plugin-transform-react-jsx@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/runtime@7.26.10': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-module-imports': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.22.11) - '@babel/types': 7.22.11 + regenerator-runtime: 0.14.1 - /@babel/plugin-transform-react-pure-annotations@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/template@7.26.9': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.10 + '@babel/types': 7.26.10 - /@babel/plugin-transform-react-pure-annotations@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/traverse@7.26.10': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.10 + '@babel/parser': 7.26.10 + '@babel/template': 7.26.9 + '@babel/types': 7.26.10 + debug: 4.4.0 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color - /@babel/plugin-transform-regenerator@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/types@7.26.10': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - regenerator-transform: 0.15.0 - dev: false + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 - /@babel/plugin-transform-regenerator@7.22.10(@babel/core@7.22.11): - resolution: {integrity: sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - regenerator-transform: 0.15.2 + '@colors/colors@1.5.0': + optional: true - /@babel/plugin-transform-reserved-words@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/cascade-layer-name-parser@2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 - /@babel/plugin-transform-reserved-words@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/color-helpers@5.0.2': {} + + '@csstools/css-calc@2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 - /@babel/plugin-transform-runtime@7.22.10(@babel/core@7.22.11): - resolution: {integrity: sha512-RchI7HePu1eu0CYNKHHHQdfenZcM4nz8rew5B1VWqeRKdcwW5aQ5HeG9eTUbWiAS1UrmHVLmoxTWHt3iLD/NhA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/css-color-parser@3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-module-imports': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 - babel-plugin-polyfill-corejs2: 0.4.5(@babel/core@7.22.11) - babel-plugin-polyfill-corejs3: 0.8.3(@babel/core@7.22.11) - babel-plugin-polyfill-regenerator: 0.5.2(@babel/core@7.22.11) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color + '@csstools/color-helpers': 5.0.2 + '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 - /@babel/plugin-transform-shorthand-properties@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@csstools/css-tokenizer': 3.0.3 - /@babel/plugin-transform-shorthand-properties@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/css-tokenizer@3.0.3': {} + + '@csstools/media-query-list-parser@4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 - /@babel/plugin-transform-spread@7.19.0(@babel/core@7.19.1): - resolution: {integrity: sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-cascade-layers@5.0.1(postcss@8.5.3)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/helper-skip-transparent-expression-wrappers': 7.18.9 - dev: false + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 - /@babel/plugin-transform-spread@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-color-function@4.0.8(postcss@8.5.3)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3) + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 - /@babel/plugin-transform-sticky-regex@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-color-mix-function@3.0.8(postcss@8.5.3)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3) + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 - /@babel/plugin-transform-sticky-regex@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-content-alt-text@2.0.4(postcss@8.5.3)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3) + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 - /@babel/plugin-transform-template-literals@7.18.9(@babel/core@7.19.1): - resolution: {integrity: sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-exponential-functions@2.0.7(postcss@8.5.3)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + postcss: 8.5.3 - /@babel/plugin-transform-template-literals@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-font-format-keywords@4.0.0(postcss@8.5.3)': + dependencies: + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-gamut-mapping@2.0.8(postcss@8.5.3)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + postcss: 8.5.3 - /@babel/plugin-transform-typeof-symbol@7.18.9(@babel/core@7.19.1): - resolution: {integrity: sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-gradients-interpolation-method@5.0.8(postcss@8.5.3)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3) + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 - /@babel/plugin-transform-typeof-symbol@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-hwb-function@4.0.8(postcss@8.5.3)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3) + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 - /@babel/plugin-transform-typescript@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-0E4/L+7gfvHub7wsbTv03oRtD69X31LByy44fGmFzbZScpupFByMcgCJ0VbBTkzyjSJKuRoGN8tcijOWKTmqOA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-ic-unit@4.0.0(postcss@8.5.3)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-create-class-features-plugin': 7.22.11(@babel/core@7.22.11) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.22.11) + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3) + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 + postcss-value-parser: 4.2.0 - /@babel/plugin-transform-unicode-escapes@7.18.10(@babel/core@7.19.1): - resolution: {integrity: sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-initial@2.0.1(postcss@8.5.3)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - dev: false + postcss: 8.5.3 - /@babel/plugin-transform-unicode-escapes@7.22.10(@babel/core@7.22.11): - resolution: {integrity: sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-is-pseudo-class@5.0.1(postcss@8.5.3)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 - /@babel/plugin-transform-unicode-property-regex@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-light-dark-function@2.0.7(postcss@8.5.3)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-create-regexp-features-plugin': 7.22.9(@babel/core@7.22.11) - '@babel/helper-plugin-utils': 7.22.5 + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3) + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 - /@babel/plugin-transform-unicode-regex@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.5.3)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-create-regexp-features-plugin': 7.19.0(@babel/core@7.19.1) - '@babel/helper-plugin-utils': 7.19.0 - dev: false + postcss: 8.5.3 - /@babel/plugin-transform-unicode-regex@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-logical-overflow@2.0.0(postcss@8.5.3)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-create-regexp-features-plugin': 7.22.9(@babel/core@7.22.11) - '@babel/helper-plugin-utils': 7.22.5 + postcss: 8.5.3 - /@babel/plugin-transform-unicode-sets-regex@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.5.3)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-create-regexp-features-plugin': 7.22.9(@babel/core@7.22.11) - '@babel/helper-plugin-utils': 7.22.5 + postcss: 8.5.3 - /@babel/preset-env@7.19.1(@babel/core@7.19.1): - resolution: {integrity: sha512-c8B2c6D16Lp+Nt6HcD+nHl0VbPKVnNPTpszahuxJJnurfMtKeZ80A+qUv48Y7wqvS+dTFuLuaM9oYxyNHbCLWA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-logical-resize@3.0.0(postcss@8.5.3)': dependencies: - '@babel/compat-data': 7.19.1 - '@babel/core': 7.19.1 - '@babel/helper-compilation-targets': 7.19.1(@babel/core@7.19.1) - '@babel/helper-plugin-utils': 7.19.0 - '@babel/helper-validator-option': 7.18.6 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.18.9(@babel/core@7.19.1) - '@babel/plugin-proposal-async-generator-functions': 7.19.1(@babel/core@7.19.1) - '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-proposal-class-static-block': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-proposal-dynamic-import': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-proposal-export-namespace-from': 7.18.9(@babel/core@7.19.1) - '@babel/plugin-proposal-json-strings': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-proposal-logical-assignment-operators': 7.18.9(@babel/core@7.19.1) - '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-proposal-object-rest-spread': 7.18.9(@babel/core@7.19.1) - '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-proposal-optional-chaining': 7.18.9(@babel/core@7.19.1) - '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-proposal-private-property-in-object': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.19.1) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.19.1) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.19.1) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.19.1) - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.19.1) - '@babel/plugin-syntax-import-assertions': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.19.1) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.19.1) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.19.1) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.19.1) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.19.1) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.19.1) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.19.1) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.19.1) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.19.1) - '@babel/plugin-transform-arrow-functions': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-async-to-generator': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-block-scoped-functions': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-block-scoping': 7.18.9(@babel/core@7.19.1) - '@babel/plugin-transform-classes': 7.19.0(@babel/core@7.19.1) - '@babel/plugin-transform-computed-properties': 7.18.9(@babel/core@7.19.1) - '@babel/plugin-transform-destructuring': 7.18.13(@babel/core@7.19.1) - '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-duplicate-keys': 7.18.9(@babel/core@7.19.1) - '@babel/plugin-transform-exponentiation-operator': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-for-of': 7.18.8(@babel/core@7.19.1) - '@babel/plugin-transform-function-name': 7.18.9(@babel/core@7.19.1) - '@babel/plugin-transform-literals': 7.18.9(@babel/core@7.19.1) - '@babel/plugin-transform-member-expression-literals': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-modules-amd': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-modules-commonjs': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-modules-systemjs': 7.19.0(@babel/core@7.19.1) - '@babel/plugin-transform-modules-umd': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-named-capturing-groups-regex': 7.19.1(@babel/core@7.19.1) - '@babel/plugin-transform-new-target': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-object-super': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-parameters': 7.18.8(@babel/core@7.19.1) - '@babel/plugin-transform-property-literals': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-regenerator': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-reserved-words': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-shorthand-properties': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-spread': 7.19.0(@babel/core@7.19.1) - '@babel/plugin-transform-sticky-regex': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-template-literals': 7.18.9(@babel/core@7.19.1) - '@babel/plugin-transform-typeof-symbol': 7.18.9(@babel/core@7.19.1) - '@babel/plugin-transform-unicode-escapes': 7.18.10(@babel/core@7.19.1) - '@babel/plugin-transform-unicode-regex': 7.18.6(@babel/core@7.19.1) - '@babel/preset-modules': 0.1.5(@babel/core@7.19.1) - '@babel/types': 7.19.0 - babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.19.1) - babel-plugin-polyfill-corejs3: 0.6.0(@babel/core@7.19.1) - babel-plugin-polyfill-regenerator: 0.4.1(@babel/core@7.19.1) - core-js-compat: 3.25.2 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - dev: false + postcss: 8.5.3 + postcss-value-parser: 4.2.0 - /@babel/preset-env@7.22.14(@babel/core@7.22.11): - resolution: {integrity: sha512-daodMIoVo+ol/g+//c/AH+szBkFj4STQUikvBijRGL72Ph+w+AMTSh55DUETe8KJlPlDT1k/mp7NBfOuiWmoig==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-logical-viewport-units@3.0.3(postcss@8.5.3)': dependencies: - '@babel/compat-data': 7.22.9 - '@babel/core': 7.22.11 - '@babel/helper-compilation-targets': 7.22.10 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-validator-option': 7.22.5 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.22.11) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.11) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.22.11) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.22.11) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.22.11) - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.22.11) - '@babel/plugin-syntax-import-assertions': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-syntax-import-attributes': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.22.11) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.11) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.11) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.11) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.11) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.11) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.11) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.11) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.22.11) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.22.11) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.22.11) - '@babel/plugin-transform-arrow-functions': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-async-generator-functions': 7.22.11(@babel/core@7.22.11) - '@babel/plugin-transform-async-to-generator': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-block-scoped-functions': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-block-scoping': 7.22.10(@babel/core@7.22.11) - '@babel/plugin-transform-class-properties': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-class-static-block': 7.22.11(@babel/core@7.22.11) - '@babel/plugin-transform-classes': 7.22.6(@babel/core@7.22.11) - '@babel/plugin-transform-computed-properties': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-destructuring': 7.22.10(@babel/core@7.22.11) - '@babel/plugin-transform-dotall-regex': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-duplicate-keys': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-dynamic-import': 7.22.11(@babel/core@7.22.11) - '@babel/plugin-transform-exponentiation-operator': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-export-namespace-from': 7.22.11(@babel/core@7.22.11) - '@babel/plugin-transform-for-of': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-function-name': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-json-strings': 7.22.11(@babel/core@7.22.11) - '@babel/plugin-transform-literals': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-logical-assignment-operators': 7.22.11(@babel/core@7.22.11) - '@babel/plugin-transform-member-expression-literals': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-modules-amd': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-modules-commonjs': 7.22.11(@babel/core@7.22.11) - '@babel/plugin-transform-modules-systemjs': 7.22.11(@babel/core@7.22.11) - '@babel/plugin-transform-modules-umd': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-new-target': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-nullish-coalescing-operator': 7.22.11(@babel/core@7.22.11) - '@babel/plugin-transform-numeric-separator': 7.22.11(@babel/core@7.22.11) - '@babel/plugin-transform-object-rest-spread': 7.22.11(@babel/core@7.22.11) - '@babel/plugin-transform-object-super': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-optional-catch-binding': 7.22.11(@babel/core@7.22.11) - '@babel/plugin-transform-optional-chaining': 7.22.12(@babel/core@7.22.11) - '@babel/plugin-transform-parameters': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-private-methods': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-private-property-in-object': 7.22.11(@babel/core@7.22.11) - '@babel/plugin-transform-property-literals': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-regenerator': 7.22.10(@babel/core@7.22.11) - '@babel/plugin-transform-reserved-words': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-shorthand-properties': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-spread': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-sticky-regex': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-template-literals': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-typeof-symbol': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-unicode-escapes': 7.22.10(@babel/core@7.22.11) - '@babel/plugin-transform-unicode-property-regex': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-unicode-regex': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-unicode-sets-regex': 7.22.5(@babel/core@7.22.11) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.22.11) - '@babel/types': 7.22.11 - babel-plugin-polyfill-corejs2: 0.4.5(@babel/core@7.22.11) - babel-plugin-polyfill-corejs3: 0.8.3(@babel/core@7.22.11) - babel-plugin-polyfill-regenerator: 0.5.2(@babel/core@7.22.11) - core-js-compat: 3.32.1 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color + '@csstools/css-tokenizer': 3.0.3 + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 - /@babel/preset-modules@0.1.5(@babel/core@7.19.1): - resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-media-minmax@2.0.7(postcss@8.5.3)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.19.1) - '@babel/types': 7.19.0 - esutils: 2.0.3 - dev: false + '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + postcss: 8.5.3 - /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.22.11): - resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} - peerDependencies: - '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.4(postcss@8.5.3)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/types': 7.22.11 - esutils: 2.0.3 + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + postcss: 8.5.3 - /@babel/preset-react@7.18.6(@babel/core@7.19.1): - resolution: {integrity: sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-nested-calc@4.0.0(postcss@8.5.3)': dependencies: - '@babel/core': 7.19.1 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/helper-validator-option': 7.18.6 - '@babel/plugin-transform-react-display-name': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-react-jsx': 7.19.0(@babel/core@7.19.1) - '@babel/plugin-transform-react-jsx-development': 7.18.6(@babel/core@7.19.1) - '@babel/plugin-transform-react-pure-annotations': 7.18.6(@babel/core@7.19.1) - dev: false - - /@babel/preset-react@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-normalize-display-values@4.0.0(postcss@8.5.3)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-validator-option': 7.22.5 - '@babel/plugin-transform-react-display-name': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-react-jsx': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-react-jsx-development': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-react-pure-annotations': 7.22.5(@babel/core@7.22.11) + postcss: 8.5.3 + postcss-value-parser: 4.2.0 - /@babel/preset-typescript@7.22.11(@babel/core@7.22.11): - resolution: {integrity: sha512-tWY5wyCZYBGY7IlalfKI1rLiGlIfnwsRHZqlky0HVv8qviwQ1Uo/05M6+s+TcTCVa6Bmoo2uJW5TMFX6Wa4qVg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/postcss-oklab-function@4.0.8(postcss@8.5.3)': dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-validator-option': 7.22.5 - '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-modules-commonjs': 7.22.11(@babel/core@7.22.11) - '@babel/plugin-transform-typescript': 7.22.11(@babel/core@7.22.11) + '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3) + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 - /@babel/regjsgen@0.8.0: - resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} + '@csstools/postcss-progressive-custom-properties@4.0.0(postcss@8.5.3)': + dependencies: + postcss: 8.5.3 + postcss-value-parser: 4.2.0 - /@babel/runtime-corejs3@7.22.11: - resolution: {integrity: sha512-NhfzUbdWbiE6fCFypbWCPu6AR8xre31EOPF7wwAIJEvGQ2avov04eymayWinCuyXmV1b0+jzoXP/HYzzUYdvwg==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-random-function@1.0.3(postcss@8.5.3)': dependencies: - core-js-pure: 3.32.1 - regenerator-runtime: 0.14.0 + '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + postcss: 8.5.3 - /@babel/runtime@7.19.0: - resolution: {integrity: sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-relative-color-syntax@3.0.8(postcss@8.5.3)': dependencies: - regenerator-runtime: 0.13.9 + '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3) + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 - /@babel/runtime@7.22.11: - resolution: {integrity: sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.3)': dependencies: - regenerator-runtime: 0.14.0 + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 - /@babel/template@7.18.10: - resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-sign-functions@1.1.2(postcss@8.5.3)': dependencies: - '@babel/code-frame': 7.18.6 - '@babel/parser': 7.19.1 - '@babel/types': 7.19.0 + '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + postcss: 8.5.3 - /@babel/template@7.22.5: - resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-stepped-value-functions@4.0.7(postcss@8.5.3)': dependencies: - '@babel/code-frame': 7.22.13 - '@babel/parser': 7.22.14 - '@babel/types': 7.22.11 + '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + postcss: 8.5.3 - /@babel/traverse@7.19.1: - resolution: {integrity: sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-text-decoration-shorthand@4.0.2(postcss@8.5.3)': dependencies: - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.19.0 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.19.1 - '@babel/types': 7.19.0 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color + '@csstools/color-helpers': 5.0.2 + postcss: 8.5.3 + postcss-value-parser: 4.2.0 - /@babel/traverse@7.22.11: - resolution: {integrity: sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-trigonometric-functions@4.0.7(postcss@8.5.3)': dependencies: - '@babel/code-frame': 7.22.13 - '@babel/generator': 7.22.10 - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-function-name': 7.22.5 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.22.14 - '@babel/types': 7.22.11 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color + '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + postcss: 8.5.3 - /@babel/types@7.19.0: - resolution: {integrity: sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==} - engines: {node: '>=6.9.0'} + '@csstools/postcss-unset-value@4.0.0(postcss@8.5.3)': dependencies: - '@babel/helper-string-parser': 7.18.10 - '@babel/helper-validator-identifier': 7.19.1 - to-fast-properties: 2.0.0 + postcss: 8.5.3 - /@babel/types@7.22.11: - resolution: {integrity: sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==} - engines: {node: '>=6.9.0'} + '@csstools/selector-resolve-nested@3.0.0(postcss-selector-parser@7.1.0)': dependencies: - '@babel/helper-string-parser': 7.22.5 - '@babel/helper-validator-identifier': 7.22.5 - to-fast-properties: 2.0.0 + postcss-selector-parser: 7.1.0 - /@colors/colors@1.5.0: - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} - engines: {node: '>=0.1.90'} - requiresBuild: true - optional: true + '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.0)': + dependencies: + postcss-selector-parser: 7.1.0 - /@ctrl/tinycolor@3.4.0: - resolution: {integrity: sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==} - engines: {node: '>=10'} - dev: false + '@csstools/utilities@2.0.0(postcss@8.5.3)': + dependencies: + postcss: 8.5.3 - /@discoveryjs/json-ext@0.5.7: - resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} - engines: {node: '>=10.0.0'} + '@discoveryjs/json-ext@0.5.7': {} - /@docsearch/css@3.5.2: - resolution: {integrity: sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==} - dev: false + '@docsearch/css@3.9.0': {} - /@docsearch/react@3.5.2(@algolia/client-search@4.19.1)(react-dom@17.0.2)(react@17.0.2)(search-insights@2.8.1): - resolution: {integrity: sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==} - peerDependencies: - '@types/react': '>= 16.8.0 < 19.0.0' - react: '>= 16.8.0 < 19.0.0' - react-dom: '>= 16.8.0 < 19.0.0' - search-insights: '>= 1 < 3' - peerDependenciesMeta: - '@types/react': - optional: true - react: - optional: true - react-dom: - optional: true - search-insights: - optional: true + '@docsearch/react@3.9.0(@algolia/client-search@5.21.0)(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.19.1)(algoliasearch@4.19.1)(search-insights@2.8.1) - '@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.19.1)(algoliasearch@4.19.1) - '@docsearch/css': 3.5.2 - algoliasearch: 4.19.1 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - search-insights: 2.8.1 + '@algolia/autocomplete-core': 1.17.9(@algolia/client-search@5.21.0)(algoliasearch@5.21.0)(search-insights@2.17.3) + '@algolia/autocomplete-preset-algolia': 1.17.9(@algolia/client-search@5.21.0)(algoliasearch@5.21.0) + '@docsearch/css': 3.9.0 + algoliasearch: 5.21.0 + optionalDependencies: + '@types/react': 18.3.18 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' - dev: false - /@docusaurus/core@2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5): - resolution: {integrity: sha512-SNsY7PshK3Ri7vtsLXVeAJGS50nJN3RgF836zkyUfAD01Fq+sAk5EwWgLw+nnm5KVNGDu7PRR2kRGDsWvqpo0g==} - engines: {node: '>=16.14'} - hasBin: true - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 - dependencies: - '@babel/core': 7.22.11 - '@babel/generator': 7.22.10 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.22.11) - '@babel/plugin-transform-runtime': 7.22.10(@babel/core@7.22.11) - '@babel/preset-env': 7.22.14(@babel/core@7.22.11) - '@babel/preset-react': 7.22.5(@babel/core@7.22.11) - '@babel/preset-typescript': 7.22.11(@babel/core@7.22.11) - '@babel/runtime': 7.22.11 - '@babel/runtime-corejs3': 7.22.11 - '@babel/traverse': 7.22.11 - '@docusaurus/cssnano-preset': 2.4.1 - '@docusaurus/logger': 2.4.1 - '@docusaurus/mdx-loader': 2.4.1(@docusaurus/types@2.4.1)(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/react-loadable': 5.5.2(react@17.0.2) - '@docusaurus/utils': 2.4.1(@docusaurus/types@2.4.1) - '@docusaurus/utils-common': 2.4.1(@docusaurus/types@2.4.1) - '@docusaurus/utils-validation': 2.4.1(@docusaurus/types@2.4.1) - '@slorber/static-site-generator-webpack-plugin': 4.0.7 - '@svgr/webpack': 6.5.1 - autoprefixer: 10.4.15(postcss@8.4.29) - babel-loader: 8.3.0(@babel/core@7.22.11)(webpack@5.88.2) + '@docusaurus/babel@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/core': 7.26.10 + '@babel/generator': 7.26.10 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-transform-runtime': 7.26.10(@babel/core@7.26.10) + '@babel/preset-env': 7.26.9(@babel/core@7.26.10) + '@babel/preset-react': 7.26.3(@babel/core@7.26.10) + '@babel/preset-typescript': 7.26.0(@babel/core@7.26.10) + '@babel/runtime': 7.26.10 + '@babel/runtime-corejs3': 7.26.10 + '@babel/traverse': 7.26.10 + '@docusaurus/logger': 3.7.0 + '@docusaurus/utils': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) babel-plugin-dynamic-import-node: 2.3.3 + fs-extra: 11.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - '@swc/core' + - acorn + - esbuild + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/bundler@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)': + dependencies: + '@babel/core': 7.26.10 + '@docusaurus/babel': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/cssnano-preset': 3.7.0 + '@docusaurus/logger': 3.7.0 + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + babel-loader: 9.2.1(@babel/core@7.26.10)(webpack@5.98.0(@swc/core@1.11.9)) + clean-css: 5.3.3 + copy-webpack-plugin: 11.0.0(webpack@5.98.0(@swc/core@1.11.9)) + css-loader: 6.11.0(webpack@5.98.0(@swc/core@1.11.9)) + css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.98.0(@swc/core@1.11.9)) + cssnano: 6.1.2(postcss@8.5.3) + file-loader: 6.2.0(webpack@5.98.0(@swc/core@1.11.9)) + html-minifier-terser: 7.2.0 + mini-css-extract-plugin: 2.9.2(webpack@5.98.0(@swc/core@1.11.9)) + null-loader: 4.0.1(webpack@5.98.0(@swc/core@1.11.9)) + postcss: 8.5.3 + postcss-loader: 7.3.4(postcss@8.5.3)(typescript@5.8.2)(webpack@5.98.0(@swc/core@1.11.9)) + postcss-preset-env: 10.1.5(postcss@8.5.3) + react-dev-utils: 12.0.1(eslint@7.32.0)(typescript@5.8.2)(webpack@5.98.0(@swc/core@1.11.9)) + terser-webpack-plugin: 5.3.14(@swc/core@1.11.9)(webpack@5.98.0(@swc/core@1.11.9)) + tslib: 2.8.1 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.98.0(@swc/core@1.11.9)))(webpack@5.98.0(@swc/core@1.11.9)) + webpack: 5.98.0(@swc/core@1.11.9) + webpackbar: 6.0.1(webpack@5.98.0(@swc/core@1.11.9)) + optionalDependencies: + '@docusaurus/faster': 3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + transitivePeerDependencies: + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - acorn + - csso + - esbuild + - eslint + - lightningcss + - react + - react-dom + - supports-color + - typescript + - uglify-js + - vue-template-compiler + - webpack-cli + + '@docusaurus/core@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)': + dependencies: + '@docusaurus/babel': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/bundler': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mdx-js/react': 3.1.0(@types/react@18.3.18)(react@18.3.1) boxen: 6.2.1 chalk: 4.1.2 - chokidar: 3.5.3 - clean-css: 5.3.2 - cli-table3: 0.6.3 + chokidar: 3.6.0 + cli-table3: 0.6.5 combine-promises: 1.2.0 commander: 5.1.0 - copy-webpack-plugin: 11.0.0(webpack@5.88.2) - core-js: 3.32.1 - css-loader: 6.8.1(webpack@5.88.2) - css-minimizer-webpack-plugin: 4.2.2(clean-css@5.3.2)(webpack@5.88.2) - cssnano: 5.1.15(postcss@8.4.29) + core-js: 3.41.0 del: 6.1.1 - detect-port: 1.5.1 + detect-port: 1.6.1 escape-html: 1.0.3 eta: 2.2.0 - file-loader: 6.2.0(webpack@5.88.2) - fs-extra: 10.1.0 - html-minifier-terser: 6.1.0 + eval: 0.1.8 + fs-extra: 11.3.0 html-tags: 3.3.1 - html-webpack-plugin: 5.5.3(webpack@5.88.2) - import-fresh: 3.3.0 + html-webpack-plugin: 5.6.3(webpack@5.98.0(@swc/core@1.11.9)) leven: 3.1.0 lodash: 4.17.21 - mini-css-extract-plugin: 2.7.6(webpack@5.88.2) - postcss: 8.4.29 - postcss-loader: 7.3.3(postcss@8.4.29)(typescript@4.9.5)(webpack@5.88.2) + p-map: 4.0.0 prompts: 2.4.2 - react: 17.0.2 - react-dev-utils: 12.0.1(eslint@7.32.0)(typescript@4.9.5)(webpack@5.88.2) - react-dom: 17.0.2(react@17.0.2) - react-helmet-async: 1.3.0(react-dom@17.0.2)(react@17.0.2) - react-loadable: /@docusaurus/react-loadable@5.5.2(react@17.0.2) - react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@5.5.2)(webpack@5.88.2) - react-router: 5.3.4(react@17.0.2) - react-router-config: 5.1.1(react-router@5.3.4)(react@17.0.2) - react-router-dom: 5.3.4(react@17.0.2) - rtl-detect: 1.0.4 - semver: 7.5.4 - serve-handler: 6.1.5 + react: 18.3.1 + react-dev-utils: 12.0.1(eslint@7.32.0)(typescript@5.8.2)(webpack@5.98.0(@swc/core@1.11.9)) + react-dom: 18.3.1(react@18.3.1) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.3.1)' + react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@6.0.0(react@18.3.1))(webpack@5.98.0(@swc/core@1.11.9)) + react-router: 5.3.4(react@18.3.1) + react-router-config: 5.1.1(react-router@5.3.4(react@18.3.1))(react@18.3.1) + react-router-dom: 5.3.4(react@18.3.1) + semver: 7.7.1 + serve-handler: 6.1.6 shelljs: 0.8.5 - terser-webpack-plugin: 5.3.9(webpack@5.88.2) - tslib: 2.6.2 - update-notifier: 5.1.0 - url-loader: 4.1.1(file-loader@6.2.0)(webpack@5.88.2) - wait-on: 6.0.1 - webpack: 5.88.2 - webpack-bundle-analyzer: 4.9.1 - webpack-dev-server: 4.15.1(webpack@5.88.2) - webpack-merge: 5.9.0 - webpackbar: 5.0.2(webpack@5.88.2) - transitivePeerDependencies: - - '@docusaurus/types' + tslib: 2.8.1 + update-notifier: 6.0.2 + webpack: 5.98.0(@swc/core@1.11.9) + webpack-bundle-analyzer: 4.10.2 + webpack-dev-server: 4.15.2(webpack@5.98.0(@swc/core@1.11.9)) + webpack-merge: 6.0.1 + transitivePeerDependencies: + - '@docusaurus/faster' - '@parcel/css' + - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -2979,114 +8353,122 @@ packages: - vue-template-compiler - webpack-cli - /@docusaurus/cssnano-preset@2.4.1: - resolution: {integrity: sha512-ka+vqXwtcW1NbXxWsh6yA1Ckii1klY9E53cJ4O9J09nkMBgrNX3iEFED1fWdv8wf4mJjvGi5RLZ2p9hJNjsLyQ==} - engines: {node: '>=16.14'} + '@docusaurus/cssnano-preset@3.7.0': + dependencies: + cssnano-preset-advanced: 6.1.2(postcss@8.5.3) + postcss: 8.5.3 + postcss-sort-media-queries: 5.2.0(postcss@8.5.3) + tslib: 2.8.1 + + '@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': dependencies: - cssnano-preset-advanced: 5.3.10(postcss@8.4.29) - postcss: 8.4.29 - postcss-sort-media-queries: 4.4.1(postcss@8.4.29) - tslib: 2.6.2 + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rspack/core': 1.2.0-alpha.0 + '@swc/core': 1.11.9 + '@swc/html': 1.11.9 + browserslist: 4.24.4 + lightningcss: 1.29.3 + swc-loader: 0.2.6(@swc/core@1.11.9)(webpack@5.98.0(@swc/core@1.11.9)) + tslib: 2.8.1 + webpack: 5.98.0(@swc/core@1.11.9) + transitivePeerDependencies: + - '@swc/helpers' + - esbuild + - uglify-js + - webpack-cli - /@docusaurus/logger@2.4.1: - resolution: {integrity: sha512-5h5ysIIWYIDHyTVd8BjheZmQZmEgWDR54aQ1BX9pjFfpyzFo5puKXKYrYJXbjEHGyVhEzmB9UXwbxGfaZhOjcg==} - engines: {node: '>=16.14'} + '@docusaurus/logger@3.7.0': dependencies: chalk: 4.1.2 - tslib: 2.6.2 + tslib: 2.8.1 - /@docusaurus/lqip-loader@2.4.1(webpack@5.88.2): - resolution: {integrity: sha512-XJ0z/xSx5HtAQ+/xBoAiRZ7DY9zEP6IImAKlAk6RxuFzyB4HT8eINWN+LwLnOsTh5boIj37JCX+T76bH0ieULA==} - engines: {node: '>=16.14'} + '@docusaurus/lqip-loader@3.7.0(webpack@5.98.0(@swc/core@1.11.9))': dependencies: - '@docusaurus/logger': 2.4.1 - file-loader: 6.2.0(webpack@5.88.2) + '@docusaurus/logger': 3.7.0 + file-loader: 6.2.0(webpack@5.98.0(@swc/core@1.11.9)) lodash: 4.17.21 - sharp: 0.30.7 - tslib: 2.6.2 + sharp: 0.32.6 + tslib: 2.8.1 transitivePeerDependencies: + - bare-buffer - webpack - dev: false - /@docusaurus/mdx-loader@2.4.1(@docusaurus/types@2.4.1)(react-dom@17.0.2)(react@17.0.2): - resolution: {integrity: sha512-4KhUhEavteIAmbBj7LVFnrVYDiU51H5YWW1zY6SmBSte/YLhDutztLTBE0PQl1Grux1jzUJeaSvAzHpTn6JJDQ==} - engines: {node: '>=16.14'} - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 + '@docusaurus/mdx-loader@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/parser': 7.22.14 - '@babel/traverse': 7.22.11 - '@docusaurus/logger': 2.4.1 - '@docusaurus/utils': 2.4.1(@docusaurus/types@2.4.1) - '@mdx-js/mdx': 1.6.22 + '@docusaurus/logger': 3.7.0 + '@docusaurus/utils': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mdx-js/mdx': 3.1.0(acorn@7.4.1) + '@slorber/remark-comment': 1.0.0 escape-html: 1.0.3 - file-loader: 6.2.0(webpack@5.88.2) - fs-extra: 10.1.0 - image-size: 1.0.2 - mdast-util-to-string: 2.0.0 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - remark-emoji: 2.2.0 + estree-util-value-to-estree: 3.3.2 + file-loader: 6.2.0(webpack@5.98.0(@swc/core@1.11.9)) + fs-extra: 11.3.0 + image-size: 1.2.0 + mdast-util-mdx: 3.0.0 + mdast-util-to-string: 4.0.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + rehype-raw: 7.0.0 + remark-directive: 3.0.1 + remark-emoji: 4.0.1 + remark-frontmatter: 5.0.0 + remark-gfm: 4.0.1 stringify-object: 3.3.0 - tslib: 2.6.2 - unified: 9.2.2 - unist-util-visit: 2.0.3 - url-loader: 4.1.1(file-loader@6.2.0)(webpack@5.88.2) - webpack: 5.88.2 + tslib: 2.8.1 + unified: 11.0.5 + unist-util-visit: 5.0.0 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.98.0(@swc/core@1.11.9)))(webpack@5.98.0(@swc/core@1.11.9)) + vfile: 6.0.3 + webpack: 5.98.0(@swc/core@1.11.9) transitivePeerDependencies: - - '@docusaurus/types' - '@swc/core' + - acorn - esbuild - supports-color - uglify-js - webpack-cli - /@docusaurus/module-type-aliases@2.4.1(react-dom@17.0.2)(react@17.0.2): - resolution: {integrity: sha512-gLBuIFM8Dp2XOCWffUDSjtxY7jQgKvYujt7Mx5s4FCTfoL5dN1EVbnrn+O2Wvh8b0a77D57qoIDY7ghgmatR1A==} - peerDependencies: - react: '*' - react-dom: '*' + '@docusaurus/module-type-aliases@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/react-loadable': 5.5.2(react@17.0.2) - '@docusaurus/types': 2.4.1(react-dom@17.0.2)(react@17.0.2) + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/history': 4.7.11 - '@types/react': 18.2.21 - '@types/react-router-config': 5.0.7 + '@types/react': 18.3.18 + '@types/react-router-config': 5.0.11 '@types/react-router-dom': 5.3.3 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - react-helmet-async: 1.3.0(react-dom@17.0.2)(react@17.0.2) - react-loadable: /@docusaurus/react-loadable@5.5.2(react@17.0.2) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.3.1)' transitivePeerDependencies: - '@swc/core' + - acorn - esbuild + - supports-color - uglify-js - webpack-cli - /@docusaurus/plugin-client-redirects@2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5): - resolution: {integrity: sha512-tp0j16gaLIJ4p+IR0P6KDOFsTOGGMY54MNPnmM61Vaqqt5omLqsuKUO8UlCGU1oW/4EIQOhXYy99XYY5MjE+7A==} - engines: {node: '>=16.14'} - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 + '@docusaurus/plugin-client-redirects@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/logger': 2.4.1 - '@docusaurus/utils': 2.4.1(@docusaurus/types@2.4.1) - '@docusaurus/utils-common': 2.4.1(@docusaurus/types@2.4.1) - '@docusaurus/utils-validation': 2.4.1(@docusaurus/types@2.4.1) + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/utils': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) eta: 2.2.0 - fs-extra: 10.1.0 + fs-extra: 11.3.0 lodash: 4.17.21 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - tslib: 2.6.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.8.1 transitivePeerDependencies: - - '@docusaurus/types' + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' + - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -3099,37 +8481,38 @@ packages: - utf-8-validate - vue-template-compiler - webpack-cli - dev: false - - /@docusaurus/plugin-content-blog@2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5): - resolution: {integrity: sha512-E2i7Knz5YIbE1XELI6RlTnZnGgS52cUO4BlCiCUCvQHbR+s1xeIWz4C6BtaVnlug0Ccz7nFSksfwDpVlkujg5Q==} - engines: {node: '>=16.14'} - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 - dependencies: - '@docusaurus/core': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/logger': 2.4.1 - '@docusaurus/mdx-loader': 2.4.1(@docusaurus/types@2.4.1)(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/types': 2.4.1(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/utils': 2.4.1(@docusaurus/types@2.4.1) - '@docusaurus/utils-common': 2.4.1(@docusaurus/types@2.4.1) - '@docusaurus/utils-validation': 2.4.1(@docusaurus/types@2.4.1) + + '@docusaurus/plugin-content-blog@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)': + dependencies: + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-docs': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2))(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) cheerio: 1.0.0-rc.12 feed: 4.2.2 - fs-extra: 10.1.0 + fs-extra: 11.3.0 lodash: 4.17.21 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) reading-time: 1.5.0 - tslib: 2.6.2 - unist-util-visit: 2.0.3 - utility-types: 3.10.0 - webpack: 5.88.2 + srcset: 4.0.0 + tslib: 2.8.1 + unist-util-visit: 5.0.0 + utility-types: 3.11.0 + webpack: 5.98.0(@swc/core@1.11.9) transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' + - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -3142,37 +8525,36 @@ packages: - utf-8-validate - vue-template-compiler - webpack-cli - dev: false - - /@docusaurus/plugin-content-docs@2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5): - resolution: {integrity: sha512-Lo7lSIcpswa2Kv4HEeUcGYqaasMUQNpjTXpV0N8G6jXgZaQurqp7E8NGYeGbDXnb48czmHWbzDL4S3+BbK0VzA==} - engines: {node: '>=16.14'} - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 - dependencies: - '@docusaurus/core': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/logger': 2.4.1 - '@docusaurus/mdx-loader': 2.4.1(@docusaurus/types@2.4.1)(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/module-type-aliases': 2.4.1(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/types': 2.4.1(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/utils': 2.4.1(@docusaurus/types@2.4.1) - '@docusaurus/utils-validation': 2.4.1(@docusaurus/types@2.4.1) - '@types/react-router-config': 5.0.7 + + '@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)': + dependencies: + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/module-type-aliases': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2))(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/react-router-config': 5.0.11 combine-promises: 1.2.0 - fs-extra: 10.1.0 - import-fresh: 3.3.0 + fs-extra: 11.3.0 js-yaml: 4.1.0 lodash: 4.17.21 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - tslib: 2.6.2 - utility-types: 3.10.0 - webpack: 5.88.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.8.1 + utility-types: 3.11.0 + webpack: 5.98.0(@swc/core@1.11.9) transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' + - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -3185,29 +8567,27 @@ packages: - utf-8-validate - vue-template-compiler - webpack-cli - dev: false - - /@docusaurus/plugin-content-pages@2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5): - resolution: {integrity: sha512-/UjuH/76KLaUlL+o1OvyORynv6FURzjurSjvn2lbWTFc4tpYY2qLYTlKpTCBVPhlLUQsfyFnshEJDLmPneq2oA==} - engines: {node: '>=16.14'} - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 - dependencies: - '@docusaurus/core': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/mdx-loader': 2.4.1(@docusaurus/types@2.4.1)(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/types': 2.4.1(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/utils': 2.4.1(@docusaurus/types@2.4.1) - '@docusaurus/utils-validation': 2.4.1(@docusaurus/types@2.4.1) - fs-extra: 10.1.0 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - tslib: 2.6.2 - webpack: 5.88.2 + + '@docusaurus/plugin-content-pages@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)': + dependencies: + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + fs-extra: 11.3.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.8.1 + webpack: 5.98.0(@swc/core@1.11.9) transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' + - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -3220,32 +8600,28 @@ packages: - utf-8-validate - vue-template-compiler - webpack-cli - dev: false - /@docusaurus/plugin-debug@2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5): - resolution: {integrity: sha512-7Yu9UPzRShlrH/G8btOpR0e6INFZr0EegWplMjOqelIwAcx3PKyR8mgPTxGTxcqiYj6hxSCRN0D8R7YrzImwNA==} - engines: {node: '>=16.14'} - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 + '@docusaurus/plugin-debug@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/types': 2.4.1(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/utils': 2.4.1(@docusaurus/types@2.4.1) - fs-extra: 10.1.0 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - react-json-view: 1.21.3(react-dom@17.0.2)(react@17.0.2) - tslib: 2.6.2 + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + fs-extra: 11.3.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-json-view-lite: 1.5.0(react@18.3.1) + tslib: 2.8.1 transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' + - '@rspack/core' - '@swc/core' - '@swc/css' - - '@types/react' + - acorn - bufferutil - csso - debug - - encoding - esbuild - eslint - lightningcss @@ -3255,25 +8631,23 @@ packages: - utf-8-validate - vue-template-compiler - webpack-cli - dev: false - /@docusaurus/plugin-google-analytics@2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5): - resolution: {integrity: sha512-dyZJdJiCoL+rcfnm0RPkLt/o732HvLiEwmtoNzOoz9MSZz117UH2J6U2vUDtzUzwtFLIf32KkeyzisbwUCgcaQ==} - engines: {node: '>=16.14'} - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 + '@docusaurus/plugin-google-analytics@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/types': 2.4.1(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/utils-validation': 2.4.1(@docusaurus/types@2.4.1) - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - tslib: 2.6.2 + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.8.1 transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' + - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -3286,25 +8660,24 @@ packages: - utf-8-validate - vue-template-compiler - webpack-cli - dev: false - /@docusaurus/plugin-google-gtag@2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5): - resolution: {integrity: sha512-mKIefK+2kGTQBYvloNEKtDmnRD7bxHLsBcxgnbt4oZwzi2nxCGjPX6+9SQO2KCN5HZbNrYmGo5GJfMgoRvy6uA==} - engines: {node: '>=16.14'} - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 + '@docusaurus/plugin-google-gtag@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/types': 2.4.1(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/utils-validation': 2.4.1(@docusaurus/types@2.4.1) - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - tslib: 2.6.2 + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/gtag.js': 0.0.12 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.8.1 transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' + - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -3317,25 +8690,23 @@ packages: - utf-8-validate - vue-template-compiler - webpack-cli - dev: false - /@docusaurus/plugin-google-tag-manager@2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5): - resolution: {integrity: sha512-Zg4Ii9CMOLfpeV2nG74lVTWNtisFaH9QNtEw48R5QE1KIwDBdTVaiSA18G1EujZjrzJJzXN79VhINSbOJO/r3g==} - engines: {node: '>=16.14'} - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 + '@docusaurus/plugin-google-tag-manager@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/types': 2.4.1(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/utils-validation': 2.4.1(@docusaurus/types@2.4.1) - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - tslib: 2.6.2 + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.8.1 transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' + - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -3348,73 +8719,98 @@ packages: - utf-8-validate - vue-template-compiler - webpack-cli - dev: false - /@docusaurus/plugin-ideal-image@2.4.1(eslint@7.32.0)(prop-types@15.8.1)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5): - resolution: {integrity: sha512-jxvgCGPmHxdae2Y2uskzxIbMCA4WLTfzkufsLbD4mEAjCRIkt6yzux6q5kqKTrO+AxzpANVcJNGmaBtKZGv5aw==} - engines: {node: '>=16.14'} - peerDependencies: - jimp: '*' - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 - peerDependenciesMeta: - jimp: - optional: true - dependencies: - '@docusaurus/core': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/lqip-loader': 2.4.1(webpack@5.88.2) - '@docusaurus/responsive-loader': 1.7.0(sharp@0.30.7) - '@docusaurus/theme-translations': 2.4.1 - '@docusaurus/types': 2.4.1(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/utils-validation': 2.4.1(@docusaurus/types@2.4.1) - '@endiliey/react-ideal-image': 0.0.11(prop-types@15.8.1)(react-waypoint@10.3.0)(react@17.0.2) - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - react-waypoint: 10.3.0(react@17.0.2) - sharp: 0.30.7 - tslib: 2.6.2 - webpack: 5.88.2 + '@docusaurus/plugin-ideal-image@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)': + dependencies: + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/lqip-loader': 3.7.0(webpack@5.98.0(@swc/core@1.11.9)) + '@docusaurus/responsive-loader': 1.7.1(sharp@0.32.6) + '@docusaurus/theme-translations': 3.7.0 + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@slorber/react-ideal-image': 0.0.14(react-waypoint@10.3.0(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-waypoint: 10.3.0(react@18.3.1) + sharp: 0.32.6 + tslib: 2.8.1 + webpack: 5.98.0(@swc/core@1.11.9) + transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - acorn + - bare-buffer + - bufferutil + - csso + - debug + - esbuild + - eslint + - lightningcss + - supports-color + - typescript + - uglify-js + - utf-8-validate + - vue-template-compiler + - webpack-cli + + '@docusaurus/plugin-sitemap@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)': + dependencies: + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + fs-extra: 11.3.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + sitemap: 7.1.2 + tslib: 2.8.1 transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' + - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug - esbuild - eslint - lightningcss - - prop-types - supports-color - typescript - uglify-js - utf-8-validate - vue-template-compiler - webpack-cli - dev: false - - /@docusaurus/plugin-sitemap@2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5): - resolution: {integrity: sha512-lZx+ijt/+atQ3FVE8FOHV/+X3kuok688OydDXrqKRJyXBJZKgGjA2Qa8RjQ4f27V2woaXhtnyrdPop/+OjVMRg==} - engines: {node: '>=16.14'} - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 - dependencies: - '@docusaurus/core': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/logger': 2.4.1 - '@docusaurus/types': 2.4.1(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/utils': 2.4.1(@docusaurus/types@2.4.1) - '@docusaurus/utils-common': 2.4.1(@docusaurus/types@2.4.1) - '@docusaurus/utils-validation': 2.4.1(@docusaurus/types@2.4.1) - fs-extra: 10.1.0 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - sitemap: 7.1.1 - tslib: 2.6.2 + + '@docusaurus/plugin-svgr@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)': + dependencies: + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@svgr/core': 8.1.0(typescript@5.8.2) + '@svgr/webpack': 8.1.0(typescript@5.8.2) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.8.1 + webpack: 5.98.0(@swc/core@1.11.9) transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' + - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -3427,40 +8823,38 @@ packages: - utf-8-validate - vue-template-compiler - webpack-cli - dev: false - - /@docusaurus/preset-classic@2.4.1(@algolia/client-search@4.19.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(search-insights@2.8.1)(typescript@4.9.5): - resolution: {integrity: sha512-P4//+I4zDqQJ+UDgoFrjIFaQ1MeS9UD1cvxVQaI6O7iBmiHQm0MGROP1TbE7HlxlDPXFJjZUK3x3cAoK63smGQ==} - engines: {node: '>=16.14'} - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 - dependencies: - '@docusaurus/core': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/plugin-content-blog': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/plugin-content-docs': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/plugin-content-pages': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/plugin-debug': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/plugin-google-analytics': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/plugin-google-gtag': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/plugin-google-tag-manager': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/plugin-sitemap': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/theme-classic': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/theme-common': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/theme-search-algolia': 2.4.1(@algolia/client-search@4.19.1)(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(search-insights@2.8.1)(typescript@4.9.5) - '@docusaurus/types': 2.4.1(react-dom@17.0.2)(react@17.0.2) - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) + + '@docusaurus/preset-classic@3.7.0(@algolia/client-search@5.21.0)(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(@types/react@18.3.18)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.8.2)': + dependencies: + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/plugin-content-blog': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/plugin-content-docs': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/plugin-content-pages': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/plugin-debug': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/plugin-google-analytics': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/plugin-google-gtag': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/plugin-google-tag-manager': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/plugin-sitemap': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/plugin-svgr': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/theme-classic': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@swc/core@1.11.9)(@types/react@18.3.18)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2))(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-search-algolia': 3.7.0(@algolia/client-search@5.21.0)(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(@types/react@18.3.18)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.8.2) + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: - '@algolia/client-search' + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' + - '@rspack/core' - '@swc/core' - '@swc/css' - '@types/react' + - acorn - bufferutil - csso - debug - - encoding - esbuild - eslint - lightningcss @@ -3471,71 +8865,56 @@ packages: - utf-8-validate - vue-template-compiler - webpack-cli - dev: false - /@docusaurus/react-loadable@5.5.2(react@17.0.2): - resolution: {integrity: sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==} - peerDependencies: - react: '*' + '@docusaurus/react-loadable@6.0.0(react@18.3.1)': dependencies: - '@types/react': 18.2.21 - prop-types: 15.8.1 - react: 17.0.2 + '@types/react': 18.3.18 + react: 18.3.1 - /@docusaurus/responsive-loader@1.7.0(sharp@0.30.7): - resolution: {integrity: sha512-N0cWuVqTRXRvkBxeMQcy/OF2l7GN8rmni5EzR3HpwR+iU2ckYPnziceojcxvvxQ5NqZg1QfEW0tycQgHp+e+Nw==} - engines: {node: '>=12'} - peerDependencies: - jimp: '*' - sharp: '*' - peerDependenciesMeta: - jimp: - optional: true - sharp: - optional: true + '@docusaurus/responsive-loader@1.7.1(sharp@0.32.6)': dependencies: loader-utils: 2.0.4 - sharp: 0.30.7 - dev: false - - /@docusaurus/theme-classic@2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5): - resolution: {integrity: sha512-Rz0wKUa+LTW1PLXmwnf8mn85EBzaGSt6qamqtmnh9Hflkc+EqiYMhtUJeLdV+wsgYq4aG0ANc+bpUDpsUhdnwg==} - engines: {node: '>=16.14'} - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 - dependencies: - '@docusaurus/core': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/mdx-loader': 2.4.1(@docusaurus/types@2.4.1)(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/module-type-aliases': 2.4.1(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/plugin-content-blog': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/plugin-content-docs': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/plugin-content-pages': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/theme-common': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/theme-translations': 2.4.1 - '@docusaurus/types': 2.4.1(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/utils': 2.4.1(@docusaurus/types@2.4.1) - '@docusaurus/utils-common': 2.4.1(@docusaurus/types@2.4.1) - '@docusaurus/utils-validation': 2.4.1(@docusaurus/types@2.4.1) - '@mdx-js/react': 1.6.22(react@17.0.2) - clsx: 1.2.1 + optionalDependencies: + sharp: 0.32.6 + + '@docusaurus/theme-classic@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@swc/core@1.11.9)(@types/react@18.3.18)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)': + dependencies: + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/module-type-aliases': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-blog': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/plugin-content-docs': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/plugin-content-pages': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2))(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-translations': 3.7.0 + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mdx-js/react': 3.1.0(@types/react@18.3.18)(react@18.3.1) + clsx: 2.1.1 copy-text-to-clipboard: 3.2.0 - infima: 0.2.0-alpha.43 + infima: 0.2.0-alpha.45 lodash: 4.17.21 nprogress: 0.2.0 - postcss: 8.4.29 - prism-react-renderer: 1.3.5(react@17.0.2) - prismjs: 1.29.0 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - react-router-dom: 5.3.4(react@17.0.2) - rtlcss: 3.5.0 - tslib: 2.6.2 - utility-types: 3.10.0 + postcss: 8.5.3 + prism-react-renderer: 2.4.1(react@18.3.1) + prismjs: 1.30.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router-dom: 5.3.4(react@18.3.1) + rtlcss: 4.3.0 + tslib: 2.8.1 + utility-types: 3.11.0 transitivePeerDependencies: + - '@docusaurus/faster' - '@parcel/css' + - '@rspack/core' - '@swc/core' - '@swc/css' + - '@types/react' + - acorn - bufferutil - csso - debug @@ -3548,84 +8927,62 @@ packages: - utf-8-validate - vue-template-compiler - webpack-cli - dev: false - - /@docusaurus/theme-common@2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5): - resolution: {integrity: sha512-G7Zau1W5rQTaFFB3x3soQoZpkgMbl/SYNG8PfMFIjKa3M3q8n0m/GRf5/H/e5BqOvt8c+ZWIXGCiz+kUCSHovA==} - engines: {node: '>=16.14'} - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 - dependencies: - '@docusaurus/mdx-loader': 2.4.1(@docusaurus/types@2.4.1)(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/module-type-aliases': 2.4.1(react-dom@17.0.2)(react@17.0.2) - '@docusaurus/plugin-content-blog': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/plugin-content-docs': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/plugin-content-pages': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/utils': 2.4.1(@docusaurus/types@2.4.1) - '@docusaurus/utils-common': 2.4.1(@docusaurus/types@2.4.1) + + '@docusaurus/theme-common@3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2))(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/module-type-aliases': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-docs': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/utils': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/history': 4.7.11 - '@types/react': 18.2.21 - '@types/react-router-config': 5.0.7 - clsx: 1.2.1 + '@types/react': 18.3.18 + '@types/react-router-config': 5.0.11 + clsx: 2.1.1 parse-numeric-range: 1.3.0 - prism-react-renderer: 1.3.5(react@17.0.2) - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - tslib: 2.6.2 - use-sync-external-store: 1.2.0(react@17.0.2) - utility-types: 3.10.0 - transitivePeerDependencies: - - '@docusaurus/types' - - '@parcel/css' + prism-react-renderer: 2.4.1(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.8.1 + utility-types: 3.11.0 + transitivePeerDependencies: - '@swc/core' - - '@swc/css' - - bufferutil - - csso - - debug + - acorn - esbuild - - eslint - - lightningcss - supports-color - - typescript - uglify-js - - utf-8-validate - - vue-template-compiler - webpack-cli - dev: false - - /@docusaurus/theme-search-algolia@2.4.1(@algolia/client-search@4.19.1)(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(search-insights@2.8.1)(typescript@4.9.5): - resolution: {integrity: sha512-6BcqW2lnLhZCXuMAvPRezFs1DpmEKzXFKlYjruuas+Xy3AQeFzDJKTJFIm49N77WFCTyxff8d3E4Q9pi/+5McQ==} - engines: {node: '>=16.14'} - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 - dependencies: - '@docsearch/react': 3.5.2(@algolia/client-search@4.19.1)(react-dom@17.0.2)(react@17.0.2)(search-insights@2.8.1) - '@docusaurus/core': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/logger': 2.4.1 - '@docusaurus/plugin-content-docs': 2.4.1(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/theme-common': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@docusaurus/theme-translations': 2.4.1 - '@docusaurus/utils': 2.4.1(@docusaurus/types@2.4.1) - '@docusaurus/utils-validation': 2.4.1(@docusaurus/types@2.4.1) - algoliasearch: 4.19.1 - algoliasearch-helper: 3.14.0(algoliasearch@4.19.1) - clsx: 1.2.1 + + '@docusaurus/theme-search-algolia@3.7.0(@algolia/client-search@5.21.0)(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(@types/react@18.3.18)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.8.2)': + dependencies: + '@docsearch/react': 3.9.0(@algolia/client-search@5.21.0)(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/plugin-content-docs': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2))(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-translations': 3.7.0 + '@docusaurus/utils': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + algoliasearch: 5.21.0 + algoliasearch-helper: 3.24.2(algoliasearch@5.21.0) + clsx: 2.1.1 eta: 2.2.0 - fs-extra: 10.1.0 + fs-extra: 11.3.0 lodash: 4.17.21 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - tslib: 2.6.2 - utility-types: 3.10.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.8.1 + utility-types: 3.11.0 transitivePeerDependencies: - '@algolia/client-search' - - '@docusaurus/types' + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' + - '@rspack/core' - '@swc/core' - '@swc/css' - '@types/react' + - acorn - bufferutil - csso - debug @@ -3639,120 +8996,119 @@ packages: - utf-8-validate - vue-template-compiler - webpack-cli - dev: false - /@docusaurus/theme-translations@2.4.1: - resolution: {integrity: sha512-T1RAGP+f86CA1kfE8ejZ3T3pUU3XcyvrGMfC/zxCtc2BsnoexuNI9Vk2CmuKCb+Tacvhxjv5unhxXce0+NKyvA==} - engines: {node: '>=16.14'} + '@docusaurus/theme-translations@3.7.0': dependencies: - fs-extra: 10.1.0 - tslib: 2.6.2 - dev: false + fs-extra: 11.3.0 + tslib: 2.8.1 - /@docusaurus/types@2.4.1(react-dom@17.0.2)(react@17.0.2): - resolution: {integrity: sha512-0R+cbhpMkhbRXX138UOc/2XZFF8hiZa6ooZAEEJFp5scytzCw4tC1gChMFXrpa3d2tYE6AX8IrOEpSonLmfQuQ==} - peerDependencies: - react: ^16.8.4 || ^17.0.0 - react-dom: ^16.8.4 || ^17.0.0 + '@docusaurus/tsconfig@3.7.0': {} + + '@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: + '@mdx-js/mdx': 3.1.0(acorn@7.4.1) '@types/history': 4.7.11 - '@types/react': 18.2.21 + '@types/react': 18.3.18 commander: 5.1.0 - joi: 17.10.1 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - react-helmet-async: 1.3.0(react-dom@17.0.2)(react@17.0.2) - utility-types: 3.10.0 - webpack: 5.88.2 - webpack-merge: 5.9.0 + joi: 17.13.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)' + utility-types: 3.11.0 + webpack: 5.98.0(@swc/core@1.11.9) + webpack-merge: 5.10.0 transitivePeerDependencies: - '@swc/core' + - acorn - esbuild + - supports-color - uglify-js - webpack-cli - /@docusaurus/utils-common@2.4.1(@docusaurus/types@2.4.1): - resolution: {integrity: sha512-bCVGdZU+z/qVcIiEQdyx0K13OC5mYwxhSuDUR95oFbKVuXYRrTVrwZIqQljuo1fyJvFTKHiL9L9skQOPokuFNQ==} - engines: {node: '>=16.14'} - peerDependencies: - '@docusaurus/types': '*' - peerDependenciesMeta: - '@docusaurus/types': - optional: true + '@docusaurus/utils-common@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/types': 2.4.1(react-dom@17.0.2)(react@17.0.2) - tslib: 2.6.2 + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + tslib: 2.8.1 + transitivePeerDependencies: + - '@swc/core' + - acorn + - esbuild + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli - /@docusaurus/utils-validation@2.4.1(@docusaurus/types@2.4.1): - resolution: {integrity: sha512-unII3hlJlDwZ3w8U+pMO3Lx3RhI4YEbY3YNsQj4yzrkZzlpqZOLuAiZK2JyULnD+TKbceKU0WyWkQXtYbLNDFA==} - engines: {node: '>=16.14'} + '@docusaurus/utils-validation@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/logger': 2.4.1 - '@docusaurus/utils': 2.4.1(@docusaurus/types@2.4.1) - joi: 17.10.1 + '@docusaurus/logger': 3.7.0 + '@docusaurus/utils': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + fs-extra: 11.3.0 + joi: 17.13.3 js-yaml: 4.1.0 - tslib: 2.6.2 + lodash: 4.17.21 + tslib: 2.8.1 transitivePeerDependencies: - - '@docusaurus/types' - '@swc/core' + - acorn - esbuild + - react + - react-dom - supports-color - uglify-js - webpack-cli - /@docusaurus/utils@2.4.1(@docusaurus/types@2.4.1): - resolution: {integrity: sha512-1lvEZdAQhKNht9aPXPoh69eeKnV0/62ROhQeFKKxmzd0zkcuE/Oc5Gpnt00y/f5bIsmOsYMY7Pqfm/5rteT5GA==} - engines: {node: '>=16.14'} - peerDependencies: - '@docusaurus/types': '*' - peerDependenciesMeta: - '@docusaurus/types': - optional: true + '@docusaurus/utils@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/logger': 2.4.1 - '@docusaurus/types': 2.4.1(react-dom@17.0.2)(react@17.0.2) - '@svgr/webpack': 6.5.1 + '@docusaurus/logger': 3.7.0 + '@docusaurus/types': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) escape-string-regexp: 4.0.0 - file-loader: 6.2.0(webpack@5.88.2) - fs-extra: 10.1.0 + file-loader: 6.2.0(webpack@5.98.0(@swc/core@1.11.9)) + fs-extra: 11.3.0 github-slugger: 1.5.0 globby: 11.1.0 gray-matter: 4.0.3 + jiti: 1.21.7 js-yaml: 4.1.0 lodash: 4.17.21 - micromatch: 4.0.5 + micromatch: 4.0.8 + prompts: 2.4.2 resolve-pathname: 3.0.0 shelljs: 0.8.5 - tslib: 2.6.2 - url-loader: 4.1.1(file-loader@6.2.0)(webpack@5.88.2) - webpack: 5.88.2 + tslib: 2.8.1 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.98.0(@swc/core@1.11.9)))(webpack@5.98.0(@swc/core@1.11.9)) + utility-types: 3.11.0 + webpack: 5.98.0(@swc/core@1.11.9) transitivePeerDependencies: - '@swc/core' + - acorn - esbuild + - react + - react-dom - supports-color - uglify-js - webpack-cli - /@endiliey/react-ideal-image@0.0.11(prop-types@15.8.1)(react-waypoint@10.3.0)(react@17.0.2): - resolution: {integrity: sha512-QxMjt/Gvur/gLxSoCy7VIyGGGrGmDN+VHcXkN3R2ApoWX0EYUE+hMgPHSW/PV6VVebZ1Nd4t2UnGRBDihu16JQ==} - engines: {node: '>= 8.9.0', npm: '> 3'} - peerDependencies: - prop-types: '>=15' - react: '>=0.14.x' - react-waypoint: '>=9.0.2' + '@dual-bundle/import-meta-resolve@4.1.0': {} + + '@emnapi/runtime@1.3.1': dependencies: - prop-types: 15.8.1 - react: 17.0.2 - react-waypoint: 10.3.0(react@17.0.2) - dev: false + tslib: 2.8.1 + optional: true - /@eslint-kit/eslint-config-base@4.1.0(@typescript-eslint/parser@5.38.0)(eslint@7.32.0): - resolution: {integrity: sha512-vDIeH8WefnMGa9T3ExFZNAXyWR2TvP6ul8NizODPERwp9upJ34Q7QUfemtJjjwiSzKpaHzSz4/qlX41hhoBxSQ==} - peerDependencies: - eslint: ^5.16.0 || ^6.8.0 || ^7.2.0 + '@eslint-community/eslint-utils@4.5.1(eslint@7.32.0)': + dependencies: + eslint: 7.32.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint-kit/eslint-config-base@4.1.0(@typescript-eslint/parser@6.21.0(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0)': dependencies: eslint: 7.32.0 - eslint-plugin-import: 2.23.2(@typescript-eslint/parser@5.38.0)(eslint@7.32.0) + eslint-plugin-import: 2.23.2(@typescript-eslint/parser@6.21.0(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0) eslint-plugin-sonarjs: 0.7.0(eslint@7.32.0) eslint-plugin-unicorn: 32.0.1(eslint@7.32.0) transitivePeerDependencies: @@ -3760,1524 +9116,1250 @@ packages: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - dev: true - /@eslint-kit/eslint-config-patch@1.0.0(eslint@7.32.0): - resolution: {integrity: sha512-b3pODxIVCfXz/q5n/C6NeB5rOjNwS+IKtMAIbi9qk4Pz9wrYgTgjaiOCxj5aEfT0gE49t7/bjvZv2tWlSe171Q==} - peerDependencies: - eslint: ^5.16.0 || ^6.8.0 || ^7.2.0 + '@eslint-kit/eslint-config-patch@1.0.0(eslint@7.32.0)': dependencies: '@rushstack/eslint-patch': 1.0.6 eslint: 7.32.0 - dev: true - - /@eslint-kit/eslint-config-prettier@3.0.0(eslint@7.32.0)(prettier@2.3.0): - resolution: {integrity: sha512-TzHP+B2zUDg24EDRIgnjLrducEuk8+oUt+fe+64TmYviRSxWv28sdSCxoAfPIhU/sJcok8g4+0RFOk3YHiYXWA==} - peerDependencies: - eslint: ^5.16.0 || ^6.8.0 || ^7.2.0 - dependencies: - eslint: 7.32.0 - eslint-plugin-prettier: 3.4.0(eslint@7.32.0)(prettier@2.3.0) - transitivePeerDependencies: - - eslint-config-prettier - - prettier - dev: true - /@eslint-kit/eslint-config-react@3.0.0(eslint@7.32.0): - resolution: {integrity: sha512-DBV0TXA+OaJSqIBX9LNF/u4fkvqiWUo6NmRKt5vJYfwczzHRqONF2+UUAahAouWuF6XNFmf/cHkXLIxZDBIfRQ==} - peerDependencies: - eslint: ^5.16.0 || ^6.8.0 || ^7.2.0 + '@eslint-kit/eslint-config-react@3.0.0(eslint@7.32.0)': dependencies: eslint: 7.32.0 eslint-plugin-react: 7.23.2(eslint@7.32.0) eslint-plugin-react-hooks: 4.2.0(eslint@7.32.0) - dev: true - /@eslint/eslintrc@0.4.3: - resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==} - engines: {node: ^10.12.0 || >=12.0.0} + '@eslint/eslintrc@0.4.3': dependencies: ajv: 6.12.6 - debug: 4.3.4 + debug: 4.4.0 espree: 7.3.1 - globals: 13.20.0 + globals: 13.24.0 ignore: 4.0.6 - import-fresh: 3.3.0 + import-fresh: 3.3.1 js-yaml: 3.14.1 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - /@fontsource/overpass@4.5.4: - resolution: {integrity: sha512-jCYMISy780D6E+RxV3MHe2Nrteg4Eq26D4yORpGp+AoW11N2IPN+u1rJJVFz8pIyXUekSGfTSHzScSsF2iTIZQ==} - dev: false + '@fontsource-variable/overpass@5.2.5': {} - /@fontsource/ubuntu@4.5.4: - resolution: {integrity: sha512-wekWBF7Mj0GOyoLHnsSXgKZyO7gfS1KzQXOqxEOUUO9wj4ligv4QAWj/hnDqKDtYdBXxBAVKr5B7Yp3LrI6/mw==} - dev: false + '@hapi/hoek@9.3.0': {} - /@hapi/hoek@9.3.0: - resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} - - /@hapi/topo@5.1.0: - resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + '@hapi/topo@5.1.0': dependencies: '@hapi/hoek': 9.3.0 - /@humanwhocodes/config-array@0.5.0: - resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==} - engines: {node: '>=10.10.0'} + '@humanwhocodes/config-array@0.5.0': dependencies: '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4 + debug: 4.4.0 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - /@humanwhocodes/object-schema@1.2.1: - resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + '@humanwhocodes/object-schema@1.2.1': {} - /@jest/schemas@29.6.3: - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.3.1 + optional: true + + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true + + '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 - /@jest/types@29.6.3: - resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/types@29.6.3': dependencies: '@jest/schemas': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.4 - '@types/istanbul-reports': 3.0.1 - '@types/node': 20.5.9 - '@types/yargs': 17.0.24 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 22.13.10 + '@types/yargs': 17.0.33 chalk: 4.1.2 - /@jridgewell/gen-mapping@0.1.1: - resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 - - /@jridgewell/gen-mapping@0.3.2: - resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} - engines: {node: '>=6.0.0'} + '@jridgewell/gen-mapping@0.3.8': dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 - '@jridgewell/trace-mapping': 0.3.15 + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 - /@jridgewell/gen-mapping@0.3.3: - resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.19 + '@jridgewell/resolve-uri@3.1.2': {} - /@jridgewell/resolve-uri@3.1.0: - resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} - engines: {node: '>=6.0.0'} + '@jridgewell/set-array@1.2.1': {} - /@jridgewell/resolve-uri@3.1.1: - resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} - engines: {node: '>=6.0.0'} + '@jridgewell/source-map@0.3.6': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 - /@jridgewell/set-array@1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.0': {} - /@jridgewell/source-map@0.3.5: - resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} + '@jridgewell/trace-mapping@0.3.25': dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.19 + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 - /@jridgewell/sourcemap-codec@1.4.14: - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + '@keyv/serialize@1.0.3': + dependencies: + buffer: 6.0.3 - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@leichtgewicht/ip-codec@2.0.5': {} - /@jridgewell/trace-mapping@0.3.15: - resolution: {integrity: sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==} + '@mdx-js/mdx@3.1.0(acorn@7.4.1)': dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 + '@types/estree': 1.0.6 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdx': 2.0.13 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.0(acorn@7.4.1) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.0 + remark-parse: 11.0.0 + remark-rehype: 11.1.1 + source-map: 0.7.4 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - acorn + - supports-color - /@jridgewell/trace-mapping@0.3.19: - resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==} + '@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1)': dependencies: - '@jridgewell/resolve-uri': 3.1.1 - '@jridgewell/sourcemap-codec': 1.4.15 + '@types/mdx': 2.0.13 + '@types/react': 18.3.18 + react: 18.3.1 - /@leichtgewicht/ip-codec@2.0.4: - resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==} + '@module-federation/error-codes@0.8.4': {} - /@mdx-js/mdx@1.6.22: - resolution: {integrity: sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA==} + '@module-federation/runtime-tools@0.8.4': dependencies: - '@babel/core': 7.12.9 - '@babel/plugin-syntax-jsx': 7.12.1(@babel/core@7.12.9) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.12.9) - '@mdx-js/util': 1.6.22 - babel-plugin-apply-mdx-type-prop: 1.6.22(@babel/core@7.12.9) - babel-plugin-extract-import-names: 1.6.22 - camelcase-css: 2.0.1 - detab: 2.0.4 - hast-util-raw: 6.0.1 - lodash.uniq: 4.5.0 - mdast-util-to-hast: 10.0.1 - remark-footnotes: 2.0.0 - remark-mdx: 1.6.22 - remark-parse: 8.0.3 - remark-squeeze-paragraphs: 4.0.0 - style-to-object: 0.3.0 - unified: 9.2.0 - unist-builder: 2.0.3 - unist-util-visit: 2.0.3 - transitivePeerDependencies: - - supports-color + '@module-federation/runtime': 0.8.4 + '@module-federation/webpack-bundler-runtime': 0.8.4 - /@mdx-js/react@1.6.22(react@17.0.2): - resolution: {integrity: sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==} - peerDependencies: - react: ^16.13.1 || ^17.0.0 + '@module-federation/runtime@0.8.4': dependencies: - react: 17.0.2 - dev: false + '@module-federation/error-codes': 0.8.4 + '@module-federation/sdk': 0.8.4 - /@mdx-js/util@1.6.22: - resolution: {integrity: sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==} + '@module-federation/sdk@0.8.4': + dependencies: + isomorphic-rslog: 0.0.6 - /@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1: - resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + '@module-federation/webpack-bundler-runtime@0.8.4': + dependencies: + '@module-federation/runtime': 0.8.4 + '@module-federation/sdk': 0.8.4 + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': dependencies: eslint-scope: 5.1.1 - dev: true - /@nodelib/fs.scandir@2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - /@nodelib/fs.stat@2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} + '@nodelib/fs.stat@2.0.5': {} - /@nodelib/fs.walk@1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} + '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.13.0 + fastq: 1.19.1 - /@polka/url@1.0.0-next.21: - resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} + '@parcel/watcher-android-arm64@2.5.1': + optional: true - /@rushstack/eslint-patch@1.0.6: - resolution: {integrity: sha512-Myxw//kzromB9yWgS8qYGuGVf91oBUUJpNvy5eM50sqvmKLbKjwLxohJnkWGTeeI9v9IBMtPLxz5Gc60FIfvCA==} - dev: true + '@parcel/watcher-darwin-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-x64@2.5.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': + optional: true + + '@parcel/watcher-win32-arm64@2.5.1': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': + optional: true + + '@parcel/watcher@2.5.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 + optional: true + + '@pnpm/config.env-replace@1.1.0': {} + + '@pnpm/network.ca-file@1.0.2': + dependencies: + graceful-fs: 4.2.10 + + '@pnpm/npm-conf@2.3.1': + dependencies: + '@pnpm/config.env-replace': 1.1.0 + '@pnpm/network.ca-file': 1.0.2 + config-chain: 1.1.13 + + '@polka/url@1.0.0-next.28': {} + + '@rspack/binding-darwin-arm64@1.2.0-alpha.0': + optional: true + + '@rspack/binding-darwin-x64@1.2.0-alpha.0': + optional: true + + '@rspack/binding-linux-arm64-gnu@1.2.0-alpha.0': + optional: true - /@sideway/address@4.1.4: - resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} + '@rspack/binding-linux-arm64-musl@1.2.0-alpha.0': + optional: true + + '@rspack/binding-linux-x64-gnu@1.2.0-alpha.0': + optional: true + + '@rspack/binding-linux-x64-musl@1.2.0-alpha.0': + optional: true + + '@rspack/binding-win32-arm64-msvc@1.2.0-alpha.0': + optional: true + + '@rspack/binding-win32-ia32-msvc@1.2.0-alpha.0': + optional: true + + '@rspack/binding-win32-x64-msvc@1.2.0-alpha.0': + optional: true + + '@rspack/binding@1.2.0-alpha.0': + optionalDependencies: + '@rspack/binding-darwin-arm64': 1.2.0-alpha.0 + '@rspack/binding-darwin-x64': 1.2.0-alpha.0 + '@rspack/binding-linux-arm64-gnu': 1.2.0-alpha.0 + '@rspack/binding-linux-arm64-musl': 1.2.0-alpha.0 + '@rspack/binding-linux-x64-gnu': 1.2.0-alpha.0 + '@rspack/binding-linux-x64-musl': 1.2.0-alpha.0 + '@rspack/binding-win32-arm64-msvc': 1.2.0-alpha.0 + '@rspack/binding-win32-ia32-msvc': 1.2.0-alpha.0 + '@rspack/binding-win32-x64-msvc': 1.2.0-alpha.0 + + '@rspack/core@1.2.0-alpha.0': + dependencies: + '@module-federation/runtime-tools': 0.8.4 + '@rspack/binding': 1.2.0-alpha.0 + '@rspack/lite-tapable': 1.0.1 + caniuse-lite: 1.0.30001704 + + '@rspack/lite-tapable@1.0.1': {} + + '@rushstack/eslint-patch@1.0.6': {} + + '@sideway/address@4.1.5': dependencies: '@hapi/hoek': 9.3.0 - /@sideway/formula@3.0.1: - resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + '@sideway/formula@3.0.1': {} - /@sideway/pinpoint@2.0.0: - resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + '@sideway/pinpoint@2.0.0': {} - /@sinclair/typebox@0.27.8: - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@sinclair/typebox@0.27.8': {} - /@sindresorhus/is@0.14.0: - resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} - engines: {node: '>=6'} + '@sindresorhus/is@4.6.0': {} - /@slorber/static-site-generator-webpack-plugin@4.0.7: - resolution: {integrity: sha512-Ug7x6z5lwrz0WqdnNFOMYrDQNTPAprvHLSh6+/fmml3qUiz6l5eq+2MzLKWtn/q5K5NpSiFsZTP/fck/3vjSxA==} - engines: {node: '>=14'} + '@sindresorhus/is@5.6.0': {} + + '@slorber/react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - eval: 0.1.8 - p-map: 4.0.0 - webpack-sources: 3.2.3 + '@babel/runtime': 7.26.10 + invariant: 2.2.4 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-fast-compare: 3.2.2 + shallowequal: 1.1.0 - /@stylelint/postcss-css-in-js@0.37.2(postcss-syntax@0.36.2)(postcss@7.0.36): - resolution: {integrity: sha512-nEhsFoJurt8oUmieT8qy4nk81WRHmJynmVwn/Vts08PL9fhgIsMhk1GId5yAN643OzqEEb5S/6At2TZW7pqPDA==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - peerDependencies: - postcss: '>=7.0.0' - postcss-syntax: '>=0.36.2' + '@slorber/react-ideal-image@0.0.14(react-waypoint@10.3.0(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/core': 7.19.1 - postcss: 7.0.36 - postcss-syntax: 0.36.2(postcss@8.4.29) - transitivePeerDependencies: - - supports-color - dev: true + react: 18.3.1 + react-waypoint: 10.3.0(react@18.3.1) - /@stylelint/postcss-markdown@0.36.2(postcss-syntax@0.36.2)(postcss@7.0.36): - resolution: {integrity: sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ==} - deprecated: 'Use the original unforked package instead: postcss-markdown' - peerDependencies: - postcss: '>=7.0.0' - postcss-syntax: '>=0.36.2' + '@slorber/remark-comment@1.0.0': dependencies: - postcss: 7.0.36 - postcss-syntax: 0.36.2(postcss@8.4.29) - remark: 13.0.0 - unist-util-find-all-after: 3.0.2 - transitivePeerDependencies: - - supports-color - dev: true + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 - /@svgr/babel-plugin-add-jsx-attribute@5.4.0: - resolution: {integrity: sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==} - engines: {node: '>=10'} - dev: false + '@stencil/core@2.22.3': {} - /@svgr/babel-plugin-add-jsx-attribute@6.5.1(@babel/core@7.22.11): - resolution: {integrity: sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==} - engines: {node: '>=10'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 + '@babel/core': 7.26.10 - /@svgr/babel-plugin-remove-jsx-attribute@5.4.0: - resolution: {integrity: sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==} - engines: {node: '>=10'} - dev: false + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 - /@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.22.11): - resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 + '@babel/core': 7.26.10 - /@svgr/babel-plugin-remove-jsx-empty-expression@5.0.1: - resolution: {integrity: sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==} - engines: {node: '>=10'} - dev: false + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 - /@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.22.11): - resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 + '@babel/core': 7.26.10 - /@svgr/babel-plugin-replace-jsx-attribute-value@5.0.1: - resolution: {integrity: sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==} - engines: {node: '>=10'} - dev: false + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 - /@svgr/babel-plugin-replace-jsx-attribute-value@6.5.1(@babel/core@7.22.11): - resolution: {integrity: sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg==} - engines: {node: '>=10'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 + '@babel/core': 7.26.10 - /@svgr/babel-plugin-svg-dynamic-title@5.4.0: - resolution: {integrity: sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==} - engines: {node: '>=10'} - dev: false + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 - /@svgr/babel-plugin-svg-dynamic-title@6.5.1(@babel/core@7.22.11): - resolution: {integrity: sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw==} - engines: {node: '>=10'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/babel-preset@8.1.0(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.22.11 + '@babel/core': 7.26.10 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.26.10) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.26.10) - /@svgr/babel-plugin-svg-em-dimensions@5.4.0: - resolution: {integrity: sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==} - engines: {node: '>=10'} - dev: false + '@svgr/core@8.1.0(typescript@5.8.2)': + dependencies: + '@babel/core': 7.26.10 + '@svgr/babel-preset': 8.1.0(@babel/core@7.26.10) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@5.8.2) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript - /@svgr/babel-plugin-svg-em-dimensions@6.5.1(@babel/core@7.22.11): - resolution: {integrity: sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA==} - engines: {node: '>=10'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/hast-util-to-babel-ast@8.0.0': dependencies: - '@babel/core': 7.22.11 + '@babel/types': 7.26.10 + entities: 4.5.0 - /@svgr/babel-plugin-transform-react-native-svg@5.4.0: - resolution: {integrity: sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==} - engines: {node: '>=10'} - dev: false + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.8.2))': + dependencies: + '@babel/core': 7.26.10 + '@svgr/babel-preset': 8.1.0(@babel/core@7.26.10) + '@svgr/core': 8.1.0(typescript@5.8.2) + '@svgr/hast-util-to-babel-ast': 8.0.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color - /@svgr/babel-plugin-transform-react-native-svg@6.5.1(@babel/core@7.22.11): - resolution: {integrity: sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg==} - engines: {node: '>=10'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.8.2))(typescript@5.8.2)': dependencies: - '@babel/core': 7.22.11 + '@svgr/core': 8.1.0(typescript@5.8.2) + cosmiconfig: 8.3.6(typescript@5.8.2) + deepmerge: 4.3.1 + svgo: 3.3.2 + transitivePeerDependencies: + - typescript - /@svgr/babel-plugin-transform-svg-component@5.5.0: - resolution: {integrity: sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==} - engines: {node: '>=10'} - dev: false + '@svgr/webpack@8.1.0(typescript@5.8.2)': + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-transform-react-constant-elements': 7.25.9(@babel/core@7.26.10) + '@babel/preset-env': 7.26.9(@babel/core@7.26.10) + '@babel/preset-react': 7.26.3(@babel/core@7.26.10) + '@babel/preset-typescript': 7.26.0(@babel/core@7.26.10) + '@svgr/core': 8.1.0(typescript@5.8.2) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.2)) + '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.8.2))(typescript@5.8.2) + transitivePeerDependencies: + - supports-color + - typescript + + '@swc/core-darwin-arm64@1.11.9': + optional: true + + '@swc/core-darwin-x64@1.11.9': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.11.9': + optional: true + + '@swc/core-linux-arm64-gnu@1.11.9': + optional: true + + '@swc/core-linux-arm64-musl@1.11.9': + optional: true + + '@swc/core-linux-x64-gnu@1.11.9': + optional: true + + '@swc/core-linux-x64-musl@1.11.9': + optional: true + + '@swc/core-win32-arm64-msvc@1.11.9': + optional: true + + '@swc/core-win32-ia32-msvc@1.11.9': + optional: true + + '@swc/core-win32-x64-msvc@1.11.9': + optional: true + + '@swc/core@1.11.9': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.19 + optionalDependencies: + '@swc/core-darwin-arm64': 1.11.9 + '@swc/core-darwin-x64': 1.11.9 + '@swc/core-linux-arm-gnueabihf': 1.11.9 + '@swc/core-linux-arm64-gnu': 1.11.9 + '@swc/core-linux-arm64-musl': 1.11.9 + '@swc/core-linux-x64-gnu': 1.11.9 + '@swc/core-linux-x64-musl': 1.11.9 + '@swc/core-win32-arm64-msvc': 1.11.9 + '@swc/core-win32-ia32-msvc': 1.11.9 + '@swc/core-win32-x64-msvc': 1.11.9 + + '@swc/counter@0.1.3': {} + + '@swc/html-darwin-arm64@1.11.9': + optional: true + + '@swc/html-darwin-x64@1.11.9': + optional: true + + '@swc/html-linux-arm-gnueabihf@1.11.9': + optional: true + + '@swc/html-linux-arm64-gnu@1.11.9': + optional: true + + '@swc/html-linux-arm64-musl@1.11.9': + optional: true + + '@swc/html-linux-x64-gnu@1.11.9': + optional: true + + '@swc/html-linux-x64-musl@1.11.9': + optional: true + + '@swc/html-win32-arm64-msvc@1.11.9': + optional: true + + '@swc/html-win32-ia32-msvc@1.11.9': + optional: true - /@svgr/babel-plugin-transform-svg-component@6.5.1(@babel/core@7.22.11): - resolution: {integrity: sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ==} - engines: {node: '>=12'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 + '@swc/html-win32-x64-msvc@1.11.9': + optional: true - /@svgr/babel-preset@5.5.0: - resolution: {integrity: sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==} - engines: {node: '>=10'} - dependencies: - '@svgr/babel-plugin-add-jsx-attribute': 5.4.0 - '@svgr/babel-plugin-remove-jsx-attribute': 5.4.0 - '@svgr/babel-plugin-remove-jsx-empty-expression': 5.0.1 - '@svgr/babel-plugin-replace-jsx-attribute-value': 5.0.1 - '@svgr/babel-plugin-svg-dynamic-title': 5.4.0 - '@svgr/babel-plugin-svg-em-dimensions': 5.4.0 - '@svgr/babel-plugin-transform-react-native-svg': 5.4.0 - '@svgr/babel-plugin-transform-svg-component': 5.5.0 - dev: false - - /@svgr/babel-preset@6.5.1(@babel/core@7.22.11): - resolution: {integrity: sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw==} - engines: {node: '>=10'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@svgr/babel-plugin-add-jsx-attribute': 6.5.1(@babel/core@7.22.11) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.22.11) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.22.11) - '@svgr/babel-plugin-replace-jsx-attribute-value': 6.5.1(@babel/core@7.22.11) - '@svgr/babel-plugin-svg-dynamic-title': 6.5.1(@babel/core@7.22.11) - '@svgr/babel-plugin-svg-em-dimensions': 6.5.1(@babel/core@7.22.11) - '@svgr/babel-plugin-transform-react-native-svg': 6.5.1(@babel/core@7.22.11) - '@svgr/babel-plugin-transform-svg-component': 6.5.1(@babel/core@7.22.11) - - /@svgr/core@5.5.0: - resolution: {integrity: sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==} - engines: {node: '>=10'} + '@swc/html@1.11.9': dependencies: - '@svgr/plugin-jsx': 5.5.0 - camelcase: 6.3.0 - cosmiconfig: 7.0.1 - transitivePeerDependencies: - - supports-color - dev: false + '@swc/counter': 0.1.3 + optionalDependencies: + '@swc/html-darwin-arm64': 1.11.9 + '@swc/html-darwin-x64': 1.11.9 + '@swc/html-linux-arm-gnueabihf': 1.11.9 + '@swc/html-linux-arm64-gnu': 1.11.9 + '@swc/html-linux-arm64-musl': 1.11.9 + '@swc/html-linux-x64-gnu': 1.11.9 + '@swc/html-linux-x64-musl': 1.11.9 + '@swc/html-win32-arm64-msvc': 1.11.9 + '@swc/html-win32-ia32-msvc': 1.11.9 + '@swc/html-win32-x64-msvc': 1.11.9 - /@svgr/core@6.5.1: - resolution: {integrity: sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==} - engines: {node: '>=10'} + '@swc/types@0.1.19': dependencies: - '@babel/core': 7.22.11 - '@svgr/babel-preset': 6.5.1(@babel/core@7.22.11) - '@svgr/plugin-jsx': 6.5.1(@svgr/core@6.5.1) - camelcase: 6.3.0 - cosmiconfig: 7.1.0 - transitivePeerDependencies: - - supports-color + '@swc/counter': 0.1.3 - /@svgr/hast-util-to-babel-ast@5.5.0: - resolution: {integrity: sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==} - engines: {node: '>=10'} + '@szmarczak/http-timer@5.0.1': dependencies: - '@babel/types': 7.19.0 - dev: false + defer-to-connect: 2.0.1 - /@svgr/hast-util-to-babel-ast@6.5.1: - resolution: {integrity: sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw==} - engines: {node: '>=10'} - dependencies: - '@babel/types': 7.22.11 - entities: 4.5.0 + '@trysound/sax@0.2.0': {} - /@svgr/plugin-jsx@5.5.0: - resolution: {integrity: sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==} - engines: {node: '>=10'} + '@types/acorn@4.0.6': dependencies: - '@babel/core': 7.19.1 - '@svgr/babel-preset': 5.5.0 - '@svgr/hast-util-to-babel-ast': 5.5.0 - svg-parser: 2.0.4 - transitivePeerDependencies: - - supports-color - dev: false + '@types/estree': 1.0.6 - /@svgr/plugin-jsx@6.5.1(@svgr/core@6.5.1): - resolution: {integrity: sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw==} - engines: {node: '>=10'} - peerDependencies: - '@svgr/core': ^6.0.0 + '@types/body-parser@1.19.5': dependencies: - '@babel/core': 7.22.11 - '@svgr/babel-preset': 6.5.1(@babel/core@7.22.11) - '@svgr/core': 6.5.1 - '@svgr/hast-util-to-babel-ast': 6.5.1 - svg-parser: 2.0.4 - transitivePeerDependencies: - - supports-color + '@types/connect': 3.4.38 + '@types/node': 22.13.10 - /@svgr/plugin-svgo@5.5.0: - resolution: {integrity: sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==} - engines: {node: '>=10'} + '@types/bonjour@3.5.13': dependencies: - cosmiconfig: 7.0.1 - deepmerge: 4.2.2 - svgo: 1.3.2 - dev: false + '@types/node': 22.13.10 - /@svgr/plugin-svgo@6.5.1(@svgr/core@6.5.1): - resolution: {integrity: sha512-omvZKf8ixP9z6GWgwbtmP9qQMPX4ODXi+wzbVZgomNFsUIlHA1sf4fThdwTWSsZGgvGAG6yE+b/F5gWUkcZ/iQ==} - engines: {node: '>=10'} - peerDependencies: - '@svgr/core': '*' + '@types/connect-history-api-fallback@1.5.4': dependencies: - '@svgr/core': 6.5.1 - cosmiconfig: 7.1.0 - deepmerge: 4.3.1 - svgo: 2.8.0 + '@types/express-serve-static-core': 5.0.6 + '@types/node': 22.13.10 - /@svgr/webpack@5.5.0: - resolution: {integrity: sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==} - engines: {node: '>=10'} + '@types/connect@3.4.38': dependencies: - '@babel/core': 7.19.1 - '@babel/plugin-transform-react-constant-elements': 7.18.12(@babel/core@7.19.1) - '@babel/preset-env': 7.19.1(@babel/core@7.19.1) - '@babel/preset-react': 7.18.6(@babel/core@7.19.1) - '@svgr/core': 5.5.0 - '@svgr/plugin-jsx': 5.5.0 - '@svgr/plugin-svgo': 5.5.0 - loader-utils: 2.0.2 - transitivePeerDependencies: - - supports-color - dev: false + '@types/node': 22.13.10 - /@svgr/webpack@6.5.1: - resolution: {integrity: sha512-cQ/AsnBkXPkEK8cLbv4Dm7JGXq2XrumKnL1dRpJD9rIO2fTIlJI9a1uCciYG1F2aUsox/hJQyNGbt3soDxSRkA==} - engines: {node: '>=10'} + '@types/debug@4.1.12': dependencies: - '@babel/core': 7.22.11 - '@babel/plugin-transform-react-constant-elements': 7.22.5(@babel/core@7.22.11) - '@babel/preset-env': 7.22.14(@babel/core@7.22.11) - '@babel/preset-react': 7.22.5(@babel/core@7.22.11) - '@babel/preset-typescript': 7.22.11(@babel/core@7.22.11) - '@svgr/core': 6.5.1 - '@svgr/plugin-jsx': 6.5.1(@svgr/core@6.5.1) - '@svgr/plugin-svgo': 6.5.1(@svgr/core@6.5.1) - transitivePeerDependencies: - - supports-color + '@types/ms': 2.1.0 - /@szmarczak/http-timer@1.1.2: - resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} - engines: {node: '>=6'} + '@types/eslint-scope@3.7.7': dependencies: - defer-to-connect: 1.1.3 - - /@trysound/sax@0.2.0: - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} - engines: {node: '>=10.13.0'} - - /@tsconfig/docusaurus@1.0.6: - resolution: {integrity: sha512-1QxDaP54hpzM6bq9E+yFEo4F9WbWHhsDe4vktZXF/iDlc9FqGr9qlg+3X/nuKQXx8QxHV7ue8NXFazzajsxFBA==} - dev: true + '@types/eslint': 9.6.1 + '@types/estree': 1.0.6 - /@types/body-parser@1.19.2: - resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} + '@types/eslint@9.6.1': dependencies: - '@types/connect': 3.4.35 - '@types/node': 20.5.9 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 - /@types/bonjour@3.5.10: - resolution: {integrity: sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==} + '@types/estree-jsx@1.0.5': dependencies: - '@types/node': 20.5.9 + '@types/estree': 1.0.6 - /@types/connect-history-api-fallback@1.5.0: - resolution: {integrity: sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==} - dependencies: - '@types/express-serve-static-core': 4.17.36 - '@types/node': 20.5.9 + '@types/estree@1.0.6': {} - /@types/connect@3.4.35: - resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} + '@types/express-serve-static-core@4.19.6': dependencies: - '@types/node': 20.5.9 + '@types/node': 22.13.10 + '@types/qs': 6.9.18 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.4 - /@types/eslint-scope@3.7.4: - resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} + '@types/express-serve-static-core@5.0.6': dependencies: - '@types/eslint': 8.44.2 - '@types/estree': 1.0.1 + '@types/node': 22.13.10 + '@types/qs': 6.9.18 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.4 - /@types/eslint@8.44.2: - resolution: {integrity: sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==} + '@types/express@4.17.21': dependencies: - '@types/estree': 1.0.1 - '@types/json-schema': 7.0.12 - - /@types/estree@1.0.1: - resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} + '@types/body-parser': 1.19.5 + '@types/express-serve-static-core': 4.19.6 + '@types/qs': 6.9.18 + '@types/serve-static': 1.15.7 - /@types/express-serve-static-core@4.17.36: - resolution: {integrity: sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==} - dependencies: - '@types/node': 20.5.9 - '@types/qs': 6.9.8 - '@types/range-parser': 1.2.4 - '@types/send': 0.17.1 + '@types/gtag.js@0.0.12': {} - /@types/express@4.17.17: - resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} + '@types/hast@3.0.4': dependencies: - '@types/body-parser': 1.19.2 - '@types/express-serve-static-core': 4.17.36 - '@types/qs': 6.9.8 - '@types/serve-static': 1.15.2 + '@types/unist': 3.0.3 - /@types/hast@2.3.5: - resolution: {integrity: sha512-SvQi0L/lNpThgPoleH53cdjB3y9zpLlVjRbqB3rH8hx1jiRSBGAhyjV3H+URFjNVRqt2EdYNrbZE5IsGlNfpRg==} - dependencies: - '@types/unist': 2.0.8 + '@types/history@4.7.11': {} - /@types/history@4.7.11: - resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==} + '@types/html-minifier-terser@6.1.0': {} - /@types/html-minifier-terser@6.1.0: - resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} + '@types/http-cache-semantics@4.0.4': {} - /@types/http-errors@2.0.1: - resolution: {integrity: sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==} + '@types/http-errors@2.0.4': {} - /@types/http-proxy@1.17.11: - resolution: {integrity: sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==} + '@types/http-proxy@1.17.16': dependencies: - '@types/node': 20.5.9 + '@types/node': 22.13.10 - /@types/istanbul-lib-coverage@2.0.4: - resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} + '@types/istanbul-lib-coverage@2.0.6': {} - /@types/istanbul-lib-report@3.0.0: - resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} + '@types/istanbul-lib-report@3.0.3': dependencies: - '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-lib-coverage': 2.0.6 - /@types/istanbul-reports@3.0.1: - resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} + '@types/istanbul-reports@3.0.4': dependencies: - '@types/istanbul-lib-report': 3.0.0 + '@types/istanbul-lib-report': 3.0.3 - /@types/json-schema@7.0.11: - resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} + '@types/json-schema@7.0.15': {} - /@types/json-schema@7.0.12: - resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} + '@types/json5@0.0.29': {} - /@types/json5@0.0.29: - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - dev: true - - /@types/keyv@3.1.4: - resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} - dependencies: - '@types/node': 20.5.9 - - /@types/lodash-es@4.17.6: - resolution: {integrity: sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==} + '@types/lodash-es@4.17.12': dependencies: - '@types/lodash': 4.14.191 - dev: false + '@types/lodash': 4.17.16 - /@types/lodash@4.14.191: - resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==} - dev: false + '@types/lodash@4.17.16': {} - /@types/mdast@3.0.10: - resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==} + '@types/mdast@4.0.4': dependencies: - '@types/unist': 2.0.6 - dev: true + '@types/unist': 3.0.3 - /@types/mdast@3.0.12: - resolution: {integrity: sha512-DT+iNIRNX884cx0/Q1ja7NyUPpZuv0KPyL5rGNxm1WC1OtHstl7n4Jb7nk+xacNShQMbczJjt8uFzznpp6kYBg==} - dependencies: - '@types/unist': 2.0.8 + '@types/mdx@2.0.13': {} - /@types/mime@1.3.2: - resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} + '@types/mime@1.3.5': {} - /@types/mime@3.0.1: - resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} + '@types/ms@2.1.0': {} - /@types/minimist@1.2.1: - resolution: {integrity: sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==} - dev: true + '@types/node-forge@1.3.11': + dependencies: + '@types/node': 22.13.10 - /@types/node@17.0.45: - resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} - dev: false + '@types/node@17.0.45': {} - /@types/node@20.5.9: - resolution: {integrity: sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==} + '@types/node@22.13.10': + dependencies: + undici-types: 6.20.0 - /@types/normalize-package-data@2.4.0: - resolution: {integrity: sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==} - dev: true + '@types/normalize-package-data@2.4.4': {} - /@types/parse-json@4.0.0: - resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} + '@types/parse-json@4.0.2': {} - /@types/parse5@5.0.3: - resolution: {integrity: sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==} + '@types/prismjs@1.26.5': {} - /@types/prop-types@15.7.5: - resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + '@types/prop-types@15.7.14': {} - /@types/q@1.5.4: - resolution: {integrity: sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==} - dev: false + '@types/qs@6.9.18': {} - /@types/qs@6.9.8: - resolution: {integrity: sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==} + '@types/range-parser@1.2.7': {} - /@types/range-parser@1.2.4: - resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} + '@types/react-dom@18.3.5(@types/react@18.3.18)': + dependencies: + '@types/react': 18.3.18 - /@types/react-router-config@5.0.7: - resolution: {integrity: sha512-pFFVXUIydHlcJP6wJm7sDii5mD/bCmmAY0wQzq+M+uX7bqS95AQqHZWP1iNMKrWVQSuHIzj5qi9BvrtLX2/T4w==} + '@types/react-router-config@5.0.11': dependencies: '@types/history': 4.7.11 - '@types/react': 18.2.21 + '@types/react': 18.3.18 '@types/react-router': 5.1.20 - /@types/react-router-dom@5.3.3: - resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} + '@types/react-router-dom@5.3.3': dependencies: '@types/history': 4.7.11 - '@types/react': 18.2.21 + '@types/react': 18.3.18 '@types/react-router': 5.1.20 - /@types/react-router@5.1.20: - resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} + '@types/react-router@5.1.20': dependencies: '@types/history': 4.7.11 - '@types/react': 18.2.21 - - /@types/react@18.2.21: - resolution: {integrity: sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==} - dependencies: - '@types/prop-types': 15.7.5 - '@types/scheduler': 0.16.3 - csstype: 3.1.2 + '@types/react': 18.3.18 - /@types/responselike@1.0.0: - resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} + '@types/react@18.3.18': dependencies: - '@types/node': 20.5.9 + '@types/prop-types': 15.7.14 + csstype: 3.1.3 - /@types/retry@0.12.0: - resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@types/retry@0.12.0': {} - /@types/sax@1.2.4: - resolution: {integrity: sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==} + '@types/sax@1.2.7': dependencies: - '@types/node': 17.0.45 - dev: false + '@types/node': 22.13.10 - /@types/scheduler@0.16.3: - resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} + '@types/semver@7.5.8': {} - /@types/send@0.17.1: - resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} + '@types/send@0.17.4': dependencies: - '@types/mime': 1.3.2 - '@types/node': 20.5.9 + '@types/mime': 1.3.5 + '@types/node': 22.13.10 - /@types/serve-index@1.9.1: - resolution: {integrity: sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==} + '@types/serve-index@1.9.4': dependencies: - '@types/express': 4.17.17 + '@types/express': 4.17.21 - /@types/serve-static@1.15.2: - resolution: {integrity: sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==} + '@types/serve-static@1.15.7': dependencies: - '@types/http-errors': 2.0.1 - '@types/mime': 3.0.1 - '@types/node': 20.5.9 + '@types/http-errors': 2.0.4 + '@types/node': 22.13.10 + '@types/send': 0.17.4 - /@types/sockjs@0.3.33: - resolution: {integrity: sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==} + '@types/sockjs@0.3.36': dependencies: - '@types/node': 20.5.9 + '@types/node': 22.13.10 - /@types/unist@2.0.6: - resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} + '@types/unist@2.0.11': {} - /@types/unist@2.0.8: - resolution: {integrity: sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==} + '@types/unist@3.0.3': {} - /@types/ws@8.5.5: - resolution: {integrity: sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==} + '@types/ws@8.18.0': dependencies: - '@types/node': 20.5.9 + '@types/node': 22.13.10 - /@types/yargs-parser@21.0.0: - resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} + '@types/yargs-parser@21.0.3': {} - /@types/yargs@17.0.24: - resolution: {integrity: sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==} + '@types/yargs@17.0.33': dependencies: - '@types/yargs-parser': 21.0.0 + '@types/yargs-parser': 21.0.3 - /@typescript-eslint/eslint-plugin@5.38.0(@typescript-eslint/parser@5.38.0)(eslint@7.32.0)(typescript@4.9.5): - resolution: {integrity: sha512-GgHi/GNuUbTOeoJiEANi0oI6fF3gBQc3bGFYj40nnAPCbhrtEDf2rjBmefFadweBmO1Du1YovHeDP2h5JLhtTQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - '@typescript-eslint/parser': ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0)(typescript@5.8.2)': dependencies: - '@typescript-eslint/parser': 5.38.0(eslint@7.32.0)(typescript@4.9.5) - '@typescript-eslint/scope-manager': 5.38.0 - '@typescript-eslint/type-utils': 5.38.0(eslint@7.32.0)(typescript@4.9.5) - '@typescript-eslint/utils': 5.38.0(eslint@7.32.0)(typescript@4.9.5) - debug: 4.3.4 + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 6.21.0(eslint@7.32.0)(typescript@5.8.2) + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/type-utils': 6.21.0(eslint@7.32.0)(typescript@5.8.2) + '@typescript-eslint/utils': 6.21.0(eslint@7.32.0)(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.0 eslint: 7.32.0 - ignore: 5.2.0 - regexpp: 3.2.0 - semver: 7.3.7 - tsutils: 3.21.0(typescript@4.9.5) - typescript: 4.9.5 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + semver: 7.7.1 + ts-api-utils: 1.4.3(typescript@5.8.2) + optionalDependencies: + typescript: 5.8.2 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/parser@5.38.0(eslint@7.32.0)(typescript@4.9.5): - resolution: {integrity: sha512-/F63giJGLDr0ms1Cr8utDAxP2SPiglaD6V+pCOcG35P2jCqdfR7uuEhz1GIC3oy4hkUF8xA1XSXmd9hOh/a5EA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/parser@6.21.0(eslint@7.32.0)(typescript@5.8.2)': dependencies: - '@typescript-eslint/scope-manager': 5.38.0 - '@typescript-eslint/types': 5.38.0 - '@typescript-eslint/typescript-estree': 5.38.0(typescript@4.9.5) - debug: 4.3.4 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.0 eslint: 7.32.0 - typescript: 4.9.5 + optionalDependencies: + typescript: 5.8.2 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/scope-manager@5.38.0: - resolution: {integrity: sha512-ByhHIuNyKD9giwkkLqzezZ9y5bALW8VNY6xXcP+VxoH4JBDKjU5WNnsiD4HJdglHECdV+lyaxhvQjTUbRboiTA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@typescript-eslint/scope-manager@6.21.0': dependencies: - '@typescript-eslint/types': 5.38.0 - '@typescript-eslint/visitor-keys': 5.38.0 - dev: true + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 - /@typescript-eslint/type-utils@5.38.0(eslint@7.32.0)(typescript@4.9.5): - resolution: {integrity: sha512-iZq5USgybUcj/lfnbuelJ0j3K9dbs1I3RICAJY9NZZpDgBYXmuUlYQGzftpQA9wC8cKgtS6DASTvF3HrXwwozA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: '*' - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/type-utils@6.21.0(eslint@7.32.0)(typescript@5.8.2)': dependencies: - '@typescript-eslint/typescript-estree': 5.38.0(typescript@4.9.5) - '@typescript-eslint/utils': 5.38.0(eslint@7.32.0)(typescript@4.9.5) - debug: 4.3.4 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.8.2) + '@typescript-eslint/utils': 6.21.0(eslint@7.32.0)(typescript@5.8.2) + debug: 4.4.0 eslint: 7.32.0 - tsutils: 3.21.0(typescript@4.9.5) - typescript: 4.9.5 + ts-api-utils: 1.4.3(typescript@5.8.2) + optionalDependencies: + typescript: 5.8.2 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/types@5.38.0: - resolution: {integrity: sha512-HHu4yMjJ7i3Cb+8NUuRCdOGu2VMkfmKyIJsOr9PfkBVYLYrtMCK/Ap50Rpov+iKpxDTfnqvDbuPLgBE5FwUNfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true + '@typescript-eslint/types@6.21.0': {} - /@typescript-eslint/typescript-estree@5.38.0(typescript@4.9.5): - resolution: {integrity: sha512-6P0RuphkR+UuV7Avv7MU3hFoWaGcrgOdi8eTe1NwhMp2/GjUJoODBTRWzlHpZh6lFOaPmSvgxGlROa0Sg5Zbyg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.8.2)': dependencies: - '@typescript-eslint/types': 5.38.0 - '@typescript-eslint/visitor-keys': 5.38.0 - debug: 4.3.4 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.0 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.3.7 - tsutils: 3.21.0(typescript@4.9.5) - typescript: 4.9.5 + minimatch: 9.0.3 + semver: 7.7.1 + ts-api-utils: 1.4.3(typescript@5.8.2) + optionalDependencies: + typescript: 5.8.2 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/utils@5.38.0(eslint@7.32.0)(typescript@4.9.5): - resolution: {integrity: sha512-6sdeYaBgk9Fh7N2unEXGz+D+som2QCQGPAf1SxrkEr+Z32gMreQ0rparXTNGRRfYUWk/JzbGdcM8NSSd6oqnTA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@typescript-eslint/utils@6.21.0(eslint@7.32.0)(typescript@5.8.2)': dependencies: - '@types/json-schema': 7.0.11 - '@typescript-eslint/scope-manager': 5.38.0 - '@typescript-eslint/types': 5.38.0 - '@typescript-eslint/typescript-estree': 5.38.0(typescript@4.9.5) + '@eslint-community/eslint-utils': 4.5.1(eslint@7.32.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.8.2) eslint: 7.32.0 - eslint-scope: 5.1.1 - eslint-utils: 3.0.0(eslint@7.32.0) + semver: 7.7.1 transitivePeerDependencies: - supports-color - typescript - dev: true - /@typescript-eslint/visitor-keys@5.38.0: - resolution: {integrity: sha512-MxnrdIyArnTi+XyFLR+kt/uNAcdOnmT+879os7qDRI+EYySR4crXJq9BXPfRzzLGq0wgxkwidrCJ9WCAoacm1w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@typescript-eslint/visitor-keys@6.21.0': dependencies: - '@typescript-eslint/types': 5.38.0 - eslint-visitor-keys: 3.3.0 - dev: true + '@typescript-eslint/types': 6.21.0 + eslint-visitor-keys: 3.4.3 - /@webassemblyjs/ast@1.11.6: - resolution: {integrity: sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==} + '@ungap/structured-clone@1.3.0': {} + + '@webassemblyjs/ast@1.14.1': dependencies: - '@webassemblyjs/helper-numbers': 1.11.6 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/helper-numbers': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - /@webassemblyjs/floating-point-hex-parser@1.11.6: - resolution: {integrity: sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==} + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} - /@webassemblyjs/helper-api-error@1.11.6: - resolution: {integrity: sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==} + '@webassemblyjs/helper-api-error@1.13.2': {} - /@webassemblyjs/helper-buffer@1.11.6: - resolution: {integrity: sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==} + '@webassemblyjs/helper-buffer@1.14.1': {} - /@webassemblyjs/helper-numbers@1.11.6: - resolution: {integrity: sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==} + '@webassemblyjs/helper-numbers@1.13.2': dependencies: - '@webassemblyjs/floating-point-hex-parser': 1.11.6 - '@webassemblyjs/helper-api-error': 1.11.6 + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 '@xtuc/long': 4.2.2 - /@webassemblyjs/helper-wasm-bytecode@1.11.6: - resolution: {integrity: sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==} + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} - /@webassemblyjs/helper-wasm-section@1.11.6: - resolution: {integrity: sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==} + '@webassemblyjs/helper-wasm-section@1.14.1': dependencies: - '@webassemblyjs/ast': 1.11.6 - '@webassemblyjs/helper-buffer': 1.11.6 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - '@webassemblyjs/wasm-gen': 1.11.6 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 - /@webassemblyjs/ieee754@1.11.6: - resolution: {integrity: sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==} + '@webassemblyjs/ieee754@1.13.2': dependencies: '@xtuc/ieee754': 1.2.0 - /@webassemblyjs/leb128@1.11.6: - resolution: {integrity: sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==} + '@webassemblyjs/leb128@1.13.2': dependencies: '@xtuc/long': 4.2.2 - /@webassemblyjs/utf8@1.11.6: - resolution: {integrity: sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==} + '@webassemblyjs/utf8@1.13.2': {} - /@webassemblyjs/wasm-edit@1.11.6: - resolution: {integrity: sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==} + '@webassemblyjs/wasm-edit@1.14.1': dependencies: - '@webassemblyjs/ast': 1.11.6 - '@webassemblyjs/helper-buffer': 1.11.6 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - '@webassemblyjs/helper-wasm-section': 1.11.6 - '@webassemblyjs/wasm-gen': 1.11.6 - '@webassemblyjs/wasm-opt': 1.11.6 - '@webassemblyjs/wasm-parser': 1.11.6 - '@webassemblyjs/wast-printer': 1.11.6 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 - /@webassemblyjs/wasm-gen@1.11.6: - resolution: {integrity: sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==} + '@webassemblyjs/wasm-gen@1.14.1': dependencies: - '@webassemblyjs/ast': 1.11.6 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - '@webassemblyjs/ieee754': 1.11.6 - '@webassemblyjs/leb128': 1.11.6 - '@webassemblyjs/utf8': 1.11.6 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 - /@webassemblyjs/wasm-opt@1.11.6: - resolution: {integrity: sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==} + '@webassemblyjs/wasm-opt@1.14.1': dependencies: - '@webassemblyjs/ast': 1.11.6 - '@webassemblyjs/helper-buffer': 1.11.6 - '@webassemblyjs/wasm-gen': 1.11.6 - '@webassemblyjs/wasm-parser': 1.11.6 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 - /@webassemblyjs/wasm-parser@1.11.6: - resolution: {integrity: sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==} + '@webassemblyjs/wasm-parser@1.14.1': dependencies: - '@webassemblyjs/ast': 1.11.6 - '@webassemblyjs/helper-api-error': 1.11.6 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - '@webassemblyjs/ieee754': 1.11.6 - '@webassemblyjs/leb128': 1.11.6 - '@webassemblyjs/utf8': 1.11.6 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 - /@webassemblyjs/wast-printer@1.11.6: - resolution: {integrity: sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==} + '@webassemblyjs/wast-printer@1.14.1': dependencies: - '@webassemblyjs/ast': 1.11.6 + '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - /@xtuc/ieee754@1.2.0: - resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + '@xtuc/ieee754@1.2.0': {} - /@xtuc/long@4.2.2: - resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + '@xtuc/long@4.2.2': {} - /accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} + accepts@1.3.8: dependencies: mime-types: 2.1.35 negotiator: 0.6.3 - /acorn-import-assertions@1.9.0(acorn@8.10.0): - resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} - peerDependencies: - acorn: ^8 + acorn-jsx@5.3.2(acorn@7.4.1): dependencies: - acorn: 8.10.0 + acorn: 7.4.1 - /acorn-jsx@5.3.1(acorn@7.4.1): - resolution: {integrity: sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-jsx@5.3.2(acorn@8.14.1): dependencies: - acorn: 7.4.1 + acorn: 8.14.1 - /acorn-walk@8.2.0: - resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} - engines: {node: '>=0.4.0'} + acorn-walk@8.3.4: + dependencies: + acorn: 8.14.1 - /acorn@7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} - hasBin: true + acorn@7.4.1: {} - /acorn@8.10.0: - resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} - engines: {node: '>=0.4.0'} - hasBin: true + acorn@8.14.1: {} - /address@1.2.2: - resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} - engines: {node: '>= 10.0.0'} + address@1.2.2: {} - /aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} + aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 indent-string: 4.0.0 - /ajv-formats@2.1.1(ajv@8.12.0): - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - dependencies: - ajv: 8.12.0 + ajv-formats@2.1.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 - /ajv-keywords@3.5.2(ajv@6.12.6): - resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} - peerDependencies: - ajv: ^6.9.1 + ajv-keywords@3.5.2(ajv@6.12.6): dependencies: ajv: 6.12.6 - /ajv-keywords@5.1.0(ajv@8.12.0): - resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} - peerDependencies: - ajv: ^8.8.2 + ajv-keywords@5.1.0(ajv@8.17.1): dependencies: - ajv: 8.12.0 + ajv: 8.17.1 fast-deep-equal: 3.1.3 - /ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 - /ajv@8.11.0: - resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==} + ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 + fast-uri: 3.0.6 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - uri-js: 4.4.1 - - /ajv@8.12.0: - resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} - dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 - /algoliasearch-helper@3.14.0(algoliasearch@4.19.1): - resolution: {integrity: sha512-gXDXzsSS0YANn5dHr71CUXOo84cN4azhHKUbg71vAWnH+1JBiR4jf7to3t3JHXknXkbV0F7f055vUSBKrltHLQ==} - peerDependencies: - algoliasearch: '>= 3.1 < 6' + algoliasearch-helper@3.24.2(algoliasearch@5.21.0): dependencies: '@algolia/events': 4.0.1 - algoliasearch: 4.19.1 - dev: false - - /algoliasearch@4.19.1: - resolution: {integrity: sha512-IJF5b93b2MgAzcE/tuzW0yOPnuUyRgGAtaPv5UUywXM8kzqfdwZTO4sPJBzoGz1eOy6H9uEchsJsBFTELZSu+g==} - dependencies: - '@algolia/cache-browser-local-storage': 4.19.1 - '@algolia/cache-common': 4.19.1 - '@algolia/cache-in-memory': 4.19.1 - '@algolia/client-account': 4.19.1 - '@algolia/client-analytics': 4.19.1 - '@algolia/client-common': 4.19.1 - '@algolia/client-personalization': 4.19.1 - '@algolia/client-search': 4.19.1 - '@algolia/logger-common': 4.19.1 - '@algolia/logger-console': 4.19.1 - '@algolia/requester-browser-xhr': 4.19.1 - '@algolia/requester-common': 4.19.1 - '@algolia/requester-node-http': 4.19.1 - '@algolia/transporter': 4.19.1 - dev: false - - /all-contributors-cli@6.20.0: - resolution: {integrity: sha512-trEQlL1s1u8FSWSwY2w9uL4GCG7Fo9HIW5rm5LtlE0SQHSolfXQBzJib07Qes5j52/t72wjuE6sEKkuRrwiuuQ==} - engines: {node: '>=4'} - hasBin: true - dependencies: - '@babel/runtime': 7.19.0 - async: 3.2.2 + algoliasearch: 5.21.0 + + algoliasearch@5.21.0: + dependencies: + '@algolia/client-abtesting': 5.21.0 + '@algolia/client-analytics': 5.21.0 + '@algolia/client-common': 5.21.0 + '@algolia/client-insights': 5.21.0 + '@algolia/client-personalization': 5.21.0 + '@algolia/client-query-suggestions': 5.21.0 + '@algolia/client-search': 5.21.0 + '@algolia/ingestion': 1.21.0 + '@algolia/monitoring': 1.21.0 + '@algolia/recommend': 5.21.0 + '@algolia/requester-browser-xhr': 5.21.0 + '@algolia/requester-fetch': 5.21.0 + '@algolia/requester-node-http': 5.21.0 + + all-contributors-cli@6.26.1: + dependencies: + '@babel/runtime': 7.26.10 + async: 3.2.6 chalk: 4.1.2 didyoumean: 1.2.2 inquirer: 7.3.3 - json-fixer: 1.6.12 + json-fixer: 1.6.15 lodash: 4.17.21 - node-fetch: 2.6.6 + node-fetch: 2.7.0 pify: 5.0.0 yargs: 15.4.1 - dev: true + optionalDependencies: + prettier: 2.8.8 + transitivePeerDependencies: + - encoding - /ansi-align@3.0.1: - resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-align@3.0.1: dependencies: string-width: 4.2.3 - /ansi-colors@4.1.1: - resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} - engines: {node: '>=6'} + ansi-colors@4.1.3: {} - /ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} + ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 - dev: true - /ansi-html-community@0.0.8: - resolution: {integrity: sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==} - engines: {'0': node >= 0.8.0} - hasBin: true + ansi-html-community@0.0.8: {} - /ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} + ansi-regex@5.0.1: {} - /ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} - engines: {node: '>=12'} + ansi-regex@6.1.0: {} - /ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} + ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 - /ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - /ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} + ansi-styles@6.2.1: {} - /anymatch@3.1.2: - resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} - engines: {node: '>= 8'} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 - /arg@5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - dev: false + arg@5.0.2: {} - /argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - /array-buffer-byte-length@1.0.0: - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} - dependencies: - call-bind: 1.0.2 - is-array-buffer: 3.0.2 - dev: true - - /array-flatten@1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - - /array-flatten@2.1.2: - resolution: {integrity: sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==} - - /array-includes@3.1.3: - resolution: {integrity: sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.18.3 - get-intrinsic: 1.1.3 - is-string: 1.0.6 - dev: true + argparse@2.0.1: {} - /array-includes@3.1.6: - resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==} - engines: {node: '>= 0.4'} + array-buffer-byte-length@1.0.2: dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 - get-intrinsic: 1.2.1 - is-string: 1.0.7 - dev: true - - /array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} + call-bound: 1.0.4 + is-array-buffer: 3.0.5 - /array.prototype.findlastindex@1.2.3: - resolution: {integrity: sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 - es-shim-unscopables: 1.0.0 - get-intrinsic: 1.2.1 - dev: true + array-flatten@1.1.1: {} - /array.prototype.flat@1.2.4: - resolution: {integrity: sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==} - engines: {node: '>= 0.4'} + array-includes@3.1.8: dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.18.3 - dev: true + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 - /array.prototype.flat@1.3.1: - resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 - es-shim-unscopables: 1.0.0 - dev: true + array-union@2.1.0: {} - /array.prototype.flatmap@1.2.4: - resolution: {integrity: sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q==} - engines: {node: '>= 0.4'} + array.prototype.flat@1.3.3: dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.18.3 - function-bind: 1.1.1 - dev: true + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-shim-unscopables: 1.1.0 - /array.prototype.flatmap@1.3.1: - resolution: {integrity: sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==} - engines: {node: '>= 0.4'} + array.prototype.flatmap@1.3.3: dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 - es-shim-unscopables: 1.0.0 - dev: true + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-shim-unscopables: 1.1.0 - /arraybuffer.prototype.slice@1.0.1: - resolution: {integrity: sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==} - engines: {node: '>= 0.4'} + arraybuffer.prototype.slice@1.0.4: dependencies: - array-buffer-byte-length: 1.0.0 - call-bind: 1.0.2 - define-properties: 1.2.0 - get-intrinsic: 1.2.1 - is-array-buffer: 3.0.2 - is-shared-array-buffer: 1.0.2 - dev: true + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 - /arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - dev: true - - /asap@2.0.6: - resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - dev: false + astral-regex@2.0.0: {} - /astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} + astring@1.9.0: {} - /async@3.2.2: - resolution: {integrity: sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==} - dev: true + async-function@1.0.0: {} - /at-least-node@1.0.0: - resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} - engines: {node: '>= 4.0.0'} + async@3.2.6: {} - /autoprefixer@10.4.15(postcss@8.4.29): - resolution: {integrity: sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - dependencies: - browserslist: 4.21.10 - caniuse-lite: 1.0.30001525 - fraction.js: 4.3.6 - normalize-range: 0.1.2 - picocolors: 1.0.0 - postcss: 8.4.29 - postcss-value-parser: 4.2.0 + at-least-node@1.0.0: {} - /autoprefixer@9.8.6: - resolution: {integrity: sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==} - hasBin: true + autoprefixer@10.4.21(postcss@8.5.3): dependencies: - browserslist: 4.21.4 - caniuse-lite: 1.0.30001407 - colorette: 1.4.0 + browserslist: 4.24.4 + caniuse-lite: 1.0.30001704 + fraction.js: 4.3.7 normalize-range: 0.1.2 - num2fraction: 1.2.2 - postcss: 7.0.36 + picocolors: 1.1.1 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - dev: true - - /available-typed-arrays@1.0.5: - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} - engines: {node: '>= 0.4'} - dev: true - - /axios@0.25.0: - resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==} - dependencies: - follow-redirects: 1.15.2 - transitivePeerDependencies: - - debug - - /babel-loader@8.3.0(@babel/core@7.22.11)(webpack@5.88.2): - resolution: {integrity: sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==} - engines: {node: '>= 8.9'} - peerDependencies: - '@babel/core': ^7.0.0 - webpack: '>=2' - dependencies: - '@babel/core': 7.22.11 - find-cache-dir: 3.3.2 - loader-utils: 2.0.4 - make-dir: 3.1.0 - schema-utils: 2.7.1 - webpack: 5.88.2 - /babel-plugin-apply-mdx-type-prop@1.6.22(@babel/core@7.12.9): - resolution: {integrity: sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ==} - peerDependencies: - '@babel/core': ^7.11.6 + available-typed-arrays@1.0.7: dependencies: - '@babel/core': 7.12.9 - '@babel/helper-plugin-utils': 7.10.4 - '@mdx-js/util': 1.6.22 + possible-typed-array-names: 1.1.0 - /babel-plugin-dynamic-import-node@2.3.3: - resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==} - dependencies: - object.assign: 4.1.4 + b4a@1.6.7: {} - /babel-plugin-extract-import-names@1.6.22: - resolution: {integrity: sha512-yJ9BsJaISua7d8zNT7oRG1ZLBJCIdZ4PZqmH8qa9N5AK01ifk3fnkc98AXhtzE7UkfCsEumvoQWgoYLhOnJ7jQ==} + babel-loader@9.2.1(@babel/core@7.26.10)(webpack@5.98.0(@swc/core@1.11.9)): dependencies: - '@babel/helper-plugin-utils': 7.10.4 + '@babel/core': 7.26.10 + find-cache-dir: 4.0.0 + schema-utils: 4.3.0 + webpack: 5.98.0(@swc/core@1.11.9) - /babel-plugin-polyfill-corejs2@0.3.3(@babel/core@7.19.1): - resolution: {integrity: sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 + babel-plugin-dynamic-import-node@2.3.3: dependencies: - '@babel/compat-data': 7.19.1 - '@babel/core': 7.19.1 - '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.19.1) - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - dev: false + object.assign: 4.1.7 - /babel-plugin-polyfill-corejs2@0.4.5(@babel/core@7.22.11): - resolution: {integrity: sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.26.10): dependencies: - '@babel/compat-data': 7.22.9 - '@babel/core': 7.22.11 - '@babel/helper-define-polyfill-provider': 0.4.2(@babel/core@7.22.11) + '@babel/compat-data': 7.26.8 + '@babel/core': 7.26.10 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.10) semver: 6.3.1 transitivePeerDependencies: - supports-color - /babel-plugin-polyfill-corejs3@0.6.0(@babel/core@7.19.1): - resolution: {integrity: sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.26.10): dependencies: - '@babel/core': 7.19.1 - '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.19.1) - core-js-compat: 3.25.2 + '@babel/core': 7.26.10 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.10) + core-js-compat: 3.41.0 transitivePeerDependencies: - supports-color - dev: false - /babel-plugin-polyfill-corejs3@0.8.3(@babel/core@7.22.11): - resolution: {integrity: sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-polyfill-regenerator@0.6.3(@babel/core@7.26.10): dependencies: - '@babel/core': 7.22.11 - '@babel/helper-define-polyfill-provider': 0.4.2(@babel/core@7.22.11) - core-js-compat: 3.32.1 + '@babel/core': 7.26.10 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.10) transitivePeerDependencies: - supports-color - /babel-plugin-polyfill-regenerator@0.4.1(@babel/core@7.19.1): - resolution: {integrity: sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.1 - '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.19.1) - transitivePeerDependencies: - - supports-color - dev: false + bail@2.0.2: {} - /babel-plugin-polyfill-regenerator@0.5.2(@babel/core@7.22.11): - resolution: {integrity: sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + balanced-match@1.0.2: {} + + balanced-match@2.0.0: {} + + bare-events@2.5.4: + optional: true + + bare-fs@4.0.1: dependencies: - '@babel/core': 7.22.11 - '@babel/helper-define-polyfill-provider': 0.4.2(@babel/core@7.22.11) + bare-events: 2.5.4 + bare-path: 3.0.0 + bare-stream: 2.6.5(bare-events@2.5.4) transitivePeerDependencies: - - supports-color + - bare-buffer + optional: true - /bail@1.0.5: - resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==} + bare-os@3.6.0: + optional: true - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bare-path@3.0.0: + dependencies: + bare-os: 3.6.0 + optional: true - /balanced-match@2.0.0: - resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} - dev: true + bare-stream@2.6.5(bare-events@2.5.4): + dependencies: + streamx: 2.22.0 + optionalDependencies: + bare-events: 2.5.4 + optional: true - /base16@1.0.0: - resolution: {integrity: sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==} - dev: false + base64-arraybuffer@1.0.2: {} - /base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: false + base64-js@1.5.1: {} - /batch@0.6.1: - resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} + batch@0.6.1: {} - /big.js@5.2.2: - resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} + big.js@5.2.2: {} - /binary-extensions@2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} - engines: {node: '>=8'} + binary-extensions@2.3.0: {} - /bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + bl@4.1.0: dependencies: buffer: 5.7.1 inherits: 2.0.4 - readable-stream: 3.6.0 - dev: false + readable-stream: 3.6.2 - /body-parser@1.20.1: - resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + body-parser@1.20.3: dependencies: bytes: 3.1.2 content-type: 1.0.5 @@ -5287,234 +10369,188 @@ packages: http-errors: 2.0.0 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.11.0 - raw-body: 2.5.1 + qs: 6.13.0 + raw-body: 2.5.2 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: - supports-color - /bonjour-service@1.1.1: - resolution: {integrity: sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==} + bonjour-service@1.3.0: dependencies: - array-flatten: 2.1.2 - dns-equal: 1.0.0 fast-deep-equal: 3.1.3 multicast-dns: 7.2.5 - /boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + boolbase@1.0.0: {} - /boxen@5.1.2: - resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} - engines: {node: '>=10'} + boxen@6.2.1: dependencies: ansi-align: 3.0.1 camelcase: 6.3.0 chalk: 4.1.2 - cli-boxes: 2.2.1 - string-width: 4.2.3 - type-fest: 0.20.2 - widest-line: 3.1.0 - wrap-ansi: 7.0.0 + cli-boxes: 3.0.0 + string-width: 5.1.2 + type-fest: 2.19.0 + widest-line: 4.0.1 + wrap-ansi: 8.1.0 - /boxen@6.2.1: - resolution: {integrity: sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + boxen@7.1.1: dependencies: ansi-align: 3.0.1 - camelcase: 6.3.0 - chalk: 4.1.2 + camelcase: 7.0.1 + chalk: 5.4.1 cli-boxes: 3.0.0 string-width: 5.1.2 type-fest: 2.19.0 widest-line: 4.0.1 wrap-ansi: 8.1.0 - /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - /braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} + brace-expansion@2.0.1: dependencies: - fill-range: 7.0.1 + balanced-match: 1.0.2 - /browserslist@4.21.10: - resolution: {integrity: sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true + braces@3.0.3: dependencies: - caniuse-lite: 1.0.30001525 - electron-to-chromium: 1.4.508 - node-releases: 2.0.13 - update-browserslist-db: 1.0.11(browserslist@4.21.10) + fill-range: 7.1.1 - /browserslist@4.21.4: - resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true + browserslist@4.24.4: dependencies: - caniuse-lite: 1.0.30001407 - electron-to-chromium: 1.4.255 - node-releases: 2.0.6 - update-browserslist-db: 1.0.9(browserslist@4.21.4) + caniuse-lite: 1.0.30001704 + electron-to-chromium: 1.5.119 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.24.4) - /buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer-from@1.1.2: {} - /buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@5.7.1: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: false - /builtin-modules@3.2.0: - resolution: {integrity: sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==} - engines: {node: '>=6'} - dev: true + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 - /bytes@3.0.0: - resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} - engines: {node: '>= 0.8'} + builtin-modules@3.3.0: {} - /bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} + bytes@3.0.0: {} - /cacheable-request@6.1.0: - resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==} - engines: {node: '>=8'} + bytes@3.1.2: {} + + cacheable-lookup@7.0.0: {} + + cacheable-request@10.2.14: dependencies: - clone-response: 1.0.3 - get-stream: 5.2.0 + '@types/http-cache-semantics': 4.0.4 + get-stream: 6.0.1 http-cache-semantics: 4.1.1 - keyv: 3.1.0 - lowercase-keys: 2.0.0 - normalize-url: 4.5.1 - responselike: 1.0.2 + keyv: 4.5.4 + mimic-response: 4.0.0 + normalize-url: 8.0.1 + responselike: 3.0.0 - /call-bind@1.0.2: - resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + cacheable@1.8.9: dependencies: - function-bind: 1.1.1 - get-intrinsic: 1.1.3 + hookified: 1.8.1 + keyv: 5.3.1 - /callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 - /camel-case@4.1.2: - resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} + call-bind@1.0.8: dependencies: - pascal-case: 3.1.2 - tslib: 2.6.2 + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 - /camelcase-css@2.0.1: - resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} - engines: {node: '>= 6'} + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 - /camelcase-keys@6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} + callsites@3.1.0: {} + + camel-case@4.1.2: dependencies: - camelcase: 5.3.1 - map-obj: 4.2.1 - quick-lru: 4.0.1 - dev: true + pascal-case: 3.1.2 + tslib: 2.8.1 - /camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - dev: true + camelcase@5.3.1: {} - /camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} + camelcase@6.3.0: {} - /caniuse-api@3.0.0: - resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} + camelcase@7.0.1: {} + + caniuse-api@3.0.0: dependencies: - browserslist: 4.21.10 - caniuse-lite: 1.0.30001525 + browserslist: 4.24.4 + caniuse-lite: 1.0.30001704 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 - /caniuse-lite@1.0.30001407: - resolution: {integrity: sha512-4ydV+t4P7X3zH83fQWNDX/mQEzYomossfpViCOx9zHBSMV+rIe3LFqglHHtVyvNl1FhTNxPxs3jei82iqOW04w==} - - /caniuse-lite@1.0.30001525: - resolution: {integrity: sha512-/3z+wB4icFt3r0USMwxujAqRvaD/B7rvGTsKhbhSQErVrJvkZCLhgNLJxU8MevahQVH6hCU9FsHdNUFbiwmE7Q==} + caniuse-lite@1.0.30001704: {} - /ccount@1.1.0: - resolution: {integrity: sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==} + ccount@2.0.1: {} - /chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 - /chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - /character-entities-legacy@1.1.4: - resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} + chalk@5.4.1: {} - /character-entities@1.2.4: - resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} + char-regex@1.0.2: {} - /character-reference-invalid@1.1.4: - resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} + character-entities-html4@2.1.0: {} - /chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - dev: true + character-entities-legacy@3.0.0: {} - /charenc@0.0.2: - resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} - dev: false + character-entities@2.0.2: {} - /cheerio-select@2.1.0: - resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + character-reference-invalid@2.0.1: {} + + chardet@0.7.0: {} + + charenc@0.0.2: {} + + cheerio-select@2.1.0: dependencies: boolbase: 1.0.0 css-select: 5.1.0 css-what: 6.1.0 domelementtype: 2.3.0 domhandler: 5.0.3 - domutils: 3.1.0 - dev: false + domutils: 3.2.2 - /cheerio@1.0.0-rc.12: - resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} - engines: {node: '>= 6'} + cheerio@1.0.0-rc.12: dependencies: cheerio-select: 2.1.0 dom-serializer: 2.0.0 domhandler: 5.0.3 - domutils: 3.1.0 + domutils: 3.2.2 htmlparser2: 8.0.2 - parse5: 7.1.2 - parse5-htmlparser2-tree-adapter: 7.0.0 - dev: false + parse5: 7.2.1 + parse5-htmlparser2-tree-adapter: 7.1.0 - /chokidar@3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} - engines: {node: '>= 8.10.0'} + chokidar@3.6.0: dependencies: - anymatch: 3.1.2 - braces: 3.0.2 + anymatch: 3.1.3 + braces: 3.0.3 glob-parent: 5.1.2 is-binary-path: 2.1.0 is-glob: 4.0.3 @@ -5523,456 +10559,268 @@ packages: optionalDependencies: fsevents: 2.3.3 - /chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - dev: false - - /chrome-trace-event@1.0.3: - resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} - engines: {node: '>=6.0'} + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 - /ci-info@2.0.0: - resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + chownr@1.1.4: {} - /ci-info@3.2.0: - resolution: {integrity: sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==} - dev: true + chrome-trace-event@1.0.4: {} - /ci-info@3.8.0: - resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} - engines: {node: '>=8'} + ci-info@3.9.0: {} - /classnames@2.3.1: - resolution: {integrity: sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==} - dev: false + classnames@2.5.1: {} - /clean-css@5.3.2: - resolution: {integrity: sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==} - engines: {node: '>= 10.0'} + clean-css@5.3.3: dependencies: source-map: 0.6.1 - /clean-regexp@1.0.0: - resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} - engines: {node: '>=4'} + clean-regexp@1.0.0: dependencies: escape-string-regexp: 1.0.5 - dev: true - - /clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} - /cli-boxes@2.2.1: - resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} - engines: {node: '>=6'} + clean-stack@2.2.0: {} - /cli-boxes@3.0.0: - resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} - engines: {node: '>=10'} + cli-boxes@3.0.0: {} - /cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} + cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 - dev: true - /cli-table3@0.6.3: - resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} - engines: {node: 10.* || >= 12.*} + cli-table3@0.6.5: dependencies: string-width: 4.2.3 optionalDependencies: '@colors/colors': 1.5.0 - /cli-width@3.0.0: - resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} - engines: {node: '>= 10'} - dev: true + cli-width@3.0.0: {} - /cliui@6.0.0: - resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + cliui@6.0.0: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 6.2.0 - dev: true - /clone-deep@4.0.1: - resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} - engines: {node: '>=6'} + clone-deep@4.0.1: dependencies: is-plain-object: 2.0.4 kind-of: 6.0.3 shallow-clone: 3.0.1 - /clone-regexp@2.2.0: - resolution: {integrity: sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==} - engines: {node: '>=6'} - dependencies: - is-regexp: 2.1.0 - dev: true - - /clone-response@1.0.3: - resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} - dependencies: - mimic-response: 1.0.1 - - /clsx@1.1.1: - resolution: {integrity: sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==} - engines: {node: '>=6'} - dev: false - - /clsx@1.2.1: - resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} - engines: {node: '>=6'} - dev: false - - /coa@2.0.2: - resolution: {integrity: sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==} - engines: {node: '>= 4.0'} - dependencies: - '@types/q': 1.5.4 - chalk: 2.4.2 - q: 1.5.1 - dev: false + clsx@2.1.1: {} - /collapse-white-space@1.0.6: - resolution: {integrity: sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==} + collapse-white-space@2.1.0: {} - /color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + color-convert@1.9.3: dependencies: color-name: 1.1.3 - /color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + color-convert@2.0.1: dependencies: color-name: 1.1.4 - /color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + color-name@1.1.3: {} - /color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-name@1.1.4: {} - /color-string@1.9.1: - resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + color-string@1.9.1: dependencies: color-name: 1.1.4 simple-swizzle: 0.2.2 - dev: false - /color@4.2.3: - resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} - engines: {node: '>=12.5.0'} + color@4.2.3: dependencies: color-convert: 2.0.1 color-string: 1.9.1 - dev: false - /colord@2.9.3: - resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + colord@2.9.3: {} - /colorette@1.4.0: - resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} - dev: true + colorette@2.0.20: {} - /colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + combine-promises@1.2.0: {} - /combine-promises@1.2.0: - resolution: {integrity: sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==} - engines: {node: '>=10'} + comma-separated-tokens@2.0.3: {} - /comma-separated-tokens@1.0.8: - resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} + commander@10.0.1: {} - /commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@2.20.3: {} - /commander@5.1.0: - resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} - engines: {node: '>= 6'} + commander@5.1.0: {} - /commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} + commander@7.2.0: {} - /commander@8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} + commander@8.3.0: {} - /commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + common-path-prefix@3.0.0: {} - /compressible@2.0.18: - resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} - engines: {node: '>= 0.6'} + compressible@2.0.18: dependencies: - mime-db: 1.52.0 + mime-db: 1.53.0 - /compression@1.7.4: - resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==} - engines: {node: '>= 0.8.0'} + compression@1.8.0: dependencies: - accepts: 1.3.8 - bytes: 3.0.0 + bytes: 3.1.2 compressible: 2.0.18 debug: 2.6.9 + negotiator: 0.6.4 on-headers: 1.0.2 - safe-buffer: 5.1.2 + safe-buffer: 5.2.1 vary: 1.1.2 transitivePeerDependencies: - supports-color - /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concat-map@0.0.1: {} - /configstore@5.0.1: - resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==} - engines: {node: '>=8'} + config-chain@1.1.13: dependencies: - dot-prop: 5.3.0 + ini: 1.3.8 + proto-list: 1.2.4 + + configstore@6.0.0: + dependencies: + dot-prop: 6.0.1 graceful-fs: 4.2.11 - make-dir: 3.1.0 - unique-string: 2.0.0 + unique-string: 3.0.0 write-file-atomic: 3.0.3 - xdg-basedir: 4.0.0 + xdg-basedir: 5.1.0 - /connect-history-api-fallback@2.0.0: - resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} - engines: {node: '>=0.8'} + connect-history-api-fallback@2.0.0: {} - /consola@2.15.3: - resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + consola@3.4.0: {} - /consolidated-events@2.0.2: - resolution: {integrity: sha512-2/uRVMdRypf5z/TW/ncD/66l75P5hH2vM/GR8Jf8HLc2xnfJtmina6F6du8+v4Z2vTrMo7jC+W1tmEEuuELgkQ==} - dev: false + consolidated-events@2.0.2: {} - /contains-path@1.0.0: - resolution: {integrity: sha512-nsTUCGt1AyU3dHIfoE/vgbaU6GMV/FVZgVGpg4x4DnWggpelA9qGqaR9z7SbTM0O17F09F1Oo1bsEmCu+bl0fg==} - engines: {node: '>=0.10.0'} + contains-path@1.0.0: dependencies: normalize-path: 2.1.1 path-starts-with: 1.0.0 - dev: true - /content-disposition@0.5.2: - resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} - engines: {node: '>= 0.6'} + content-disposition@0.5.2: {} - /content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 - /content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - /convert-source-map@1.8.0: - resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} - dependencies: - safe-buffer: 5.1.2 + content-type@1.0.5: {} - /convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + convert-source-map@2.0.0: {} - /cookie-signature@1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + cookie-signature@1.0.6: {} - /cookie@0.5.0: - resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} - engines: {node: '>= 0.6'} + cookie@0.7.1: {} - /copy-text-to-clipboard@3.2.0: - resolution: {integrity: sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==} - engines: {node: '>=12'} - dev: false + copy-text-to-clipboard@3.2.0: {} - /copy-webpack-plugin@11.0.0(webpack@5.88.2): - resolution: {integrity: sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==} - engines: {node: '>= 14.15.0'} - peerDependencies: - webpack: ^5.1.0 + copy-webpack-plugin@11.0.0(webpack@5.98.0(@swc/core@1.11.9)): dependencies: - fast-glob: 3.3.1 + fast-glob: 3.3.3 glob-parent: 6.0.2 globby: 13.2.2 normalize-path: 3.0.0 - schema-utils: 4.2.0 - serialize-javascript: 6.0.1 - webpack: 5.88.2 - - /core-js-compat@3.25.2: - resolution: {integrity: sha512-TxfyECD4smdn3/CjWxczVtJqVLEEC2up7/82t7vC0AzNogr+4nQ8vyF7abxAuTXWvjTClSbvGhU0RgqA4ToQaQ==} - dependencies: - browserslist: 4.21.4 - dev: false + schema-utils: 4.3.0 + serialize-javascript: 6.0.2 + webpack: 5.98.0(@swc/core@1.11.9) - /core-js-compat@3.32.1: - resolution: {integrity: sha512-GSvKDv4wE0bPnQtjklV101juQ85g6H3rm5PDP20mqlS5j0kXF3pP97YvAu5hl+uFHqMictp3b2VxOHljWMAtuA==} + core-js-compat@3.41.0: dependencies: - browserslist: 4.21.10 - - /core-js-pure@3.32.1: - resolution: {integrity: sha512-f52QZwkFVDPf7UEQZGHKx6NYxsxmVGJe5DIvbzOdRMJlmT6yv0KDjR8rmy3ngr/t5wU54c7Sp/qIJH0ppbhVpQ==} - requiresBuild: true + browserslist: 4.24.4 - /core-js@3.32.1: - resolution: {integrity: sha512-lqufgNn9NLnESg5mQeYsxQP5w7wrViSj0jr/kv6ECQiByzQkrn1MKvV0L3acttpDqfQrHLwr2KCMgX5b8X+lyQ==} - requiresBuild: true + core-js-pure@3.41.0: {} - /core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + core-js@3.41.0: {} - /cosmiconfig@6.0.0: - resolution: {integrity: sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==} - engines: {node: '>=8'} - dependencies: - '@types/parse-json': 4.0.0 - import-fresh: 3.3.0 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.2 + core-util-is@1.0.3: {} - /cosmiconfig@7.0.1: - resolution: {integrity: sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==} - engines: {node: '>=10'} + cosmiconfig@6.0.0: dependencies: - '@types/parse-json': 4.0.0 - import-fresh: 3.3.0 + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 parse-json: 5.2.0 path-type: 4.0.0 yaml: 1.10.2 - /cosmiconfig@7.1.0: - resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} - engines: {node: '>=10'} + cosmiconfig@8.3.6(typescript@5.8.2): dependencies: - '@types/parse-json': 4.0.0 - import-fresh: 3.3.0 + import-fresh: 3.3.1 + js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 - yaml: 1.10.2 + optionalDependencies: + typescript: 5.8.2 - /cosmiconfig@8.3.1(typescript@4.9.5): - resolution: {integrity: sha512-SB2dDDohny1d/U1CxuBrzSrLXqgwIBFLSFIbXVDWOFnm3CmlMFCNztRStzgZdEzpV/tTINW9XGdc+L6bX7DiSA==} - engines: {node: '>=18'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true + cosmiconfig@9.0.0(typescript@5.8.2): dependencies: - import-fresh: 3.3.0 + env-paths: 2.2.1 + import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 - path-type: 4.0.0 - typescript: 4.9.5 - - /cross-fetch@3.1.8: - resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} - dependencies: - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding - dev: false + optionalDependencies: + typescript: 5.8.2 - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - /crypt@0.0.2: - resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} - dev: false + crypt@0.0.2: {} - /crypto-random-string@2.0.0: - resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} - engines: {node: '>=8'} + crypto-random-string@4.0.0: + dependencies: + type-fest: 1.4.0 - /css-declaration-sorter@6.4.1(postcss@8.4.29): - resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==} - engines: {node: ^10 || ^12 || >=14} - peerDependencies: - postcss: ^8.0.9 + css-blank-pseudo@7.0.1(postcss@8.5.3): dependencies: - postcss: 8.4.29 + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 - /css-loader@6.8.1(webpack@5.88.2): - resolution: {integrity: sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==} - engines: {node: '>= 12.13.0'} - peerDependencies: - webpack: ^5.0.0 + css-declaration-sorter@7.2.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + + css-functions-list@3.2.3: {} + + css-has-pseudo@7.0.2(postcss@8.5.3): dependencies: - icss-utils: 5.1.0(postcss@8.4.29) - postcss: 8.4.29 - postcss-modules-extract-imports: 3.0.0(postcss@8.4.29) - postcss-modules-local-by-default: 4.0.3(postcss@8.4.29) - postcss-modules-scope: 3.0.0(postcss@8.4.29) - postcss-modules-values: 4.0.0(postcss@8.4.29) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 - semver: 7.5.4 - webpack: 5.88.2 - /css-minimizer-webpack-plugin@4.2.2(clean-css@5.3.2)(webpack@5.88.2): - resolution: {integrity: sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA==} - engines: {node: '>= 14.15.0'} - peerDependencies: - '@parcel/css': '*' - '@swc/css': '*' - clean-css: '*' - csso: '*' - esbuild: '*' - lightningcss: '*' - webpack: ^5.0.0 - peerDependenciesMeta: - '@parcel/css': - optional: true - '@swc/css': - optional: true - clean-css: - optional: true - csso: - optional: true - esbuild: - optional: true - lightningcss: - optional: true + css-line-break@2.1.0: dependencies: - clean-css: 5.3.2 - cssnano: 5.1.15(postcss@8.4.29) - jest-worker: 29.6.4 - postcss: 8.4.29 - schema-utils: 4.2.0 - serialize-javascript: 6.0.1 - source-map: 0.6.1 - webpack: 5.88.2 + utrie: 1.0.2 + + css-loader@6.11.0(webpack@5.98.0(@swc/core@1.11.9)): + dependencies: + icss-utils: 5.1.0(postcss@8.5.3) + postcss: 8.5.3 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.3) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.3) + postcss-modules-scope: 3.2.1(postcss@8.5.3) + postcss-modules-values: 4.0.0(postcss@8.5.3) + postcss-value-parser: 4.2.0 + semver: 7.7.1 + optionalDependencies: + webpack: 5.98.0(@swc/core@1.11.9) - /css-select-base-adapter@0.1.1: - resolution: {integrity: sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==} - dev: false + css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.98.0(@swc/core@1.11.9)): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + cssnano: 6.1.2(postcss@8.5.3) + jest-worker: 29.7.0 + postcss: 8.5.3 + schema-utils: 4.3.0 + serialize-javascript: 6.0.2 + webpack: 5.98.0(@swc/core@1.11.9) + optionalDependencies: + clean-css: 5.3.3 - /css-select@2.1.0: - resolution: {integrity: sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==} + css-prefers-color-scheme@10.0.0(postcss@8.5.3): dependencies: - boolbase: 1.0.0 - css-what: 3.4.2 - domutils: 1.7.0 - nth-check: 1.0.2 - dev: false + postcss: 8.5.3 - /css-select@4.3.0: - resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + css-select@4.3.0: dependencies: boolbase: 1.0.0 css-what: 6.1.0 @@ -5980,229 +10828,165 @@ packages: domutils: 2.8.0 nth-check: 2.1.1 - /css-select@5.1.0: - resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + css-select@5.1.0: dependencies: boolbase: 1.0.0 css-what: 6.1.0 domhandler: 5.0.3 - domutils: 3.1.0 + domutils: 3.2.2 nth-check: 2.1.1 - dev: false - /css-tree@1.0.0-alpha.37: - resolution: {integrity: sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==} - engines: {node: '>=8.0.0'} + css-tree@2.2.1: dependencies: - mdn-data: 2.0.4 - source-map: 0.6.1 - dev: false + mdn-data: 2.0.28 + source-map-js: 1.2.1 - /css-tree@1.1.3: - resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} - engines: {node: '>=8.0.0'} + css-tree@2.3.1: dependencies: - mdn-data: 2.0.14 - source-map: 0.6.1 + mdn-data: 2.0.30 + source-map-js: 1.2.1 - /css-what@3.4.2: - resolution: {integrity: sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==} - engines: {node: '>= 6'} - dev: false + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + css-what@6.1.0: {} + + cssdb@8.2.4: {} + + cssesc@3.0.0: {} + + cssnano-preset-advanced@6.1.2(postcss@8.5.3): + dependencies: + autoprefixer: 10.4.21(postcss@8.5.3) + browserslist: 4.24.4 + cssnano-preset-default: 6.1.2(postcss@8.5.3) + postcss: 8.5.3 + postcss-discard-unused: 6.0.5(postcss@8.5.3) + postcss-merge-idents: 6.0.3(postcss@8.5.3) + postcss-reduce-idents: 6.0.3(postcss@8.5.3) + postcss-zindex: 6.0.2(postcss@8.5.3) + + cssnano-preset-default@6.1.2(postcss@8.5.3): + dependencies: + browserslist: 4.24.4 + css-declaration-sorter: 7.2.0(postcss@8.5.3) + cssnano-utils: 4.0.2(postcss@8.5.3) + postcss: 8.5.3 + postcss-calc: 9.0.1(postcss@8.5.3) + postcss-colormin: 6.1.0(postcss@8.5.3) + postcss-convert-values: 6.1.0(postcss@8.5.3) + postcss-discard-comments: 6.0.2(postcss@8.5.3) + postcss-discard-duplicates: 6.0.3(postcss@8.5.3) + postcss-discard-empty: 6.0.3(postcss@8.5.3) + postcss-discard-overridden: 6.0.2(postcss@8.5.3) + postcss-merge-longhand: 6.0.5(postcss@8.5.3) + postcss-merge-rules: 6.1.1(postcss@8.5.3) + postcss-minify-font-values: 6.1.0(postcss@8.5.3) + postcss-minify-gradients: 6.0.3(postcss@8.5.3) + postcss-minify-params: 6.1.0(postcss@8.5.3) + postcss-minify-selectors: 6.0.4(postcss@8.5.3) + postcss-normalize-charset: 6.0.2(postcss@8.5.3) + postcss-normalize-display-values: 6.0.2(postcss@8.5.3) + postcss-normalize-positions: 6.0.2(postcss@8.5.3) + postcss-normalize-repeat-style: 6.0.2(postcss@8.5.3) + postcss-normalize-string: 6.0.2(postcss@8.5.3) + postcss-normalize-timing-functions: 6.0.2(postcss@8.5.3) + postcss-normalize-unicode: 6.1.0(postcss@8.5.3) + postcss-normalize-url: 6.0.2(postcss@8.5.3) + postcss-normalize-whitespace: 6.0.2(postcss@8.5.3) + postcss-ordered-values: 6.0.2(postcss@8.5.3) + postcss-reduce-initial: 6.1.0(postcss@8.5.3) + postcss-reduce-transforms: 6.0.2(postcss@8.5.3) + postcss-svgo: 6.0.3(postcss@8.5.3) + postcss-unique-selectors: 6.0.4(postcss@8.5.3) + + cssnano-utils@4.0.2(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + + cssnano@6.1.2(postcss@8.5.3): + dependencies: + cssnano-preset-default: 6.1.2(postcss@8.5.3) + lilconfig: 3.1.3 + postcss: 8.5.3 + + csso@5.0.5: + dependencies: + css-tree: 2.2.1 - /css-what@6.1.0: - resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} - engines: {node: '>= 6'} + csstype@3.1.3: {} - /cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 - /cssnano-preset-advanced@5.3.10(postcss@8.4.29): - resolution: {integrity: sha512-fnYJyCS9jgMU+cmHO1rPSPf9axbQyD7iUhLO5Df6O4G+fKIOMps+ZbU0PdGFejFBBZ3Pftf18fn1eG7MAPUSWQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - autoprefixer: 10.4.15(postcss@8.4.29) - cssnano-preset-default: 5.2.14(postcss@8.4.29) - postcss: 8.4.29 - postcss-discard-unused: 5.1.0(postcss@8.4.29) - postcss-merge-idents: 5.1.1(postcss@8.4.29) - postcss-reduce-idents: 5.2.0(postcss@8.4.29) - postcss-zindex: 5.1.0(postcss@8.4.29) - - /cssnano-preset-default@5.2.14(postcss@8.4.29): - resolution: {integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - css-declaration-sorter: 6.4.1(postcss@8.4.29) - cssnano-utils: 3.1.0(postcss@8.4.29) - postcss: 8.4.29 - postcss-calc: 8.2.4(postcss@8.4.29) - postcss-colormin: 5.3.1(postcss@8.4.29) - postcss-convert-values: 5.1.3(postcss@8.4.29) - postcss-discard-comments: 5.1.2(postcss@8.4.29) - postcss-discard-duplicates: 5.1.0(postcss@8.4.29) - postcss-discard-empty: 5.1.1(postcss@8.4.29) - postcss-discard-overridden: 5.1.0(postcss@8.4.29) - postcss-merge-longhand: 5.1.7(postcss@8.4.29) - postcss-merge-rules: 5.1.4(postcss@8.4.29) - postcss-minify-font-values: 5.1.0(postcss@8.4.29) - postcss-minify-gradients: 5.1.1(postcss@8.4.29) - postcss-minify-params: 5.1.4(postcss@8.4.29) - postcss-minify-selectors: 5.2.1(postcss@8.4.29) - postcss-normalize-charset: 5.1.0(postcss@8.4.29) - postcss-normalize-display-values: 5.1.0(postcss@8.4.29) - postcss-normalize-positions: 5.1.1(postcss@8.4.29) - postcss-normalize-repeat-style: 5.1.1(postcss@8.4.29) - postcss-normalize-string: 5.1.0(postcss@8.4.29) - postcss-normalize-timing-functions: 5.1.0(postcss@8.4.29) - postcss-normalize-unicode: 5.1.1(postcss@8.4.29) - postcss-normalize-url: 5.1.0(postcss@8.4.29) - postcss-normalize-whitespace: 5.1.1(postcss@8.4.29) - postcss-ordered-values: 5.1.3(postcss@8.4.29) - postcss-reduce-initial: 5.1.2(postcss@8.4.29) - postcss-reduce-transforms: 5.1.0(postcss@8.4.29) - postcss-svgo: 5.1.0(postcss@8.4.29) - postcss-unique-selectors: 5.1.1(postcss@8.4.29) - - /cssnano-utils@3.1.0(postcss@8.4.29): - resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.29 - - /cssnano@5.1.15(postcss@8.4.29): - resolution: {integrity: sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - cssnano-preset-default: 5.2.14(postcss@8.4.29) - lilconfig: 2.1.0 - postcss: 8.4.29 - yaml: 1.10.2 + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 - /csso@4.2.0: - resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} - engines: {node: '>=8.0.0'} + data-view-byte-offset@1.0.1: dependencies: - css-tree: 1.1.3 + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 - /csstype@3.1.2: - resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + debounce@1.2.1: {} - /debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + debug@2.6.9: dependencies: ms: 2.0.0 - /debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + debug@3.2.7: dependencies: ms: 2.1.3 - dev: true - - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - /decamelize-keys@1.1.0: - resolution: {integrity: sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==} - engines: {node: '>=0.10.0'} + debug@4.4.0: dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 - dev: true + ms: 2.1.3 - /decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - dev: true + decamelize@1.2.0: {} - /decompress-response@3.3.0: - resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} - engines: {node: '>=4'} + decode-named-character-reference@1.1.0: dependencies: - mimic-response: 1.0.1 + character-entities: 2.0.2 - /decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 - dev: false - - /deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - /deep-is@0.1.3: - resolution: {integrity: sha512-GtxAN4HvBachZzm4OnWqc45ESpUCMwkYcsjnsPs23FwJbsO+k4t0k9bQCgOmzIlpHO28+WPK/KRbRk0DDHuuDw==} + deep-extend@0.6.0: {} - /deepmerge@4.2.2: - resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} - engines: {node: '>=0.10.0'} - dev: false + deep-is@0.1.4: {} - /deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} + deepmerge@4.3.1: {} - /default-gateway@6.0.3: - resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==} - engines: {node: '>= 10'} + default-gateway@6.0.3: dependencies: execa: 5.1.1 - /defer-to-connect@1.1.3: - resolution: {integrity: sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==} - - /define-lazy-prop@2.0.0: - resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} - engines: {node: '>=8'} + defer-to-connect@2.0.1: {} - /define-properties@1.1.4: - resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} - engines: {node: '>= 0.4'} + define-data-property@1.1.4: dependencies: - has-property-descriptors: 1.0.0 - object-keys: 1.1.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 - /define-properties@1.2.0: - resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} - engines: {node: '>= 0.4'} + define-lazy-prop@2.0.0: {} + + define-properties@1.2.1: dependencies: - has-property-descriptors: 1.0.0 + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 object-keys: 1.1.1 - dev: true - /del@6.1.1: - resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} - engines: {node: '>=10'} + del@6.1.1: dependencies: globby: 11.1.0 graceful-fs: 4.2.11 @@ -6213,691 +10997,422 @@ packages: rimraf: 3.0.2 slash: 3.0.0 - /depd@1.1.2: - resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} - engines: {node: '>= 0.6'} - - /depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} + depd@1.1.2: {} - /destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + depd@2.0.0: {} - /detab@2.0.4: - resolution: {integrity: sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g==} - dependencies: - repeat-string: 1.6.1 + dequal@2.0.3: {} - /detect-libc@1.0.3: - resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} - engines: {node: '>=0.10'} - hasBin: true - dev: false + destroy@1.2.0: {} - /detect-libc@2.0.1: - resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} - engines: {node: '>=8'} - dev: false + detect-libc@1.0.3: + optional: true - /detect-libc@2.0.2: - resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} - engines: {node: '>=8'} - dev: false + detect-libc@2.0.3: {} - /detect-node@2.1.0: - resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + detect-node@2.1.0: {} - /detect-port-alt@1.1.6: - resolution: {integrity: sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==} - engines: {node: '>= 4.2.1'} - hasBin: true + detect-port-alt@1.1.6: dependencies: address: 1.2.2 debug: 2.6.9 transitivePeerDependencies: - supports-color - /detect-port@1.5.1: - resolution: {integrity: sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==} - hasBin: true + detect-port@1.6.1: dependencies: address: 1.2.2 - debug: 4.3.4 + debug: 4.4.0 transitivePeerDependencies: - supports-color - /didyoumean@1.2.2: - resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - dev: true + devlop@1.1.0: + dependencies: + dequal: 2.0.3 - /dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} + didyoumean@1.2.2: {} + + dir-glob@3.0.1: dependencies: path-type: 4.0.0 - /dns-equal@1.0.0: - resolution: {integrity: sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==} - - /dns-packet@5.6.1: - resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==} - engines: {node: '>=6'} + dns-packet@5.6.1: dependencies: - '@leichtgewicht/ip-codec': 2.0.4 + '@leichtgewicht/ip-codec': 2.0.5 - /doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} + doctrine@2.1.0: dependencies: esutils: 2.0.3 - dev: true - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} + doctrine@3.0.0: dependencies: esutils: 2.0.3 - /docusaurus-plugin-hotjar@0.0.2: - resolution: {integrity: sha512-Jsdxa6k4YQm4SBiY5mv9h/6sKUrQs6lC6mRoPUfjiPVtnhURE3d0dj4Vnrpy/tRVSAbywAqA0F/PGn5RKHtVaw==} - dev: false - - /docusaurus-plugin-sass@0.2.1(@docusaurus/core@2.4.1)(sass@1.43.4)(webpack@5.88.2): - resolution: {integrity: sha512-cRugbRWnKLjFPQTo1k2cyn/AANYkXAPHv+DaLs7bkOfOofEgTSrMdpNidt3oZ0ltQcjUUbvmSRRjH9R1ifdRMA==} - peerDependencies: - '@docusaurus/core': ^2.0.0 - sass: ^1.30.0 + docusaurus-plugin-sass@0.2.6(@docusaurus/core@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2))(sass@1.85.1)(webpack@5.98.0(@swc/core@1.11.9)): dependencies: - '@docusaurus/core': 2.4.1(@docusaurus/types@2.4.1)(eslint@7.32.0)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - sass: 1.43.4 - sass-loader: 10.2.0(sass@1.43.4)(webpack@5.88.2) + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.11.9)(acorn@7.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1))(@swc/core@1.11.9)(acorn@7.4.1)(eslint@7.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2) + sass: 1.85.1 + sass-loader: 16.0.5(sass@1.85.1)(webpack@5.98.0(@swc/core@1.11.9)) transitivePeerDependencies: - - fibers + - '@rspack/core' - node-sass + - sass-embedded - webpack - dev: true - /dom-converter@0.2.0: - resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==} + dom-converter@0.2.0: dependencies: utila: 0.4.0 - /dom-serializer@0.2.2: - resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==} - dependencies: - domelementtype: 2.3.0 - entities: 2.2.0 - - /dom-serializer@1.4.1: - resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + dom-serializer@1.4.1: dependencies: domelementtype: 2.3.0 domhandler: 4.3.1 entities: 2.2.0 - /dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 entities: 4.5.0 - dev: false - - /domelementtype@1.3.1: - resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==} - - /domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - /domhandler@2.4.2: - resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==} - dependencies: - domelementtype: 1.3.1 - dev: true + domelementtype@2.3.0: {} - /domhandler@4.3.1: - resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} - engines: {node: '>= 4'} + domhandler@4.3.1: dependencies: domelementtype: 2.3.0 - /domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} + domhandler@5.0.3: dependencies: domelementtype: 2.3.0 - dev: false - - /domutils@1.7.0: - resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} - dependencies: - dom-serializer: 0.2.2 - domelementtype: 1.3.1 - /domutils@2.8.0: - resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + domutils@2.8.0: dependencies: dom-serializer: 1.4.1 domelementtype: 2.3.0 domhandler: 4.3.1 - /domutils@3.1.0: - resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + domutils@3.2.2: dependencies: dom-serializer: 2.0.0 domelementtype: 2.3.0 domhandler: 5.0.3 - dev: false - /dot-case@3.0.4: - resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.8.1 - /dot-prop@5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} - engines: {node: '>=8'} + dot-prop@6.0.1: dependencies: is-obj: 2.0.0 - /dotenv@10.0.0: - resolution: {integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==} - engines: {node: '>=10'} - dev: false + dotenv@16.4.7: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 - /duplexer3@0.1.5: - resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} + duplexer@0.1.2: {} - /duplexer@0.1.2: - resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + eastasianwidth@0.2.0: {} - /eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ee-first@1.1.1: {} - /ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.119: {} - /electron-to-chromium@1.4.255: - resolution: {integrity: sha512-H+mFNKow6gi2P5Gi2d1Fvd3TUEJlB9CF7zYaIV9T83BE3wP1xZ0mRPbNTm0KUjyd1QiVy7iKXuIcjlDtBQMiAQ==} + emoji-regex@8.0.0: {} - /electron-to-chromium@1.4.508: - resolution: {integrity: sha512-FFa8QKjQK/A5QuFr2167myhMesGrhlOBD+3cYNxO9/S4XzHEXesyTD/1/xF644gC8buFPz3ca6G1LOQD0tZrrg==} + emoji-regex@9.2.2: {} - /emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emojilib@2.4.0: {} - /emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + emojis-list@3.0.0: {} - /emojis-list@3.0.0: - resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} - engines: {node: '>= 4'} + emoticon@4.1.0: {} - /emoticon@3.2.0: - resolution: {integrity: sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg==} + encodeurl@1.0.2: {} - /encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} + encodeurl@2.0.0: {} - /end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + end-of-stream@1.4.4: dependencies: once: 1.4.0 - /enhanced-resolve@5.15.0: - resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} - engines: {node: '>=10.13.0'} + enhanced-resolve@5.18.1: dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 - /enquirer@2.3.6: - resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} - engines: {node: '>=8.6'} + enquirer@2.4.1: dependencies: - ansi-colors: 4.1.1 + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 - /entities@1.1.2: - resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==} - dev: true + entities@2.2.0: {} - /entities@2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + entities@4.5.0: {} - /entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} + env-paths@2.2.1: {} - /error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 - /es-abstract@1.18.3: - resolution: {integrity: sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - es-to-primitive: 1.2.1 - function-bind: 1.1.1 - get-intrinsic: 1.1.3 - has: 1.0.3 - has-symbols: 1.0.3 - is-callable: 1.2.3 - is-negative-zero: 2.0.1 - is-regex: 1.1.3 - is-string: 1.0.6 - object-inspect: 1.12.2 + es-abstract@1.23.9: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 object-keys: 1.1.1 - object.assign: 4.1.4 - string.prototype.trimend: 1.0.4 - string.prototype.trimstart: 1.0.4 - unbox-primitive: 1.0.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 - /es-abstract@1.22.1: - resolution: {integrity: sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==} - engines: {node: '>= 0.4'} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.6.0: {} + + es-object-atoms@1.1.1: dependencies: - array-buffer-byte-length: 1.0.0 - arraybuffer.prototype.slice: 1.0.1 - available-typed-arrays: 1.0.5 - call-bind: 1.0.2 - es-set-tostringtag: 2.0.1 - es-to-primitive: 1.2.1 - function.prototype.name: 1.1.6 - get-intrinsic: 1.2.1 - get-symbol-description: 1.0.0 - globalthis: 1.0.3 - gopd: 1.0.1 - has: 1.0.3 - has-property-descriptors: 1.0.0 - has-proto: 1.0.1 - has-symbols: 1.0.3 - internal-slot: 1.0.5 - is-array-buffer: 3.0.2 - is-callable: 1.2.7 - is-negative-zero: 2.0.2 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 - is-string: 1.0.7 - is-typed-array: 1.1.12 - is-weakref: 1.0.2 - object-inspect: 1.12.3 - object-keys: 1.1.1 - object.assign: 4.1.4 - regexp.prototype.flags: 1.5.0 - safe-array-concat: 1.0.0 - safe-regex-test: 1.0.0 - string.prototype.trim: 1.2.7 - string.prototype.trimend: 1.0.6 - string.prototype.trimstart: 1.0.6 - typed-array-buffer: 1.0.0 - typed-array-byte-length: 1.0.0 - typed-array-byte-offset: 1.0.0 - typed-array-length: 1.0.4 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.11 - dev: true - - /es-module-lexer@1.3.0: - resolution: {integrity: sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==} - - /es-set-tostringtag@2.0.1: - resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} - engines: {node: '>= 0.4'} + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: dependencies: - get-intrinsic: 1.2.1 - has: 1.0.3 - has-tostringtag: 1.0.0 - dev: true + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 - /es-shim-unscopables@1.0.0: - resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} + es-shim-unscopables@1.1.0: dependencies: - has: 1.0.3 - dev: true + hasown: 2.0.2 - /es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} + es-to-primitive@1.3.0: dependencies: - is-callable: 1.2.3 - is-date-object: 1.0.4 - is-symbol: 1.0.4 + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 - /escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 - /escape-goat@2.1.1: - resolution: {integrity: sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==} - engines: {node: '>=8'} + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.14.1 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.2 - /escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escalade@3.2.0: {} - /escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} + escape-goat@4.0.0: {} - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} + escape-html@1.0.3: {} - /eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.28.1): - resolution: {integrity: sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==} - engines: {node: '>= 4'} - peerDependencies: - eslint-plugin-import: '>=1.4.0' - dependencies: - eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.38.0)(eslint@7.32.0) - dev: true + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} - /eslint-import-resolver-node@0.3.4: - resolution: {integrity: sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==} + escape-string-regexp@5.0.0: {} + + eslint-config-prettier@9.1.0(eslint@7.32.0): dependencies: - debug: 2.6.9 - resolve: 1.22.1 - transitivePeerDependencies: - - supports-color - dev: true + eslint: 7.32.0 - /eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.23.2(@typescript-eslint/parser@6.21.0(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0)): dependencies: - debug: 3.2.7 - is-core-module: 2.13.0 - resolve: 1.22.4 - transitivePeerDependencies: - - supports-color - dev: true + eslint-plugin-import: 2.23.2(@typescript-eslint/parser@6.21.0(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0) - /eslint-module-utils@2.6.1(@typescript-eslint/parser@5.38.0)(eslint-import-resolver-node@0.3.4): - resolution: {integrity: sha512-ZXI9B8cxAJIH4nfkhTwcRTEAnrVfobYqwjWy/QMCZ8rHkZHFjf9yO4BzpiF9kCSfNlMG54eKigISHpX0+AaT4A==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true + eslint-import-resolver-node@0.3.9: dependencies: - '@typescript-eslint/parser': 5.38.0(eslint@7.32.0)(typescript@4.9.5) debug: 3.2.7 - eslint-import-resolver-node: 0.3.4 - pkg-dir: 2.0.0 + is-core-module: 2.16.1 + resolve: 1.22.10 transitivePeerDependencies: - supports-color - dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.38.0)(eslint-import-resolver-node@0.3.9)(eslint@7.32.0): - resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true + eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@7.32.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@7.32.0): dependencies: - '@typescript-eslint/parser': 5.38.0(eslint@7.32.0)(typescript@4.9.5) debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 6.21.0(eslint@7.32.0)(typescript@5.8.2) eslint: 7.32.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - dev: true - /eslint-plugin-import@2.23.2(@typescript-eslint/parser@5.38.0)(eslint@7.32.0): - resolution: {integrity: sha512-LmNoRptHBxOP+nb0PIKz1y6OSzCJlB+0g0IGS3XV4KaKk2q4szqQ6s6F1utVf5ZRkxk/QOTjdxe7v4VjS99Bsg==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true + eslint-plugin-import@2.23.2(@typescript-eslint/parser@6.21.0(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0): dependencies: - '@typescript-eslint/parser': 5.38.0(eslint@7.32.0)(typescript@4.9.5) - array-includes: 3.1.3 - array.prototype.flat: 1.2.4 + array-includes: 3.1.8 + array.prototype.flat: 1.3.3 contains-path: 1.0.0 debug: 2.6.9 doctrine: 2.1.0 eslint: 7.32.0 - eslint-import-resolver-node: 0.3.4 - eslint-module-utils: 2.6.1(@typescript-eslint/parser@5.38.0)(eslint-import-resolver-node@0.3.4) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@7.32.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@7.32.0) find-up: 2.1.0 - has: 1.0.3 - is-core-module: 2.10.0 + has: 1.0.4 + is-core-module: 2.16.1 minimatch: 3.1.2 - object.values: 1.1.4 + object.values: 1.2.1 pkg-up: 2.0.0 read-pkg-up: 3.0.0 - resolve: 1.22.1 - tsconfig-paths: 3.9.0 - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - dev: true - - /eslint-plugin-import@2.28.1(@typescript-eslint/parser@5.38.0)(eslint@7.32.0): - resolution: {integrity: sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - dependencies: - '@typescript-eslint/parser': 5.38.0(eslint@7.32.0)(typescript@4.9.5) - array-includes: 3.1.6 - array.prototype.findlastindex: 1.2.3 - array.prototype.flat: 1.3.1 - array.prototype.flatmap: 1.3.1 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 7.32.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.38.0)(eslint-import-resolver-node@0.3.9)(eslint@7.32.0) - has: 1.0.3 - is-core-module: 2.13.0 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.7 - object.groupby: 1.0.1 - object.values: 1.1.7 - semver: 6.3.1 - tsconfig-paths: 3.14.2 + resolve: 1.22.10 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 6.21.0(eslint@7.32.0)(typescript@5.8.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - dev: true - - /eslint-plugin-prettier@3.4.0(eslint@7.32.0)(prettier@2.3.0): - resolution: {integrity: sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw==} - engines: {node: '>=6.0.0'} - peerDependencies: - eslint: '>=5.0.0' - eslint-config-prettier: '*' - prettier: '>=1.13.0' - peerDependenciesMeta: - eslint-config-prettier: - optional: true - dependencies: - eslint: 7.32.0 - prettier: 2.3.0 - prettier-linter-helpers: 1.0.0 - dev: true - /eslint-plugin-react-hooks@4.2.0(eslint@7.32.0): - resolution: {integrity: sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + eslint-plugin-react-hooks@4.2.0(eslint@7.32.0): dependencies: eslint: 7.32.0 - dev: true - /eslint-plugin-react@7.23.2(eslint@7.32.0): - resolution: {integrity: sha512-AfjgFQB+nYszudkxRkTFu0UR1zEQig0ArVMPloKhxwlwkzaw/fBiH0QWcBBhZONlXqQC51+nfqFrkn4EzHcGBw==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 + eslint-plugin-react@7.23.2(eslint@7.32.0): dependencies: - array-includes: 3.1.3 - array.prototype.flatmap: 1.2.4 + array-includes: 3.1.8 + array.prototype.flatmap: 1.3.3 doctrine: 2.1.0 eslint: 7.32.0 - has: 1.0.3 - jsx-ast-utils: 3.2.0 + has: 1.0.4 + jsx-ast-utils: 3.3.5 minimatch: 3.1.2 - object.entries: 1.1.4 - object.fromentries: 2.0.4 - object.values: 1.1.4 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 prop-types: 15.8.1 - resolve: 2.0.0-next.3 - string.prototype.matchall: 4.0.5 - dev: true + resolve: 2.0.0-next.5 + string.prototype.matchall: 4.0.12 - /eslint-plugin-sonarjs@0.7.0(eslint@7.32.0): - resolution: {integrity: sha512-vi6zGkd5Eznc32AQlleWUOMrMeDiUih9wR7nPPfrDCyVRmwYNHIBRPZGv1EgXwELwaPghCSvoAoHoR7uSbBF/Q==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 + eslint-plugin-sonarjs@0.7.0(eslint@7.32.0): dependencies: eslint: 7.32.0 - dev: true - /eslint-plugin-unicorn@32.0.1(eslint@7.32.0): - resolution: {integrity: sha512-LaZ9utnXtOJjnoDkpm+nQsONUUmyRR0WD6PGROSdQRRW3LRmgK/ZP8wxjW+Ai+2uolKTtuJzLx2mvbIeIoLqpg==} - engines: {node: '>=12'} - peerDependencies: - eslint: '>=7.23.0' + eslint-plugin-unicorn@32.0.1(eslint@7.32.0): dependencies: - ci-info: 3.2.0 + ci-info: 3.9.0 clean-regexp: 1.0.0 eslint: 7.32.0 eslint-template-visitor: 2.3.2(eslint@7.32.0) eslint-utils: 2.1.0 import-modules: 2.1.0 - is-builtin-module: 3.1.0 + is-builtin-module: 3.2.1 lodash: 4.17.21 pluralize: 8.0.0 read-pkg-up: 7.0.1 - regexp-tree: 0.1.23 + regexp-tree: 0.1.27 reserved-words: 0.1.2 safe-regex: 2.1.1 - semver: 7.3.7 + semver: 7.7.1 transitivePeerDependencies: - supports-color - dev: true - /eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} + eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 estraverse: 4.3.0 - /eslint-template-visitor@2.3.2(eslint@7.32.0): - resolution: {integrity: sha512-3ydhqFpuV7x1M9EK52BPNj6V0Kwu0KKkcIAfpUhwHbR8ocRln/oUHgfxQupY8O1h4Qv/POHDumb/BwwNfxbtnA==} - peerDependencies: - eslint: '>=7.0.0' + eslint-template-visitor@2.3.2(eslint@7.32.0): dependencies: - '@babel/core': 7.19.1 - '@babel/eslint-parser': 7.21.3(@babel/core@7.19.1)(eslint@7.32.0) + '@babel/core': 7.26.10 + '@babel/eslint-parser': 7.26.10(@babel/core@7.26.10)(eslint@7.32.0) eslint: 7.32.0 eslint-visitor-keys: 2.1.0 - esquery: 1.4.0 + esquery: 1.6.0 multimap: 1.1.0 transitivePeerDependencies: - supports-color - dev: true - /eslint-utils@2.1.0: - resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} - engines: {node: '>=6'} + eslint-utils@2.1.0: dependencies: eslint-visitor-keys: 1.3.0 - /eslint-utils@3.0.0(eslint@7.32.0): - resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} - engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} - peerDependencies: - eslint: '>=5' - dependencies: - eslint: 7.32.0 - eslint-visitor-keys: 2.1.0 - dev: true - - /eslint-visitor-keys@1.3.0: - resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} - engines: {node: '>=4'} + eslint-visitor-keys@1.3.0: {} - /eslint-visitor-keys@2.1.0: - resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} - engines: {node: '>=10'} + eslint-visitor-keys@2.1.0: {} - /eslint-visitor-keys@3.3.0: - resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true + eslint-visitor-keys@3.4.3: {} - /eslint@7.32.0: - resolution: {integrity: sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==} - engines: {node: ^10.12.0 || >=12.0.0} - hasBin: true + eslint@7.32.0: dependencies: '@babel/code-frame': 7.12.11 '@eslint/eslintrc': 0.4.3 '@humanwhocodes/config-array': 0.5.0 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4 + cross-spawn: 7.0.6 + debug: 4.4.0 doctrine: 3.0.0 - enquirer: 2.3.6 + enquirer: 2.4.1 escape-string-regexp: 4.0.0 eslint-scope: 5.1.1 eslint-utils: 2.1.0 eslint-visitor-keys: 2.1.0 espree: 7.3.1 - esquery: 1.4.0 + esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 6.0.1 functional-red-black-tree: 1.0.1 glob-parent: 5.1.2 - globals: 13.20.0 + globals: 13.24.0 ignore: 4.0.6 - import-fresh: 3.3.0 + import-fresh: 3.3.1 imurmurhash: 0.1.4 is-glob: 4.0.3 js-yaml: 3.14.1 @@ -6906,82 +11421,93 @@ packages: lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 - optionator: 0.9.1 + optionator: 0.9.4 progress: 2.0.3 regexpp: 3.2.0 - semver: 7.3.7 + semver: 7.7.1 strip-ansi: 6.0.1 strip-json-comments: 3.1.1 - table: 6.7.1 + table: 6.9.0 text-table: 0.2.0 - v8-compile-cache: 2.3.0 + v8-compile-cache: 2.4.0 transitivePeerDependencies: - supports-color - /espree@7.3.1: - resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==} - engines: {node: ^10.12.0 || >=12.0.0} + espree@7.3.1: dependencies: acorn: 7.4.1 - acorn-jsx: 5.3.1(acorn@7.4.1) + acorn-jsx: 5.3.2(acorn@7.4.1) eslint-visitor-keys: 1.3.0 - /esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true + esprima@4.0.1: {} - /esquery@1.4.0: - resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} - engines: {node: '>=0.10'} + esquery@1.6.0: dependencies: estraverse: 5.3.0 - /esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 - /estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} + estraverse@4.3.0: {} - /estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} + estraverse@5.3.0: {} - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.6 - /eta@2.2.0: - resolution: {integrity: sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==} - engines: {node: '>=6.0.0'} + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 - /etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} + estree-util-is-identifier-name@3.0.0: {} - /eval@0.1.8: - resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} - engines: {node: '>= 0.8'} + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.6 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.4 + + estree-util-value-to-estree@3.3.2: + dependencies: + '@types/estree': 1.0.6 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + + estree-walker@3.0.3: dependencies: - '@types/node': 20.5.9 + '@types/estree': 1.0.6 + + esutils@2.0.3: {} + + eta@2.2.0: {} + + etag@1.8.1: {} + + eval@0.1.8: + dependencies: + '@types/node': 22.13.10 require-like: 0.1.2 - /eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + eventemitter3@4.0.7: {} - /events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} + events@3.3.0: {} - /execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} + execa@5.1.1: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 2.1.0 is-stream: 2.0.1 @@ -6991,48 +11517,36 @@ packages: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - /execall@2.0.0: - resolution: {integrity: sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==} - engines: {node: '>=8'} - dependencies: - clone-regexp: 2.2.0 - dev: true - - /expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - dev: false + expand-template@2.0.3: {} - /express@4.18.2: - resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} - engines: {node: '>= 0.10.0'} + express@4.21.2: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.1 + body-parser: 1.20.3 content-disposition: 0.5.4 content-type: 1.0.5 - cookie: 0.5.0 + cookie: 0.7.1 cookie-signature: 1.0.6 debug: 2.6.9 depd: 2.0.0 - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.2.0 + finalhandler: 1.3.1 fresh: 0.5.2 http-errors: 2.0.0 - merge-descriptors: 1.0.1 + merge-descriptors: 1.0.3 methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 - path-to-regexp: 0.1.7 + path-to-regexp: 0.1.12 proxy-addr: 2.0.7 - qs: 6.11.0 + qs: 6.13.0 range-parser: 1.2.1 safe-buffer: 5.2.1 - send: 0.18.0 - serve-static: 1.15.0 + send: 0.19.0 + serve-static: 1.16.2 setprototypeof: 1.2.0 statuses: 2.0.1 type-is: 1.6.18 @@ -7041,149 +11555,82 @@ packages: transitivePeerDependencies: - supports-color - /extend-shallow@2.0.1: - resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} - engines: {node: '>=0.10.0'} + extend-shallow@2.0.1: dependencies: is-extendable: 0.1.1 - /extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + extend@3.0.2: {} - /external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} + external-editor@3.1.0: dependencies: chardet: 0.7.0 iconv-lite: 0.4.24 tmp: 0.0.33 - dev: true - - /fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - /fast-diff@1.2.0: - resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} - dev: true + fast-deep-equal@3.1.3: {} - /fast-glob@3.2.12: - resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} - engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 + fast-fifo@1.3.2: {} - /fast-glob@3.3.1: - resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} - engines: {node: '>=8.6.0'} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.5 - - /fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + micromatch: 4.0.8 - /fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-json-stable-stringify@2.1.0: {} - /fast-url-parser@1.1.3: - resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} - dependencies: - punycode: 1.4.1 + fast-levenshtein@2.0.6: {} - /fastest-levenshtein@1.0.12: - resolution: {integrity: sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==} - dev: true + fast-uri@3.0.6: {} - /fastq@1.13.0: - resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} - dependencies: - reusify: 1.0.4 + fastest-levenshtein@1.0.16: {} - /faye-websocket@0.11.4: - resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} - engines: {node: '>=0.8.0'} + fastq@1.19.1: dependencies: - websocket-driver: 0.7.4 + reusify: 1.1.0 - /fbemitter@3.0.0: - resolution: {integrity: sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==} + fault@2.0.1: dependencies: - fbjs: 3.0.5 - transitivePeerDependencies: - - encoding - dev: false - - /fbjs-css-vars@1.0.2: - resolution: {integrity: sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==} - dev: false + format: 0.2.2 - /fbjs@3.0.5: - resolution: {integrity: sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==} + faye-websocket@0.11.4: dependencies: - cross-fetch: 3.1.8 - fbjs-css-vars: 1.0.2 - loose-envify: 1.4.0 - object-assign: 4.1.1 - promise: 7.3.1 - setimmediate: 1.0.5 - ua-parser-js: 1.0.35 - transitivePeerDependencies: - - encoding - dev: false + websocket-driver: 0.7.4 - /feed@4.2.2: - resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==} - engines: {node: '>=0.4.0'} + feed@4.2.2: dependencies: xml-js: 1.6.11 - dev: false - /figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} + figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 - dev: true - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@10.0.7: dependencies: - flat-cache: 3.0.4 + flat-cache: 6.1.7 - /file-loader@6.2.0(webpack@5.88.2): - resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==} - engines: {node: '>= 10.13.0'} - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 + file-entry-cache@6.0.1: dependencies: - loader-utils: 2.0.2 - schema-utils: 3.1.1 - webpack: 5.88.2 + flat-cache: 3.2.0 - /filesize@8.0.7: - resolution: {integrity: sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==} - engines: {node: '>= 0.4.0'} + file-loader@6.2.0(webpack@5.98.0(@swc/core@1.11.9)): + dependencies: + loader-utils: 2.0.4 + schema-utils: 3.3.0 + webpack: 5.98.0(@swc/core@1.11.9) - /fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} + filesize@8.0.7: {} + + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 - /finalhandler@1.2.0: - resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} - engines: {node: '>= 0.8'} + finalhandler@1.3.1: dependencies: debug: 2.6.9 - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 @@ -7192,255 +11639,170 @@ packages: transitivePeerDependencies: - supports-color - /find-cache-dir@3.3.2: - resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} - engines: {node: '>=8'} + find-cache-dir@4.0.0: dependencies: - commondir: 1.0.1 - make-dir: 3.1.0 - pkg-dir: 4.2.0 + common-path-prefix: 3.0.0 + pkg-dir: 7.0.0 - /find-up@2.1.0: - resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} - engines: {node: '>=4'} + find-up@2.1.0: dependencies: locate-path: 2.0.0 - dev: true - /find-up@3.0.0: - resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} - engines: {node: '>=6'} + find-up@3.0.0: dependencies: locate-path: 3.0.0 - /find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} + find-up@4.1.0: dependencies: locate-path: 5.0.0 path-exists: 4.0.0 - /find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + find-up@5.0.0: dependencies: locate-path: 6.0.0 path-exists: 4.0.0 - /flat-cache@3.0.4: - resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} - engines: {node: ^10.12.0 || >=12.0.0} + find-up@6.3.0: dependencies: - flatted: 3.2.0 - rimraf: 3.0.2 + locate-path: 7.2.0 + path-exists: 5.0.0 - /flatted@3.2.0: - resolution: {integrity: sha512-XprP7lDrVT+kE2c2YlfiV+IfS9zxukiIOvNamPNsImNhXadSsQEbosItdL9bUQlCZXR13SvPk20BjWSWLA7m4A==} + flat-cache@3.2.0: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + rimraf: 3.0.2 - /flux@4.0.4(react@17.0.2): - resolution: {integrity: sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw==} - peerDependencies: - react: ^15.0.2 || ^16.0.0 || ^17.0.0 + flat-cache@6.1.7: dependencies: - fbemitter: 3.0.0 - fbjs: 3.0.5 - react: 17.0.2 - transitivePeerDependencies: - - encoding - dev: false + cacheable: 1.8.9 + flatted: 3.3.3 + hookified: 1.8.1 - /follow-redirects@1.15.2: - resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true + flat@5.0.2: {} - /for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + flatted@3.3.3: {} + + follow-redirects@1.15.9: {} + + for-each@0.3.5: dependencies: is-callable: 1.2.7 - dev: true - /fork-ts-checker-webpack-plugin@6.5.3(eslint@7.32.0)(typescript@4.9.5)(webpack@5.88.2): - resolution: {integrity: sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==} - engines: {node: '>=10', yarn: '>=1.0.0'} - peerDependencies: - eslint: '>= 6' - typescript: '>= 2.7' - vue-template-compiler: '*' - webpack: '>= 4' - peerDependenciesMeta: - eslint: - optional: true - vue-template-compiler: - optional: true + fork-ts-checker-webpack-plugin@6.5.3(eslint@7.32.0)(typescript@5.8.2)(webpack@5.98.0(@swc/core@1.11.9)): dependencies: - '@babel/code-frame': 7.22.13 - '@types/json-schema': 7.0.12 + '@babel/code-frame': 7.26.2 + '@types/json-schema': 7.0.15 chalk: 4.1.2 - chokidar: 3.5.3 + chokidar: 3.6.0 cosmiconfig: 6.0.0 deepmerge: 4.3.1 - eslint: 7.32.0 fs-extra: 9.1.0 glob: 7.2.3 memfs: 3.5.3 minimatch: 3.1.2 schema-utils: 2.7.0 - semver: 7.5.4 + semver: 7.7.1 tapable: 1.1.3 - typescript: 4.9.5 - webpack: 5.88.2 + typescript: 5.8.2 + webpack: 5.98.0(@swc/core@1.11.9) + optionalDependencies: + eslint: 7.32.0 - /forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} + form-data-encoder@2.1.4: {} - /fraction.js@4.3.6: - resolution: {integrity: sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==} + format@0.2.2: {} - /fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} + forwarded@0.2.0: {} - /fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - dev: false + fraction.js@4.3.7: {} - /fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} + fresh@0.5.2: {} + + fs-constants@1.0.0: {} + + fs-extra@11.3.0: dependencies: graceful-fs: 4.2.11 jsonfile: 6.1.0 - universalify: 2.0.0 + universalify: 2.0.1 - /fs-extra@9.1.0: - resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} - engines: {node: '>=10'} + fs-extra@9.1.0: dependencies: at-least-node: 1.0.0 graceful-fs: 4.2.11 jsonfile: 6.1.0 - universalify: 2.0.0 + universalify: 2.0.1 - /fs-monkey@1.0.4: - resolution: {integrity: sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==} + fs-monkey@1.0.6: {} - /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fs.realpath@1.0.0: {} - /fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true + fsevents@2.3.3: optional: true - /function-bind@1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + function-bind@1.1.2: {} - /function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} - engines: {node: '>= 0.4'} + function.prototype.name@1.1.8: dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 functions-have-names: 1.2.3 - dev: true - - /functional-red-black-tree@1.0.1: - resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} + hasown: 2.0.2 + is-callable: 1.2.7 - /functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - dev: true + functional-red-black-tree@1.0.1: {} - /gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} + functions-have-names@1.2.3: {} - /get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - dev: true + gensync@1.0.0-beta.2: {} - /get-intrinsic@1.1.3: - resolution: {integrity: sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==} - dependencies: - function-bind: 1.1.1 - has: 1.0.3 - has-symbols: 1.0.3 + get-caller-file@2.0.5: {} - /get-intrinsic@1.2.1: - resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} + get-intrinsic@1.3.0: dependencies: - function-bind: 1.1.1 - has: 1.0.3 - has-proto: 1.0.1 - has-symbols: 1.0.3 - dev: true - - /get-own-enumerable-property-symbols@3.0.2: - resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} - - /get-stdin@8.0.0: - resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==} - engines: {node: '>=10'} - dev: true + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 - /get-stream@4.1.0: - resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} - engines: {node: '>=6'} - dependencies: - pump: 3.0.0 + get-own-enumerable-property-symbols@3.0.2: {} - /get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} + get-proto@1.0.1: dependencies: - pump: 3.0.0 + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 - /get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} + get-stream@6.0.1: {} - /get-symbol-description@1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} - engines: {node: '>= 0.4'} + get-symbol-description@1.1.0: dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - dev: true + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 - /github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - dev: false + github-from-package@0.0.0: {} - /github-slugger@1.5.0: - resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==} + github-slugger@1.5.0: {} - /glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 - /glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 - /glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + glob-to-regexp@0.4.1: {} - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + glob@7.2.3: dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -7449,357 +11811,302 @@ packages: once: 1.4.0 path-is-absolute: 1.0.1 - /global-dirs@3.0.1: - resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} - engines: {node: '>=10'} + global-dirs@3.0.1: dependencies: ini: 2.0.0 - /global-modules@2.0.0: - resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} - engines: {node: '>=6'} + global-modules@2.0.0: dependencies: global-prefix: 3.0.0 - /global-prefix@3.0.0: - resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} - engines: {node: '>=6'} + global-prefix@3.0.0: dependencies: ini: 1.3.8 kind-of: 6.0.3 which: 1.3.1 - /globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} + globals@11.12.0: {} - /globals@13.20.0: - resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} - engines: {node: '>=8'} + globals@13.24.0: dependencies: type-fest: 0.20.2 - /globalthis@1.0.3: - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} - engines: {node: '>= 0.4'} + globalthis@1.0.4: dependencies: - define-properties: 1.2.0 - dev: true + define-properties: 1.2.1 + gopd: 1.2.0 - /globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} + globby@11.1.0: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.2.12 - ignore: 5.2.0 + fast-glob: 3.3.3 + ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 - /globby@13.2.2: - resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + globby@13.2.2: dependencies: dir-glob: 3.0.1 - fast-glob: 3.3.1 - ignore: 5.2.4 + fast-glob: 3.3.3 + ignore: 5.3.2 merge2: 1.4.1 slash: 4.0.0 - /globjoin@0.1.4: - resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} - dev: true + globjoin@0.1.4: {} - /gonzales-pe@4.3.0: - resolution: {integrity: sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==} - engines: {node: '>=0.6.0'} - hasBin: true - dependencies: - minimist: 1.2.8 - dev: true + gopd@1.2.0: {} - /gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + got@12.6.1: dependencies: - get-intrinsic: 1.2.1 - dev: true + '@sindresorhus/is': 5.6.0 + '@szmarczak/http-timer': 5.0.1 + cacheable-lookup: 7.0.0 + cacheable-request: 10.2.14 + decompress-response: 6.0.0 + form-data-encoder: 2.1.4 + get-stream: 6.0.1 + http2-wrapper: 2.2.1 + lowercase-keys: 3.0.0 + p-cancelable: 3.0.0 + responselike: 3.0.0 - /got@9.6.0: - resolution: {integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==} - engines: {node: '>=8.6'} - dependencies: - '@sindresorhus/is': 0.14.0 - '@szmarczak/http-timer': 1.1.2 - '@types/keyv': 3.1.4 - '@types/responselike': 1.0.0 - cacheable-request: 6.1.0 - decompress-response: 3.3.0 - duplexer3: 0.1.5 - get-stream: 4.1.0 - lowercase-keys: 1.0.1 - mimic-response: 1.0.1 - p-cancelable: 1.1.0 - to-readable-stream: 1.0.0 - url-parse-lax: 3.0.0 - - /graceful-fs@4.2.10: - resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} - dev: true + graceful-fs@4.2.10: {} - /graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graceful-fs@4.2.11: {} - /gray-matter@4.0.3: - resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} - engines: {node: '>=6.0'} + graphemer@1.4.0: {} + + gray-matter@4.0.3: dependencies: js-yaml: 3.14.1 kind-of: 6.0.3 section-matter: 1.0.0 strip-bom-string: 1.0.0 - /gzip-size@6.0.0: - resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} - engines: {node: '>=10'} + gzip-size@6.0.0: dependencies: duplexer: 0.1.2 - /handle-thing@2.0.1: - resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} + handle-thing@2.0.1: {} - /hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - dev: true + has-bigints@1.1.0: {} - /has-bigints@1.0.1: - resolution: {integrity: sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==} + has-flag@3.0.0: {} - /has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - dev: true + has-flag@4.0.0: {} - /has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} - /has-property-descriptors@1.0.0: - resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + has-tostringtag@1.0.2: dependencies: - get-intrinsic: 1.1.3 + has-symbols: 1.1.0 - /has-proto@1.0.1: - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} - engines: {node: '>= 0.4'} - dev: true + has-yarn@3.0.0: {} - /has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} + has@1.0.4: {} - /has-tostringtag@1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} - engines: {node: '>= 0.4'} + hasown@2.0.2: dependencies: - has-symbols: 1.0.3 - dev: true + function-bind: 1.1.2 - /has-yarn@2.1.0: - resolution: {integrity: sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==} - engines: {node: '>=8'} + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.0.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 - /has@1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} + hast-util-parse-selector@4.0.0: dependencies: - function-bind: 1.1.1 - - /hast-to-hyperscript@9.0.1: - resolution: {integrity: sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA==} - dependencies: - '@types/unist': 2.0.8 - comma-separated-tokens: 1.0.8 - property-information: 5.6.0 - space-separated-tokens: 1.1.5 - style-to-object: 0.3.0 - unist-util-is: 4.1.0 - web-namespaces: 1.1.4 - - /hast-util-from-parse5@6.0.1: - resolution: {integrity: sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA==} - dependencies: - '@types/parse5': 5.0.3 - hastscript: 6.0.0 - property-information: 5.6.0 - vfile: 4.2.1 - vfile-location: 3.2.0 - web-namespaces: 1.1.4 - - /hast-util-parse-selector@2.2.5: - resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==} - - /hast-util-raw@6.0.1: - resolution: {integrity: sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig==} - dependencies: - '@types/hast': 2.3.5 - hast-util-from-parse5: 6.0.1 - hast-util-to-parse5: 6.0.0 - html-void-elements: 1.0.5 - parse5: 6.0.1 - unist-util-position: 3.1.0 - vfile: 4.2.1 - web-namespaces: 1.1.4 - xtend: 4.0.2 - zwitch: 1.0.5 - - /hast-util-to-parse5@6.0.0: - resolution: {integrity: sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ==} - dependencies: - hast-to-hyperscript: 9.0.1 - property-information: 5.6.0 - web-namespaces: 1.1.4 - xtend: 4.0.2 - zwitch: 1.0.5 - - /hastscript@6.0.0: - resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==} - dependencies: - '@types/hast': 2.3.5 - comma-separated-tokens: 1.0.8 - hast-util-parse-selector: 2.2.5 - property-information: 5.6.0 - space-separated-tokens: 1.1.5 - - /he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true + '@types/hast': 3.0.4 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + parse5: 7.2.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.6 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.0.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.16 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.6 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.0.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.16 + unist-util-position: 5.0.0 + vfile-message: 4.0.2 + transitivePeerDependencies: + - supports-color + + hast-util-to-parse5@8.0.0: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.0.0 + space-separated-tokens: 2.0.2 - /history@4.10.1: - resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==} + he@1.2.0: {} + + history@4.10.1: dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.26.10 loose-envify: 1.4.0 resolve-pathname: 3.0.0 - tiny-invariant: 1.3.1 + tiny-invariant: 1.3.3 tiny-warning: 1.0.3 value-equal: 1.0.1 - /hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + hoist-non-react-statics@3.3.2: dependencies: react-is: 16.13.1 - /hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - dev: true + hookified@1.8.1: {} - /hosted-git-info@4.0.2: - resolution: {integrity: sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==} - engines: {node: '>=10'} - dependencies: - lru-cache: 6.0.0 - dev: true + hosted-git-info@2.8.9: {} - /hpack.js@2.1.6: - resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==} + hpack.js@2.1.6: dependencies: inherits: 2.0.4 obuf: 1.1.2 readable-stream: 2.3.8 wbuf: 1.7.3 - /html-entities@2.4.0: - resolution: {integrity: sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==} + html-entities@2.5.2: {} - /html-minifier-terser@6.1.0: - resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} - engines: {node: '>=12'} - hasBin: true + html-escaper@2.0.2: {} + + html-minifier-terser@6.1.0: dependencies: camel-case: 4.1.2 - clean-css: 5.3.2 + clean-css: 5.3.3 commander: 8.3.0 he: 1.2.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.19.3 + terser: 5.39.0 - /html-tags@3.2.0: - resolution: {integrity: sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==} - engines: {node: '>=8'} - dev: true + html-minifier-terser@7.2.0: + dependencies: + camel-case: 4.1.2 + clean-css: 5.3.3 + commander: 10.0.1 + entities: 4.5.0 + param-case: 3.0.4 + relateurl: 0.2.7 + terser: 5.39.0 - /html-tags@3.3.1: - resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} - engines: {node: '>=8'} + html-tags@3.3.1: {} - /html-void-elements@1.0.5: - resolution: {integrity: sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==} + html-void-elements@3.0.0: {} - /html-webpack-plugin@5.5.3(webpack@5.88.2): - resolution: {integrity: sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==} - engines: {node: '>=10.13.0'} - peerDependencies: - webpack: ^5.20.0 + html-webpack-plugin@5.6.3(webpack@5.98.0(@swc/core@1.11.9)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 lodash: 4.17.21 pretty-error: 4.0.0 tapable: 2.2.1 - webpack: 5.88.2 + optionalDependencies: + webpack: 5.98.0(@swc/core@1.11.9) - /htmlparser2@3.10.1: - resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} + html2canvas@1.4.1: dependencies: - domelementtype: 1.3.1 - domhandler: 2.4.2 - domutils: 1.7.0 - entities: 1.1.2 - inherits: 2.0.4 - readable-stream: 3.6.0 - dev: true + css-line-break: 2.1.0 + text-segmentation: 1.0.3 - /htmlparser2@6.1.0: - resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==} + htmlparser2@6.1.0: dependencies: domelementtype: 2.3.0 domhandler: 4.3.1 domutils: 2.8.0 entities: 2.2.0 - /htmlparser2@8.0.2: - resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + htmlparser2@8.0.2: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 - domutils: 3.1.0 + domutils: 3.2.2 entities: 4.5.0 - dev: false - /http-cache-semantics@4.1.1: - resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + http-cache-semantics@4.1.1: {} - /http-deceiver@1.2.7: - resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==} + http-deceiver@1.2.7: {} - /http-errors@1.6.3: - resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} - engines: {node: '>= 0.6'} + http-errors@1.6.3: dependencies: depd: 1.1.2 inherits: 2.0.3 setprototypeof: 1.1.0 statuses: 1.5.0 - /http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} + http-errors@2.0.0: dependencies: depd: 2.0.0 inherits: 2.0.4 @@ -7807,140 +12114,90 @@ packages: statuses: 2.0.1 toidentifier: 1.0.1 - /http-parser-js@0.5.8: - resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==} + http-parser-js@0.5.9: {} - /http-proxy-middleware@2.0.6(@types/express@4.17.17): - resolution: {integrity: sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/express': ^4.17.13 - peerDependenciesMeta: - '@types/express': - optional: true + http-proxy-middleware@2.0.7(@types/express@4.17.21): dependencies: - '@types/express': 4.17.17 - '@types/http-proxy': 1.17.11 + '@types/http-proxy': 1.17.16 http-proxy: 1.18.1 is-glob: 4.0.3 is-plain-obj: 3.0.0 - micromatch: 4.0.5 + micromatch: 4.0.8 + optionalDependencies: + '@types/express': 4.17.21 transitivePeerDependencies: - debug - /http-proxy@1.18.1: - resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} - engines: {node: '>=8.0.0'} + http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.2 + follow-redirects: 1.15.9 requires-port: 1.0.0 transitivePeerDependencies: - debug - /human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} + http2-wrapper@2.2.1: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 - /iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} + human-signals@2.1.0: {} + + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 - /icss-utils@5.1.0(postcss@8.4.29): - resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 + icss-utils@5.1.0(postcss@8.5.3): dependencies: - postcss: 8.4.29 + postcss: 8.5.3 - /ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: false + ieee754@1.2.1: {} - /ignore@4.0.6: - resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} - engines: {node: '>= 4'} + ignore@4.0.6: {} - /ignore@5.2.0: - resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} - engines: {node: '>= 4'} + ignore@5.3.2: {} - /ignore@5.2.4: - resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} - engines: {node: '>= 4'} + ignore@7.0.3: {} - /image-size@1.0.2: - resolution: {integrity: sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==} - engines: {node: '>=14.0.0'} - hasBin: true + image-size@1.2.0: dependencies: queue: 6.0.2 - /immer@9.0.21: - resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} + immer@9.0.21: {} - /import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} + immutable@5.0.3: {} + + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - /import-lazy@2.1.0: - resolution: {integrity: sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==} - engines: {node: '>=4'} - - /import-lazy@4.0.0: - resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} - engines: {node: '>=8'} - dev: true + import-lazy@4.0.0: {} - /import-modules@2.1.0: - resolution: {integrity: sha512-8HEWcnkbGpovH9yInoisxaSoIg9Brbul+Ju3Kqe2UsYDUBJD/iQjSgEj0zPcTDPKfPp2fs5xlv1i+JSye/m1/A==} - engines: {node: '>=8'} - dev: true + import-modules@2.1.0: {} - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} + imurmurhash@0.1.4: {} - /indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} + indent-string@4.0.0: {} - /infima@0.2.0-alpha.43: - resolution: {integrity: sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ==} - engines: {node: '>=12'} - dev: false + infima@0.2.0-alpha.45: {} - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + inflight@1.0.6: dependencies: once: 1.4.0 wrappy: 1.0.2 - /inherits@2.0.3: - resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + inherits@2.0.3: {} - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + inherits@2.0.4: {} - /ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + ini@1.3.8: {} - /ini@2.0.0: - resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} - engines: {node: '>=10'} + ini@2.0.0: {} - /inline-style-parser@0.1.1: - resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} + inline-style-parser@0.2.4: {} - /inquirer@7.3.3: - resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} - engines: {node: '>=8.0.0'} + inquirer@7.3.3: dependencies: ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -7955,2385 +12212,2057 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 through: 2.3.8 - dev: true - - /internal-slot@1.0.3: - resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.1.3 - has: 1.0.3 - side-channel: 1.0.4 - dev: true - /internal-slot@1.0.5: - resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} - engines: {node: '>= 0.4'} + internal-slot@1.1.0: dependencies: - get-intrinsic: 1.2.1 - has: 1.0.3 - side-channel: 1.0.4 - dev: true + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 - /interpret@1.4.0: - resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} - engines: {node: '>= 0.10'} + interpret@1.4.0: {} - /invariant@2.2.4: - resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + invariant@2.2.4: dependencies: loose-envify: 1.4.0 - /ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} + ipaddr.js@1.9.1: {} - /ipaddr.js@2.1.0: - resolution: {integrity: sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==} - engines: {node: '>= 10'} + ipaddr.js@2.2.0: {} - /is-alphabetical@1.0.4: - resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} + is-alphabetical@2.0.1: {} - /is-alphanumerical@1.0.4: - resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + is-alphanumerical@2.0.1: dependencies: - is-alphabetical: 1.0.4 - is-decimal: 1.0.4 + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 - /is-array-buffer@3.0.2: - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - is-typed-array: 1.1.12 - dev: true + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 - /is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-arrayish@0.2.1: {} - /is-arrayish@0.3.2: - resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - dev: false + is-arrayish@0.3.2: {} - /is-bigint@1.0.2: - resolution: {integrity: sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==} + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 - /is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} + is-bigint@1.1.0: dependencies: - binary-extensions: 2.2.0 + has-bigints: 1.1.0 - /is-boolean-object@1.1.1: - resolution: {integrity: sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==} - engines: {node: '>= 0.4'} + is-binary-path@2.1.0: dependencies: - call-bind: 1.0.2 + binary-extensions: 2.3.0 - /is-buffer@2.0.5: - resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} - engines: {node: '>=4'} + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 - /is-builtin-module@3.1.0: - resolution: {integrity: sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==} - engines: {node: '>=6'} + is-builtin-module@3.2.1: dependencies: - builtin-modules: 3.2.0 - dev: true + builtin-modules: 3.3.0 - /is-callable@1.2.3: - resolution: {integrity: sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==} - engines: {node: '>= 0.4'} + is-callable@1.2.7: {} - /is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - dev: true + is-ci@3.0.1: + dependencies: + ci-info: 3.9.0 - /is-ci@2.0.0: - resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} - hasBin: true + is-core-module@2.16.1: dependencies: - ci-info: 2.0.0 + hasown: 2.0.2 - /is-core-module@2.10.0: - resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==} + is-data-view@1.0.2: dependencies: - has: 1.0.3 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 - /is-core-module@2.13.0: - resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} + is-date-object@1.1.0: dependencies: - has: 1.0.3 + call-bound: 1.0.4 + has-tostringtag: 1.0.2 - /is-date-object@1.0.4: - resolution: {integrity: sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==} - engines: {node: '>= 0.4'} + is-decimal@2.0.1: {} - /is-decimal@1.0.4: - resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} + is-docker@2.2.1: {} - /is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true + is-extendable@0.1.1: {} - /is-extendable@0.1.1: - resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} - engines: {node: '>=0.10.0'} + is-extglob@2.1.1: {} - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 - /is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + is-fullwidth-code-point@3.0.0: {} - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 - /is-hexadecimal@1.0.4: - resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} + is-hexadecimal@2.0.1: {} - /is-installed-globally@0.4.0: - resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} - engines: {node: '>=10'} + is-installed-globally@0.4.0: dependencies: global-dirs: 3.0.1 is-path-inside: 3.0.3 - /is-negative-zero@2.0.1: - resolution: {integrity: sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==} - engines: {node: '>= 0.4'} - - /is-negative-zero@2.0.2: - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} - engines: {node: '>= 0.4'} - dev: true - - /is-npm@5.0.0: - resolution: {integrity: sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==} - engines: {node: '>=10'} + is-map@2.0.3: {} - /is-number-object@1.0.5: - resolution: {integrity: sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==} - engines: {node: '>= 0.4'} + is-npm@6.0.0: {} - /is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 - /is-obj@1.0.1: - resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} - engines: {node: '>=0.10.0'} + is-number@7.0.0: {} - /is-obj@2.0.0: - resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} - engines: {node: '>=8'} + is-obj@1.0.1: {} - /is-path-cwd@2.2.0: - resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} - engines: {node: '>=6'} + is-obj@2.0.0: {} - /is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} + is-path-cwd@2.2.0: {} - /is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - dev: true + is-path-inside@3.0.3: {} - /is-plain-obj@2.1.0: - resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} - engines: {node: '>=8'} + is-plain-obj@3.0.0: {} - /is-plain-obj@3.0.0: - resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} - engines: {node: '>=10'} + is-plain-obj@4.1.0: {} - /is-plain-object@2.0.4: - resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} - engines: {node: '>=0.10.0'} + is-plain-object@2.0.4: dependencies: isobject: 3.0.1 - /is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} + is-plain-object@5.0.0: {} - /is-regex@1.1.3: - resolution: {integrity: sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==} - engines: {node: '>= 0.4'} + is-regex@1.2.1: dependencies: - call-bind: 1.0.2 - has-symbols: 1.0.3 + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 - /is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} + is-regexp@1.0.0: {} + + is-root@2.1.0: {} + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 - dev: true + call-bound: 1.0.4 - /is-regexp@1.0.0: - resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} - engines: {node: '>=0.10.0'} + is-stream@2.0.1: {} - /is-regexp@2.1.0: - resolution: {integrity: sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==} - engines: {node: '>=6'} - dev: true + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 - /is-root@2.1.0: - resolution: {integrity: sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==} - engines: {node: '>=6'} + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 - /is-shared-array-buffer@1.0.2: - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + is-typed-array@1.1.15: dependencies: - call-bind: 1.0.2 - dev: true + which-typed-array: 1.1.19 - /is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} + is-typedarray@1.0.0: {} - /is-string@1.0.6: - resolution: {integrity: sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==} - engines: {node: '>= 0.4'} + is-weakmap@2.0.2: {} - /is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} + is-weakref@1.1.1: dependencies: - has-tostringtag: 1.0.0 - dev: true + call-bound: 1.0.4 - /is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} + is-weakset@2.0.4: dependencies: - has-symbols: 1.0.3 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 - /is-typed-array@1.1.12: - resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} - engines: {node: '>= 0.4'} + is-wsl@2.2.0: dependencies: - which-typed-array: 1.1.11 - dev: true + is-docker: 2.2.1 - /is-typedarray@1.0.0: - resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + is-yarn-global@0.4.1: {} - /is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - dev: true + isarray@0.0.1: {} + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isobject@3.0.1: {} + + isomorphic-rslog@0.0.6: {} + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.13.10 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 - /is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + jest-worker@27.5.1: dependencies: - call-bind: 1.0.2 - dev: true + '@types/node': 22.13.10 + merge-stream: 2.0.0 + supports-color: 8.1.1 - /is-whitespace-character@1.0.4: - resolution: {integrity: sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==} + jest-worker@29.7.0: + dependencies: + '@types/node': 22.13.10 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 - /is-word-character@1.0.4: - resolution: {integrity: sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==} + jiti@1.21.7: {} - /is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} + joi@17.13.3: dependencies: - is-docker: 2.2.1 + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 - /is-yarn-global@0.3.0: - resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==} + js-tokens@4.0.0: {} - /isarray@0.0.1: - resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 - /isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 - /isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - dev: true + jsesc@3.0.2: {} - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jsesc@3.1.0: {} - /isobject@3.0.1: - resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} - engines: {node: '>=0.10.0'} + json-buffer@3.0.1: {} - /jest-util@29.6.3: - resolution: {integrity: sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + json-fixer@1.6.15: dependencies: - '@jest/types': 29.6.3 - '@types/node': 20.5.9 + '@babel/runtime': 7.26.10 chalk: 4.1.2 - ci-info: 3.8.0 + pegjs: 0.10.0 + + json-parse-better-errors@1.0.2: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: graceful-fs: 4.2.11 - picomatch: 2.3.1 - /jest-worker@27.5.1: - resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} - engines: {node: '>= 10.13.0'} + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.8 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + keyv@5.3.1: + dependencies: + '@keyv/serialize': 1.0.3 + + kind-of@6.0.3: {} + + kleur@3.0.3: {} + + known-css-properties@0.35.0: {} + + latest-version@7.0.0: + dependencies: + package-json: 8.1.1 + + launch-editor@2.10.0: + dependencies: + picocolors: 1.1.1 + shell-quote: 1.8.2 + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-darwin-arm64@1.29.3: + optional: true + + lightningcss-darwin-x64@1.29.3: + optional: true + + lightningcss-freebsd-x64@1.29.3: + optional: true + + lightningcss-linux-arm-gnueabihf@1.29.3: + optional: true + + lightningcss-linux-arm64-gnu@1.29.3: + optional: true + + lightningcss-linux-arm64-musl@1.29.3: + optional: true + + lightningcss-linux-x64-gnu@1.29.3: + optional: true + + lightningcss-linux-x64-musl@1.29.3: + optional: true + + lightningcss-win32-arm64-msvc@1.29.3: + optional: true + + lightningcss-win32-x64-msvc@1.29.3: + optional: true + + lightningcss@1.29.3: dependencies: - '@types/node': 20.5.9 - merge-stream: 2.0.0 - supports-color: 8.1.1 + detect-libc: 2.0.3 + optionalDependencies: + lightningcss-darwin-arm64: 1.29.3 + lightningcss-darwin-x64: 1.29.3 + lightningcss-freebsd-x64: 1.29.3 + lightningcss-linux-arm-gnueabihf: 1.29.3 + lightningcss-linux-arm64-gnu: 1.29.3 + lightningcss-linux-arm64-musl: 1.29.3 + lightningcss-linux-x64-gnu: 1.29.3 + lightningcss-linux-x64-musl: 1.29.3 + lightningcss-win32-arm64-msvc: 1.29.3 + lightningcss-win32-x64-msvc: 1.29.3 - /jest-worker@29.6.4: - resolution: {integrity: sha512-6dpvFV4WjcWbDVGgHTWo/aupl8/LbBx2NSKfiwqf79xC/yeJjKHT1+StcKy/2KTmW16hE68ccKVOtXf+WZGz7Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-json-file@4.0.0: dependencies: - '@types/node': 20.5.9 - jest-util: 29.6.3 - merge-stream: 2.0.0 - supports-color: 8.1.1 + graceful-fs: 4.2.11 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 - /jiti@1.19.3: - resolution: {integrity: sha512-5eEbBDQT/jF1xg6l36P+mWGGoH9Spuy0PCdSr2dtWRDGC6ph/w9ZCL4lmESW8f8F7MwT3XKescfP0wnZWAKL9w==} - hasBin: true + loader-runner@4.3.0: {} - /joi@17.10.1: - resolution: {integrity: sha512-vIiDxQKmRidUVp8KngT8MZSOcmRVm2zV7jbMjNYWuHcJWI0bUck3nRTGQjhpPlQenIQIBC5Vp9AhcnHbWQqafw==} + loader-utils@2.0.4: dependencies: - '@hapi/hoek': 9.3.0 - '@hapi/topo': 5.1.0 - '@sideway/address': 4.1.4 - '@sideway/formula': 3.0.1 - '@sideway/pinpoint': 2.0.0 + big.js: 5.2.2 + emojis-list: 3.0.0 + json5: 2.2.3 - /js-cookie@2.2.1: - resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==} - dev: false + loader-utils@3.3.1: {} - /js-cookie@3.0.1: - resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==} - engines: {node: '>=12'} - dev: false + locate-path@2.0.0: + dependencies: + p-locate: 2.0.0 + path-exists: 3.0.0 - /js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 - /js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true + locate-path@5.0.0: dependencies: - argparse: 1.0.10 - esprima: 4.0.1 + p-locate: 4.1.0 - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true + locate-path@6.0.0: dependencies: - argparse: 2.0.1 + p-locate: 5.0.0 - /jsesc@0.5.0: - resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} - hasBin: true + locate-path@7.2.0: + dependencies: + p-locate: 6.0.0 - /jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true + lodash-es@4.17.21: {} - /json-buffer@3.0.0: - resolution: {integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==} + lodash.debounce@4.0.8: {} - /json-fixer@1.6.12: - resolution: {integrity: sha512-BGO9HExf0ZUVYvuWsps71Re513Ss0il1Wp7wYWkir2NthzincvNJEUu82KagEfAkGdjOMsypj3t2JB7drBKWnA==} - engines: {node: '>=10'} - dependencies: - '@babel/runtime': 7.19.0 - chalk: 4.1.2 - pegjs: 0.10.0 - dev: true + lodash.memoize@4.1.2: {} - /json-parse-better-errors@1.0.2: - resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} - dev: true + lodash.merge@4.6.2: {} - /json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + lodash.truncate@4.4.2: {} - /json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + lodash.uniq@4.5.0: {} - /json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + lodash@4.17.21: {} - /json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + longest-streak@3.1.0: {} - /json5@1.0.1: - resolution: {integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==} - hasBin: true + loose-envify@1.4.0: dependencies: - minimist: 1.2.8 - dev: true + js-tokens: 4.0.0 - /json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true + lower-case@2.0.2: dependencies: - minimist: 1.2.8 - dev: true - - /json5@2.2.1: - resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==} - engines: {node: '>=6'} - hasBin: true + tslib: 2.8.1 - /json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true + lowercase-keys@3.0.0: {} - /jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + lru-cache@5.1.1: dependencies: - universalify: 2.0.0 - optionalDependencies: - graceful-fs: 4.2.11 + yallist: 3.1.1 - /jsx-ast-utils@3.2.0: - resolution: {integrity: sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==} - engines: {node: '>=4.0'} - dependencies: - array-includes: 3.1.3 - object.assign: 4.1.4 - dev: true + markdown-extensions@2.0.0: {} - /keyv@3.1.0: - resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} + markdown-table@2.0.0: dependencies: - json-buffer: 3.0.0 + repeat-string: 1.6.1 - /kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} + markdown-table@3.0.4: {} - /kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} + math-intrinsics@1.1.0: {} - /klona@2.0.5: - resolution: {integrity: sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==} - engines: {node: '>= 8'} - dev: true + mathml-tag-names@2.1.3: {} - /known-css-properties@0.21.0: - resolution: {integrity: sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw==} - dev: true + mdast-util-directive@3.1.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-visit-parents: 6.0.1 + transitivePeerDependencies: + - supports-color - /latest-version@5.1.0: - resolution: {integrity: sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==} - engines: {node: '>=8'} + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.1.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-frontmatter@2.0.1: dependencies: - package-json: 6.5.0 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + escape-string-regexp: 5.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-extension-frontmatter: 2.0.0 + transitivePeerDependencies: + - supports-color - /launch-editor@2.6.0: - resolution: {integrity: sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==} + mdast-util-gfm-autolink-literal@2.0.1: dependencies: - picocolors: 1.0.0 - shell-quote: 1.8.1 + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 - /leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color - /levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + mdast-util-gfm-strikethrough@2.0.0: dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color - /lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color - /lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color - /load-json-file@4.0.0: - resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} - engines: {node: '>=4'} + mdast-util-gfm@3.1.0: dependencies: - graceful-fs: 4.2.10 - parse-json: 4.0.0 - pify: 3.0.0 - strip-bom: 3.0.0 - dev: true + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color - /loader-runner@4.3.0: - resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} - engines: {node: '>=6.11.5'} + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color - /loader-utils@2.0.2: - resolution: {integrity: sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==} - engines: {node: '>=8.9.0'} + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: dependencies: - big.js: 5.2.2 - emojis-list: 3.0.0 - json5: 2.2.1 + mdast-util-from-markdown: 2.0.2 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color - /loader-utils@2.0.4: - resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==} - engines: {node: '>=8.9.0'} + mdast-util-mdxjs-esm@2.0.1: dependencies: - big.js: 5.2.2 - emojis-list: 3.0.0 - json5: 2.2.3 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color - /loader-utils@3.2.1: - resolution: {integrity: sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==} - engines: {node: '>= 12.13.0'} + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 - /locate-path@2.0.0: - resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} - engines: {node: '>=4'} + mdast-util-to-hast@13.2.0: dependencies: - p-locate: 2.0.0 - path-exists: 3.0.0 - dev: true + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 - /locate-path@3.0.0: - resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} - engines: {node: '>=6'} + mdast-util-to-markdown@2.1.2: dependencies: - p-locate: 3.0.0 - path-exists: 3.0.0 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 - /locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} + mdast-util-to-string@4.0.0: dependencies: - p-locate: 4.1.0 + '@types/mdast': 4.0.4 - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + mdn-data@2.0.28: {} + + mdn-data@2.0.30: {} + + mdn-data@2.12.2: {} + + mdn-data@2.18.0: {} + + media-typer@0.3.0: {} + + medium-zoom@1.1.0: {} + + memfs@3.5.3: dependencies: - p-locate: 5.0.0 + fs-monkey: 1.0.6 - /lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - dev: false + meow@13.2.0: {} - /lodash.clonedeep@4.5.0: - resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + merge-descriptors@1.0.3: {} - /lodash.curry@4.1.1: - resolution: {integrity: sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==} - dev: false + merge-stream@2.0.0: {} - /lodash.debounce@4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + merge2@1.4.1: {} - /lodash.escape@4.0.1: - resolution: {integrity: sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==} + methods@1.1.2: {} - /lodash.flatten@4.4.0: - resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.1.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - /lodash.flow@3.5.0: - resolution: {integrity: sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==} - dev: false + micromark-extension-directive@3.0.2: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + parse-entities: 4.0.2 - /lodash.invokemap@4.6.0: - resolution: {integrity: sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w==} + micromark-extension-frontmatter@2.0.0: + dependencies: + fault: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - /lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - /lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - /lodash.pullall@4.2.0: - resolution: {integrity: sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg==} + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - /lodash.truncate@4.4.2: - resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - /lodash.uniq@4.5.0: - resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 - /lodash.uniqby@4.7.0: - resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==} + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - /lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.0: + dependencies: + '@types/estree': 1.0.6 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.2 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.2 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.1: + dependencies: + '@types/acorn': 4.0.6 + '@types/estree': 1.0.6 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.2 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.2 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.2 - /log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} + micromark-extension-mdx-md@2.0.0: dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - dev: true + micromark-util-types: 2.0.2 - /longest-streak@2.0.4: - resolution: {integrity: sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==} - dev: true + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.6 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.2 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.2 - /loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true + micromark-extension-mdxjs@3.0.0: dependencies: - js-tokens: 4.0.0 + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) + micromark-extension-mdx-expression: 3.0.0 + micromark-extension-mdx-jsx: 3.0.1 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 - /lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + micromark-factory-destination@2.0.1: dependencies: - tslib: 2.6.2 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - /lowercase-keys@1.0.1: - resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} - engines: {node: '>=0.10.0'} + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - /lowercase-keys@2.0.0: - resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} - engines: {node: '>=8'} + micromark-factory-mdx-expression@2.0.2: + dependencies: + '@types/estree': 1.0.6 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.2 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.2 - /lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + micromark-factory-space@1.1.0: dependencies: - yallist: 3.1.1 + micromark-util-character: 1.2.0 + micromark-util-types: 1.1.0 - /lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} + micromark-factory-space@2.0.1: dependencies: - yallist: 4.0.0 + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 - /make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} + micromark-factory-title@2.0.1: dependencies: - semver: 6.3.1 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - /map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - dev: true + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - /map-obj@4.2.1: - resolution: {integrity: sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==} - engines: {node: '>=8'} - dev: true + micromark-util-character@1.2.0: + dependencies: + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 - /markdown-escapes@1.0.4: - resolution: {integrity: sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==} + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - /mathml-tag-names@2.1.3: - resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} - dev: true + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 - /mdast-squeeze-paragraphs@4.0.0: - resolution: {integrity: sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ==} + micromark-util-classify-character@2.0.1: dependencies: - unist-util-remove: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - /mdast-util-definitions@4.0.0: - resolution: {integrity: sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==} + micromark-util-combine-extensions@2.0.1: dependencies: - unist-util-visit: 2.0.3 + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 - /mdast-util-from-markdown@0.8.5: - resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==} + micromark-util-decode-numeric-character-reference@2.0.2: dependencies: - '@types/mdast': 3.0.10 - mdast-util-to-string: 2.0.0 - micromark: 2.11.4 - parse-entities: 2.0.0 - unist-util-stringify-position: 2.0.3 - transitivePeerDependencies: - - supports-color - dev: true - - /mdast-util-to-hast@10.0.1: - resolution: {integrity: sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA==} - dependencies: - '@types/mdast': 3.0.12 - '@types/unist': 2.0.8 - mdast-util-definitions: 4.0.0 - mdurl: 1.0.1 - unist-builder: 2.0.3 - unist-util-generated: 1.1.6 - unist-util-position: 3.1.0 - unist-util-visit: 2.0.3 - - /mdast-util-to-markdown@0.6.5: - resolution: {integrity: sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==} - dependencies: - '@types/unist': 2.0.6 - longest-streak: 2.0.4 - mdast-util-to-string: 2.0.0 - parse-entities: 2.0.0 - repeat-string: 1.6.1 - zwitch: 1.0.5 - dev: true + micromark-util-symbol: 2.0.1 - /mdast-util-to-string@2.0.0: - resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==} + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 - /mdn-data@2.0.14: - resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + micromark-util-encode@2.0.1: {} - /mdn-data@2.0.4: - resolution: {integrity: sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==} - dev: false + micromark-util-events-to-acorn@2.0.2: + dependencies: + '@types/acorn': 4.0.6 + '@types/estree': 1.0.6 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.2 - /mdurl@1.0.1: - resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} + micromark-util-html-tag-name@2.0.1: {} - /media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 - /medium-zoom@1.0.8: - resolution: {integrity: sha512-CjFVuFq/IfrdqesAXfg+hzlDKu6A2n80ZIq0Kl9kWjoHh9j1N9Uvk5X0/MmN0hOfm5F9YBswlClhcwnmtwz7gA==} - dev: false + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 - /memfs@3.5.3: - resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} - engines: {node: '>= 4.0.0'} + micromark-util-sanitize-uri@2.0.1: dependencies: - fs-monkey: 1.0.4 + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 - /meow@9.0.0: - resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} - engines: {node: '>=10'} + micromark-util-subtokenize@2.1.0: dependencies: - '@types/minimist': 1.2.1 - camelcase-keys: 6.2.2 - decamelize: 1.2.0 - decamelize-keys: 1.1.0 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.2 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: 0.18.1 - yargs-parser: 20.2.9 - dev: true + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - /merge-descriptors@1.0.1: - resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + micromark-util-symbol@1.1.0: {} - /merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + micromark-util-symbol@2.0.1: {} - /merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} + micromark-util-types@1.1.0: {} - /methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} + micromark-util-types@2.0.2: {} - /micromark@2.11.4: - resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} + micromark@4.0.2: dependencies: - debug: 4.3.4 - parse-entities: 2.0.0 + '@types/debug': 4.1.12 + debug: 4.4.0 + decode-named-character-reference: 1.1.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 transitivePeerDependencies: - supports-color - dev: true - /micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} + micromatch@4.0.8: dependencies: - braces: 3.0.2 + braces: 3.0.3 picomatch: 2.3.1 - /mime-db@1.33.0: - resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==} - engines: {node: '>= 0.6'} + mime-db@1.33.0: {} - /mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} + mime-db@1.52.0: {} - /mime-types@2.1.18: - resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==} - engines: {node: '>= 0.6'} + mime-db@1.53.0: {} + + mime-types@2.1.18: dependencies: mime-db: 1.33.0 - /mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 - /mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} - hasBin: true - - /mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} + mime@1.6.0: {} - /mimic-response@1.0.1: - resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} - engines: {node: '>=4'} + mimic-fn@2.1.0: {} - /mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - dev: false + mimic-response@3.1.0: {} - /min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - dev: true + mimic-response@4.0.0: {} - /mini-css-extract-plugin@2.7.6(webpack@5.88.2): - resolution: {integrity: sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==} - engines: {node: '>= 12.13.0'} - peerDependencies: - webpack: ^5.0.0 + mini-css-extract-plugin@2.9.2(webpack@5.98.0(@swc/core@1.11.9)): dependencies: - schema-utils: 4.2.0 - webpack: 5.88.2 + schema-utils: 4.3.0 + tapable: 2.2.1 + webpack: 5.98.0(@swc/core@1.11.9) - /minimalistic-assert@1.0.1: - resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + minimalistic-assert@1.0.1: {} - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 - /minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} + minimatch@9.0.3: dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - dev: true - - /minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - /mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - dev: false + brace-expansion: 2.0.1 - /mkdirp@0.5.5: - resolution: {integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==} - hasBin: true - dependencies: - minimist: 1.2.8 - dev: false + minimist@1.2.8: {} - /mrmime@1.0.1: - resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} - engines: {node: '>=10'} + mkdirp-classic@0.5.3: {} - /ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + mrmime@2.0.1: {} - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.0.0: {} - /ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + ms@2.1.3: {} - /multicast-dns@7.2.5: - resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} - hasBin: true + multicast-dns@7.2.5: dependencies: dns-packet: 5.6.1 thunky: 1.1.0 - /multimap@1.1.0: - resolution: {integrity: sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw==} - dev: true + multimap@1.1.0: {} - /mute-stream@0.0.8: - resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} - dev: true + mute-stream@0.0.8: {} - /nanoid@3.3.6: - resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true + nanoid@3.3.9: {} + + napi-build-utils@2.0.0: {} - /napi-build-utils@1.0.2: - resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} - dev: false + natural-compare@1.4.0: {} - /natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@0.6.3: {} - /negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} + negotiator@0.6.4: {} - /neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + neo-async@2.6.2: {} - /no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.6.2 + tslib: 2.8.1 - /node-abi@3.5.0: - resolution: {integrity: sha512-LtHvNIBgOy5mO8mPEUtkCW/YCRWYEKshIvqhe1GHHyXEHEB5mgICyYnAcl4qan3uFeRROErKGzatFHPf6kDxWw==} - engines: {node: '>=10'} + node-abi@3.74.0: dependencies: - semver: 7.3.7 - dev: false + semver: 7.7.1 - /node-addon-api@4.2.0: - resolution: {integrity: sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q==} - dev: false + node-addon-api@6.1.0: {} - /node-addon-api@5.1.0: - resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} - dev: false - - /node-emoji@1.11.0: - resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} - dependencies: - lodash: 4.17.21 + node-addon-api@7.1.1: + optional: true - /node-fetch@2.6.6: - resolution: {integrity: sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==} - engines: {node: 4.x || >=6.0.0} + node-emoji@2.2.0: dependencies: - whatwg-url: 5.0.0 - dev: true + '@sindresorhus/is': 4.6.0 + char-regex: 1.0.2 + emojilib: 2.4.0 + skin-tone: 2.0.0 - /node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 - dev: false - - /node-forge@1.3.1: - resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} - engines: {node: '>= 6.13.0'} - /node-releases@2.0.13: - resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} + node-forge@1.3.1: {} - /node-releases@2.0.6: - resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} + node-releases@2.0.19: {} - /normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.1 - semver: 5.7.1 - validate-npm-package-license: 3.0.4 - dev: true - - /normalize-package-data@3.0.2: - resolution: {integrity: sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==} - engines: {node: '>=10'} - dependencies: - hosted-git-info: 4.0.2 - resolve: 1.22.1 - semver: 7.3.7 + resolve: 1.22.10 + semver: 5.7.2 validate-npm-package-license: 3.0.4 - dev: true - /normalize-path@2.1.1: - resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} - engines: {node: '>=0.10.0'} + normalize-path@2.1.1: dependencies: remove-trailing-separator: 1.1.0 - dev: true - - /normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - /normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - /normalize-selector@0.2.0: - resolution: {integrity: sha512-dxvWdI8gw6eAvk9BlPffgEoGfM7AdijoCwOEJge3e3ulT2XLgmU7KvvxprOaCu05Q1uGRHmOhHe1r6emZoKyFw==} - dev: true + normalize-path@3.0.0: {} - /normalize-url@4.5.1: - resolution: {integrity: sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==} - engines: {node: '>=8'} + normalize-range@0.1.2: {} - /normalize-url@6.1.0: - resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} - engines: {node: '>=10'} + normalize-url@8.0.1: {} - /npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} + npm-run-path@4.0.1: dependencies: path-key: 3.1.1 - /nprogress@0.2.0: - resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} - dev: false + nprogress@0.2.0: {} - /nth-check@1.0.2: - resolution: {integrity: sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==} + nth-check@2.1.1: dependencies: boolbase: 1.0.0 - dev: false - /nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + null-loader@4.0.1(webpack@5.98.0(@swc/core@1.11.9)): dependencies: - boolbase: 1.0.0 - - /num2fraction@1.2.2: - resolution: {integrity: sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==} - dev: true - - /object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} + loader-utils: 2.0.4 + schema-utils: 3.3.0 + webpack: 5.98.0(@swc/core@1.11.9) - /object-inspect@1.12.2: - resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} + object-assign@4.1.1: {} - /object-inspect@1.12.3: - resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} - dev: true + object-inspect@1.13.4: {} - /object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} + object-keys@1.1.1: {} - /object.assign@4.1.4: - resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} - engines: {node: '>= 0.4'} + object.assign@4.1.7: dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - has-symbols: 1.0.3 + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 object-keys: 1.1.1 - /object.entries@1.1.4: - resolution: {integrity: sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.18.3 - dev: true - - /object.fromentries@2.0.4: - resolution: {integrity: sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.18.3 - has: 1.0.3 - dev: true - - /object.fromentries@2.0.7: - resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 - dev: true - - /object.getownpropertydescriptors@2.1.2: - resolution: {integrity: sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==} - engines: {node: '>= 0.8'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.18.3 - dev: false - - /object.groupby@1.0.1: - resolution: {integrity: sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==} + object.entries@1.1.9: dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 - get-intrinsic: 1.2.1 - dev: true + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 - /object.values@1.1.4: - resolution: {integrity: sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==} - engines: {node: '>= 0.4'} + object.fromentries@2.0.8: dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.18.3 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 - /object.values@1.1.7: - resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} - engines: {node: '>= 0.4'} + object.values@1.2.1: dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 - dev: true + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 - /obuf@1.1.2: - resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + obuf@1.1.2: {} - /on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 - /on-headers@1.0.2: - resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} - engines: {node: '>= 0.8'} + on-headers@1.0.2: {} - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + once@1.4.0: dependencies: wrappy: 1.0.2 - /onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 - /open@8.4.2: - resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} - engines: {node: '>=12'} + open@8.4.2: dependencies: define-lazy-prop: 2.0.0 is-docker: 2.2.1 is-wsl: 2.2.0 - /opener@1.5.2: - resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} - hasBin: true + opener@1.5.2: {} - /opentype.js@0.11.0: - resolution: {integrity: sha512-Z9NkAyQi/iEKQYzCSa7/VJSqVIs33wknw8Z8po+DzuRUAqivJ+hJZ94mveg3xIeKwLreJdWTMyEO7x1K13l41Q==} - hasBin: true + opentype.js@0.11.0: dependencies: string.prototype.codepointat: 0.2.1 tiny-inflate: 1.0.3 - dev: false - /optionator@0.9.1: - resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} - engines: {node: '>= 0.8.0'} + optionator@0.9.4: dependencies: - deep-is: 0.1.3 + deep-is: 0.1.4 fast-levenshtein: 2.0.6 levn: 0.4.1 prelude-ls: 1.2.1 type-check: 0.4.0 - word-wrap: 1.2.3 + word-wrap: 1.2.5 - /os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - dev: true + os-tmpdir@1.0.2: {} - /p-cancelable@1.1.0: - resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==} - engines: {node: '>=6'} + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 - /p-limit@1.3.0: - resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} - engines: {node: '>=4'} + p-cancelable@3.0.0: {} + + p-limit@1.3.0: dependencies: p-try: 1.0.0 - dev: true - /p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} + p-limit@2.3.0: dependencies: p-try: 2.2.0 - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 - /p-locate@2.0.0: - resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} - engines: {node: '>=4'} + p-limit@4.0.0: + dependencies: + yocto-queue: 1.2.0 + + p-locate@2.0.0: dependencies: p-limit: 1.3.0 - dev: true - /p-locate@3.0.0: - resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} - engines: {node: '>=6'} + p-locate@3.0.0: dependencies: p-limit: 2.3.0 - /p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} + p-locate@4.1.0: dependencies: p-limit: 2.3.0 - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + p-locate@5.0.0: dependencies: p-limit: 3.1.0 - /p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + + p-map@4.0.0: dependencies: aggregate-error: 3.1.0 - /p-retry@4.6.2: - resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} - engines: {node: '>=8'} + p-retry@4.6.2: dependencies: '@types/retry': 0.12.0 retry: 0.13.1 - /p-try@1.0.0: - resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} - engines: {node: '>=4'} - dev: true + p-try@1.0.0: {} - /p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} + p-try@2.2.0: {} - /package-json@6.5.0: - resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==} - engines: {node: '>=8'} + package-json@8.1.1: dependencies: - got: 9.6.0 - registry-auth-token: 4.2.2 - registry-url: 5.1.0 - semver: 6.3.1 + got: 12.6.1 + registry-auth-token: 5.1.0 + registry-url: 6.0.1 + semver: 7.7.1 - /param-case@3.0.4: - resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} + param-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.8.1 - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + parent-module@1.0.1: dependencies: callsites: 3.1.0 - /parse-entities@2.0.0: - resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} + parse-entities@4.0.2: dependencies: - character-entities: 1.2.4 - character-entities-legacy: 1.1.4 - character-reference-invalid: 1.1.4 - is-alphanumerical: 1.0.4 - is-decimal: 1.0.4 - is-hexadecimal: 1.0.4 + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.1.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 - /parse-json@4.0.0: - resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} - engines: {node: '>=4'} + parse-json@4.0.0: dependencies: error-ex: 1.3.2 json-parse-better-errors: 1.0.2 - dev: true - /parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} + parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.18.6 + '@babel/code-frame': 7.26.2 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - /parse-numeric-range@1.3.0: - resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} - dev: false + parse-numeric-range@1.3.0: {} - /parse5-htmlparser2-tree-adapter@7.0.0: - resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} + parse5-htmlparser2-tree-adapter@7.1.0: dependencies: domhandler: 5.0.3 - parse5: 7.1.2 - dev: false - - /parse5@6.0.1: - resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + parse5: 7.2.1 - /parse5@7.1.2: - resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + parse5@7.2.1: dependencies: entities: 4.5.0 - dev: false - /parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} + parseurl@1.3.3: {} - /pascal-case@3.1.2: - resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + pascal-case@3.1.2: dependencies: no-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.8.1 - /path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} + path-exists@3.0.0: {} - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} + path-exists@4.0.0: {} - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} + path-exists@5.0.0: {} - /path-is-inside@1.0.2: - resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} + path-is-absolute@1.0.1: {} - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} + path-is-inside@1.0.2: {} - /path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-key@3.1.1: {} - /path-starts-with@1.0.0: - resolution: {integrity: sha512-CQVfSVN+llYh2D0HWG0jUGJ1ua9VbQyANgIo7cBis37FcpKiN13qh7UxttQqXtgcMZgzjpk/9RktoN6up/XLBQ==} - engines: {node: '>=4'} + path-parse@1.0.7: {} + + path-starts-with@1.0.0: dependencies: normalize-path: 2.1.1 - dev: true - /path-to-regexp@0.1.7: - resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + path-to-regexp@0.1.12: {} - /path-to-regexp@1.8.0: - resolution: {integrity: sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==} + path-to-regexp@1.9.0: dependencies: isarray: 0.0.1 - /path-to-regexp@2.2.1: - resolution: {integrity: sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==} + path-to-regexp@3.3.0: {} - /path-type@3.0.0: - resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} - engines: {node: '>=4'} + path-type@3.0.0: dependencies: pify: 3.0.0 - dev: true - /path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} + path-type@4.0.0: {} - /pegjs@0.10.0: - resolution: {integrity: sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==} - engines: {node: '>=0.10'} - hasBin: true - dev: true + pegjs@0.10.0: {} - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + picocolors@1.1.1: {} - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} + picomatch@2.3.1: {} - /pify@3.0.0: - resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} - engines: {node: '>=4'} - dev: true + pify@3.0.0: {} - /pify@5.0.0: - resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} - engines: {node: '>=10'} - dev: true + pify@5.0.0: {} - /pkg-dir@2.0.0: - resolution: {integrity: sha512-ojakdnUgL5pzJYWw2AIDEupaQCX5OPbM688ZevubICjdIX01PRSYKqm33fJoCOJBRseYCTUlQRnBNX+Pchaejw==} - engines: {node: '>=4'} + pkg-dir@7.0.0: + dependencies: + find-up: 6.3.0 + + pkg-up@2.0.0: dependencies: find-up: 2.1.0 - dev: true - /pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} + pkg-up@3.1.0: dependencies: - find-up: 4.1.0 + find-up: 3.0.0 - /pkg-up@2.0.0: - resolution: {integrity: sha512-fjAPuiws93rm7mPUu21RdBnkeZNrbfCFCwfAhPWY+rR3zG0ubpe5cEReHOw5fIbfmsxEV/g2kSxGTATY3Bpnwg==} - engines: {node: '>=4'} + plugin-image-zoom@1.2.0: dependencies: - find-up: 2.1.0 - dev: true + medium-zoom: 1.1.0 - /pkg-up@3.1.0: - resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} - engines: {node: '>=8'} + pluralize@8.0.0: {} + + possible-typed-array-names@1.1.0: {} + + postcss-attribute-case-insensitive@7.0.1(postcss@8.5.3): dependencies: - find-up: 3.0.0 + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 - /plugin-image-zoom@1.2.0: - resolution: {integrity: sha512-uVCjp4bXuli39gmBs+JQvXMtpfLL+5yWfRIKZyM41d3D9oxGBEHmRzDu9EgusIwmBrKJvF9QuOZENw/9s6G+Jw==} + postcss-calc@9.0.1(postcss@8.5.3): dependencies: - medium-zoom: 1.0.8 - dev: false + postcss: 8.5.3 + postcss-selector-parser: 6.1.2 + postcss-value-parser: 4.2.0 - /pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} - dev: true + postcss-clamp@4.1.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-value-parser: 4.2.0 - /postcss-calc@8.2.4(postcss@8.4.29): - resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} - peerDependencies: - postcss: ^8.2.2 + postcss-color-functional-notation@7.0.8(postcss@8.5.3): + dependencies: + '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3) + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 + + postcss-color-hex-alpha@10.0.0(postcss@8.5.3): dependencies: - postcss: 8.4.29 - postcss-selector-parser: 6.0.13 + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-colormin@5.3.1(postcss@8.4.29): - resolution: {integrity: sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-color-rebeccapurple@10.0.0(postcss@8.5.3): + dependencies: + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + + postcss-colormin@6.1.0(postcss@8.5.3): dependencies: - browserslist: 4.21.10 + browserslist: 4.24.4 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.4.29 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-convert-values@5.1.3(postcss@8.4.29): - resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-convert-values@6.1.0(postcss@8.5.3): dependencies: - browserslist: 4.21.10 - postcss: 8.4.29 + browserslist: 4.24.4 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-discard-comments@5.1.2(postcss@8.4.29): - resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-custom-media@11.0.5(postcss@8.5.3): dependencies: - postcss: 8.4.29 + '@csstools/cascade-layer-name-parser': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + postcss: 8.5.3 - /postcss-discard-duplicates@5.1.0(postcss@8.4.29): - resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-custom-properties@14.0.4(postcss@8.5.3): dependencies: - postcss: 8.4.29 + '@csstools/cascade-layer-name-parser': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 + postcss-value-parser: 4.2.0 - /postcss-discard-empty@5.1.1(postcss@8.4.29): - resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-custom-selectors@8.0.4(postcss@8.5.3): dependencies: - postcss: 8.4.29 + '@csstools/cascade-layer-name-parser': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 - /postcss-discard-overridden@5.1.0(postcss@8.4.29): - resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-dir-pseudo-class@9.0.1(postcss@8.5.3): dependencies: - postcss: 8.4.29 + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 - /postcss-discard-unused@5.1.0(postcss@8.4.29): - resolution: {integrity: sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-discard-comments@6.0.2(postcss@8.5.3): dependencies: - postcss: 8.4.29 - postcss-selector-parser: 6.0.13 + postcss: 8.5.3 - /postcss-html@0.36.0(postcss-syntax@0.36.2)(postcss@7.0.36): - resolution: {integrity: sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==} - peerDependencies: - postcss: '>=5.0.0' - postcss-syntax: '>=0.36.0' + postcss-discard-duplicates@6.0.3(postcss@8.5.3): dependencies: - htmlparser2: 3.10.1 - postcss: 7.0.36 - postcss-syntax: 0.36.2(postcss@8.4.29) - dev: true + postcss: 8.5.3 - /postcss-less@3.1.4: - resolution: {integrity: sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==} - engines: {node: '>=6.14.4'} + postcss-discard-empty@6.0.3(postcss@8.5.3): dependencies: - postcss: 7.0.36 - dev: true + postcss: 8.5.3 - /postcss-loader@7.3.3(postcss@8.4.29)(typescript@4.9.5)(webpack@5.88.2): - resolution: {integrity: sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==} - engines: {node: '>= 14.15.0'} - peerDependencies: - postcss: ^7.0.0 || ^8.0.1 - webpack: ^5.0.0 + postcss-discard-overridden@6.0.2(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + + postcss-discard-unused@6.0.5(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-selector-parser: 6.1.2 + + postcss-double-position-gradients@6.0.0(postcss@8.5.3): + dependencies: + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3) + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + + postcss-focus-visible@10.0.1(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 + + postcss-focus-within@9.0.1(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 + + postcss-font-variant@5.0.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + + postcss-gap-properties@6.0.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + + postcss-image-set-function@7.0.0(postcss@8.5.3): dependencies: - cosmiconfig: 8.3.1(typescript@4.9.5) - jiti: 1.19.3 - postcss: 8.4.29 - semver: 7.5.4 - webpack: 5.88.2 + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + + postcss-lab-function@7.0.8(postcss@8.5.3): + dependencies: + '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3) + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 + + postcss-loader@7.3.4(postcss@8.5.3)(typescript@5.8.2)(webpack@5.98.0(@swc/core@1.11.9)): + dependencies: + cosmiconfig: 8.3.6(typescript@5.8.2) + jiti: 1.21.7 + postcss: 8.5.3 + semver: 7.7.1 + webpack: 5.98.0(@swc/core@1.11.9) transitivePeerDependencies: - typescript - /postcss-media-query-parser@0.2.3: - resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} - dev: true + postcss-logical@8.1.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-value-parser: 4.2.0 - /postcss-merge-idents@5.1.1(postcss@8.4.29): - resolution: {integrity: sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-media-query-parser@0.2.3: {} + + postcss-merge-idents@6.0.3(postcss@8.5.3): dependencies: - cssnano-utils: 3.1.0(postcss@8.4.29) - postcss: 8.4.29 + cssnano-utils: 4.0.2(postcss@8.5.3) + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-merge-longhand@5.1.7(postcss@8.4.29): - resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-merge-longhand@6.0.5(postcss@8.5.3): dependencies: - postcss: 8.4.29 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - stylehacks: 5.1.1(postcss@8.4.29) + stylehacks: 6.1.1(postcss@8.5.3) - /postcss-merge-rules@5.1.4(postcss@8.4.29): - resolution: {integrity: sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-merge-rules@6.1.1(postcss@8.5.3): dependencies: - browserslist: 4.21.10 + browserslist: 4.24.4 caniuse-api: 3.0.0 - cssnano-utils: 3.1.0(postcss@8.4.29) - postcss: 8.4.29 - postcss-selector-parser: 6.0.13 + cssnano-utils: 4.0.2(postcss@8.5.3) + postcss: 8.5.3 + postcss-selector-parser: 6.1.2 - /postcss-minify-font-values@5.1.0(postcss@8.4.29): - resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-minify-font-values@6.1.0(postcss@8.5.3): dependencies: - postcss: 8.4.29 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-minify-gradients@5.1.1(postcss@8.4.29): - resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-minify-gradients@6.0.3(postcss@8.5.3): dependencies: colord: 2.9.3 - cssnano-utils: 3.1.0(postcss@8.4.29) - postcss: 8.4.29 + cssnano-utils: 4.0.2(postcss@8.5.3) + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-minify-params@5.1.4(postcss@8.4.29): - resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-minify-params@6.1.0(postcss@8.5.3): dependencies: - browserslist: 4.21.10 - cssnano-utils: 3.1.0(postcss@8.4.29) - postcss: 8.4.29 + browserslist: 4.24.4 + cssnano-utils: 4.0.2(postcss@8.5.3) + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-minify-selectors@5.2.1(postcss@8.4.29): - resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-minify-selectors@6.0.4(postcss@8.5.3): dependencies: - postcss: 8.4.29 - postcss-selector-parser: 6.0.13 + postcss: 8.5.3 + postcss-selector-parser: 6.1.2 - /postcss-modules-extract-imports@3.0.0(postcss@8.4.29): - resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 + postcss-modules-extract-imports@3.1.0(postcss@8.5.3): dependencies: - postcss: 8.4.29 + postcss: 8.5.3 - /postcss-modules-local-by-default@4.0.3(postcss@8.4.29): - resolution: {integrity: sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 + postcss-modules-local-by-default@4.2.0(postcss@8.5.3): dependencies: - icss-utils: 5.1.0(postcss@8.4.29) - postcss: 8.4.29 - postcss-selector-parser: 6.0.13 + icss-utils: 5.1.0(postcss@8.5.3) + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 - /postcss-modules-scope@3.0.0(postcss@8.4.29): - resolution: {integrity: sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 + postcss-modules-scope@3.2.1(postcss@8.5.3): dependencies: - postcss: 8.4.29 - postcss-selector-parser: 6.0.13 + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 - /postcss-modules-values@4.0.0(postcss@8.4.29): - resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 + postcss-modules-values@4.0.0(postcss@8.5.3): dependencies: - icss-utils: 5.1.0(postcss@8.4.29) - postcss: 8.4.29 + icss-utils: 5.1.0(postcss@8.5.3) + postcss: 8.5.3 - /postcss-normalize-charset@5.1.0(postcss@8.4.29): - resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-nesting@13.0.1(postcss@8.5.3): dependencies: - postcss: 8.4.29 + '@csstools/selector-resolve-nested': 3.0.0(postcss-selector-parser@7.1.0) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 - /postcss-normalize-display-values@5.1.0(postcss@8.4.29): - resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-normalize-charset@6.0.2(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + + postcss-normalize-display-values@6.0.2(postcss@8.5.3): dependencies: - postcss: 8.4.29 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-normalize-positions@5.1.1(postcss@8.4.29): - resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-normalize-positions@6.0.2(postcss@8.5.3): dependencies: - postcss: 8.4.29 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-normalize-repeat-style@5.1.1(postcss@8.4.29): - resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-normalize-repeat-style@6.0.2(postcss@8.5.3): dependencies: - postcss: 8.4.29 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-normalize-string@5.1.0(postcss@8.4.29): - resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-normalize-string@6.0.2(postcss@8.5.3): dependencies: - postcss: 8.4.29 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-normalize-timing-functions@5.1.0(postcss@8.4.29): - resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-normalize-timing-functions@6.0.2(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + + postcss-normalize-unicode@6.1.0(postcss@8.5.3): dependencies: - postcss: 8.4.29 + browserslist: 4.24.4 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-normalize-unicode@5.1.1(postcss@8.4.29): - resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-normalize-url@6.0.2(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + + postcss-normalize-whitespace@6.0.2(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + + postcss-opacity-percentage@3.0.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + + postcss-ordered-values@6.0.2(postcss@8.5.3): dependencies: - browserslist: 4.21.10 - postcss: 8.4.29 + cssnano-utils: 4.0.2(postcss@8.5.3) + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-normalize-url@5.1.0(postcss@8.4.29): - resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-overflow-shorthand@6.0.0(postcss@8.5.3): dependencies: - normalize-url: 6.1.0 - postcss: 8.4.29 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-normalize-whitespace@5.1.1(postcss@8.4.29): - resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-page-break@3.0.4(postcss@8.5.3): dependencies: - postcss: 8.4.29 - postcss-value-parser: 4.2.0 + postcss: 8.5.3 - /postcss-ordered-values@5.1.3(postcss@8.4.29): - resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-place@10.0.0(postcss@8.5.3): dependencies: - cssnano-utils: 3.1.0(postcss@8.4.29) - postcss: 8.4.29 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-reduce-idents@5.2.0(postcss@8.4.29): - resolution: {integrity: sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.29 + postcss-preset-env@10.1.5(postcss@8.5.3): + dependencies: + '@csstools/postcss-cascade-layers': 5.0.1(postcss@8.5.3) + '@csstools/postcss-color-function': 4.0.8(postcss@8.5.3) + '@csstools/postcss-color-mix-function': 3.0.8(postcss@8.5.3) + '@csstools/postcss-content-alt-text': 2.0.4(postcss@8.5.3) + '@csstools/postcss-exponential-functions': 2.0.7(postcss@8.5.3) + '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.5.3) + '@csstools/postcss-gamut-mapping': 2.0.8(postcss@8.5.3) + '@csstools/postcss-gradients-interpolation-method': 5.0.8(postcss@8.5.3) + '@csstools/postcss-hwb-function': 4.0.8(postcss@8.5.3) + '@csstools/postcss-ic-unit': 4.0.0(postcss@8.5.3) + '@csstools/postcss-initial': 2.0.1(postcss@8.5.3) + '@csstools/postcss-is-pseudo-class': 5.0.1(postcss@8.5.3) + '@csstools/postcss-light-dark-function': 2.0.7(postcss@8.5.3) + '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.5.3) + '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.5.3) + '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.5.3) + '@csstools/postcss-logical-resize': 3.0.0(postcss@8.5.3) + '@csstools/postcss-logical-viewport-units': 3.0.3(postcss@8.5.3) + '@csstools/postcss-media-minmax': 2.0.7(postcss@8.5.3) + '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.4(postcss@8.5.3) + '@csstools/postcss-nested-calc': 4.0.0(postcss@8.5.3) + '@csstools/postcss-normalize-display-values': 4.0.0(postcss@8.5.3) + '@csstools/postcss-oklab-function': 4.0.8(postcss@8.5.3) + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3) + '@csstools/postcss-random-function': 1.0.3(postcss@8.5.3) + '@csstools/postcss-relative-color-syntax': 3.0.8(postcss@8.5.3) + '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.5.3) + '@csstools/postcss-sign-functions': 1.1.2(postcss@8.5.3) + '@csstools/postcss-stepped-value-functions': 4.0.7(postcss@8.5.3) + '@csstools/postcss-text-decoration-shorthand': 4.0.2(postcss@8.5.3) + '@csstools/postcss-trigonometric-functions': 4.0.7(postcss@8.5.3) + '@csstools/postcss-unset-value': 4.0.0(postcss@8.5.3) + autoprefixer: 10.4.21(postcss@8.5.3) + browserslist: 4.24.4 + css-blank-pseudo: 7.0.1(postcss@8.5.3) + css-has-pseudo: 7.0.2(postcss@8.5.3) + css-prefers-color-scheme: 10.0.0(postcss@8.5.3) + cssdb: 8.2.4 + postcss: 8.5.3 + postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.3) + postcss-clamp: 4.1.0(postcss@8.5.3) + postcss-color-functional-notation: 7.0.8(postcss@8.5.3) + postcss-color-hex-alpha: 10.0.0(postcss@8.5.3) + postcss-color-rebeccapurple: 10.0.0(postcss@8.5.3) + postcss-custom-media: 11.0.5(postcss@8.5.3) + postcss-custom-properties: 14.0.4(postcss@8.5.3) + postcss-custom-selectors: 8.0.4(postcss@8.5.3) + postcss-dir-pseudo-class: 9.0.1(postcss@8.5.3) + postcss-double-position-gradients: 6.0.0(postcss@8.5.3) + postcss-focus-visible: 10.0.1(postcss@8.5.3) + postcss-focus-within: 9.0.1(postcss@8.5.3) + postcss-font-variant: 5.0.0(postcss@8.5.3) + postcss-gap-properties: 6.0.0(postcss@8.5.3) + postcss-image-set-function: 7.0.0(postcss@8.5.3) + postcss-lab-function: 7.0.8(postcss@8.5.3) + postcss-logical: 8.1.0(postcss@8.5.3) + postcss-nesting: 13.0.1(postcss@8.5.3) + postcss-opacity-percentage: 3.0.0(postcss@8.5.3) + postcss-overflow-shorthand: 6.0.0(postcss@8.5.3) + postcss-page-break: 3.0.4(postcss@8.5.3) + postcss-place: 10.0.0(postcss@8.5.3) + postcss-pseudo-class-any-link: 10.0.1(postcss@8.5.3) + postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.3) + postcss-selector-not: 8.0.1(postcss@8.5.3) + + postcss-pseudo-class-any-link@10.0.1(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 + + postcss-reduce-idents@6.0.3(postcss@8.5.3): + dependencies: + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-reduce-initial@5.1.2(postcss@8.4.29): - resolution: {integrity: sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-reduce-initial@6.1.0(postcss@8.5.3): dependencies: - browserslist: 4.21.10 + browserslist: 4.24.4 caniuse-api: 3.0.0 - postcss: 8.4.29 + postcss: 8.5.3 - /postcss-reduce-transforms@5.1.0(postcss@8.4.29): - resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-reduce-transforms@6.0.2(postcss@8.5.3): dependencies: - postcss: 8.4.29 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - /postcss-resolve-nested-selector@0.1.1: - resolution: {integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==} - dev: true + postcss-replace-overflow-wrap@4.0.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + + postcss-resolve-nested-selector@0.1.6: {} - /postcss-safe-parser@4.0.2: - resolution: {integrity: sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==} - engines: {node: '>=6.0.0'} + postcss-safe-parser@7.0.1(postcss@8.5.3): dependencies: - postcss: 7.0.36 - dev: true + postcss: 8.5.3 - /postcss-sass@0.4.4: - resolution: {integrity: sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg==} + postcss-scss@4.0.9(postcss@8.5.3): dependencies: - gonzales-pe: 4.3.0 - postcss: 7.0.36 - dev: true + postcss: 8.5.3 - /postcss-scss@2.1.1: - resolution: {integrity: sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==} - engines: {node: '>=6.0.0'} + postcss-selector-not@8.0.1(postcss@8.5.3): dependencies: - postcss: 7.0.36 - dev: true + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 - /postcss-selector-parser@6.0.10: - resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} - engines: {node: '>=4'} + postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - dev: true - /postcss-selector-parser@6.0.13: - resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==} - engines: {node: '>=4'} + postcss-selector-parser@7.1.0: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - /postcss-sort-media-queries@4.4.1(postcss@8.4.29): - resolution: {integrity: sha512-QDESFzDDGKgpiIh4GYXsSy6sek2yAwQx1JASl5AxBtU1Lq2JfKBljIPNdil989NcSKRQX1ToiaKphImtBuhXWw==} - engines: {node: '>=10.0.0'} - peerDependencies: - postcss: ^8.4.16 + postcss-sort-media-queries@5.2.0(postcss@8.5.3): dependencies: - postcss: 8.4.29 - sort-css-media-queries: 2.1.0 + postcss: 8.5.3 + sort-css-media-queries: 2.2.0 - /postcss-sorting@5.0.1: - resolution: {integrity: sha512-Y9fUFkIhfrm6i0Ta3n+89j56EFqaNRdUKqXyRp6kvTcSXnmgEjaVowCXH+JBe9+YKWqd4nc28r2sgwnzJalccA==} - engines: {node: '>=8.7.0'} + postcss-sorting@8.0.2(postcss@8.5.3): dependencies: - lodash: 4.17.21 - postcss: 7.0.36 - dev: true + postcss: 8.5.3 - /postcss-svgo@5.1.0(postcss@8.4.29): - resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-svgo@6.0.3(postcss@8.5.3): dependencies: - postcss: 8.4.29 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - svgo: 2.8.0 - - /postcss-syntax@0.36.2(postcss@8.4.29): - resolution: {integrity: sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==} - peerDependencies: - postcss: '>=5.0.0' - postcss-html: '*' - postcss-jsx: '*' - postcss-less: '*' - postcss-markdown: '*' - postcss-scss: '*' - peerDependenciesMeta: - postcss-html: - optional: true - postcss-jsx: - optional: true - postcss-less: - optional: true - postcss-markdown: - optional: true - postcss-scss: - optional: true - dependencies: - postcss: 8.4.29 - dev: true + svgo: 3.3.2 - /postcss-unique-selectors@5.1.1(postcss@8.4.29): - resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + postcss-unique-selectors@6.0.4(postcss@8.5.3): dependencies: - postcss: 8.4.29 - postcss-selector-parser: 6.0.13 - - /postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + postcss: 8.5.3 + postcss-selector-parser: 6.1.2 - /postcss-zindex@5.1.0(postcss@8.4.29): - resolution: {integrity: sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.29 + postcss-value-parser@4.2.0: {} - /postcss@7.0.36: - resolution: {integrity: sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==} - engines: {node: '>=6.0.0'} + postcss-zindex@6.0.2(postcss@8.5.3): dependencies: - chalk: 2.4.2 - source-map: 0.6.1 - supports-color: 6.1.0 - dev: true + postcss: 8.5.3 - /postcss@8.4.29: - resolution: {integrity: sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==} - engines: {node: ^10 || ^12 || >=14} + postcss@8.5.3: dependencies: - nanoid: 3.3.6 - picocolors: 1.0.0 - source-map-js: 1.0.2 + nanoid: 3.3.9 + picocolors: 1.1.1 + source-map-js: 1.2.1 - /prebuild-install@7.1.1: - resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} - engines: {node: '>=10'} - hasBin: true + prebuild-install@7.1.3: dependencies: - detect-libc: 2.0.1 + detect-libc: 2.0.3 expand-template: 2.0.3 github-from-package: 0.0.0 minimist: 1.2.8 mkdirp-classic: 0.5.3 - napi-build-utils: 1.0.2 - node-abi: 3.5.0 - pump: 3.0.0 + napi-build-utils: 2.0.0 + node-abi: 3.74.0 + pump: 3.0.2 rc: 1.2.8 simple-get: 4.0.1 - tar-fs: 2.1.1 + tar-fs: 2.1.2 tunnel-agent: 0.6.0 - dev: false - - /prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - /prepend-http@2.0.0: - resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==} - engines: {node: '>=4'} + prelude-ls@1.2.1: {} - /prettier-linter-helpers@1.0.0: - resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} - engines: {node: '>=6.0.0'} - dependencies: - fast-diff: 1.2.0 - dev: true + prettier@2.8.8: + optional: true - /prettier@2.3.0: - resolution: {integrity: sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==} - engines: {node: '>=10.13.0'} - hasBin: true - dev: true + prettier@3.5.3: {} - /pretty-error@4.0.0: - resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==} + pretty-error@4.0.0: dependencies: lodash: 4.17.21 renderkid: 3.0.0 - /pretty-time@1.1.0: - resolution: {integrity: sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==} - engines: {node: '>=4'} + pretty-time@1.1.0: {} - /prism-react-renderer@1.3.5(react@17.0.2): - resolution: {integrity: sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg==} - peerDependencies: - react: '>=0.14.9' + prism-react-renderer@2.4.1(react@18.3.1): dependencies: - react: 17.0.2 - dev: false + '@types/prismjs': 1.26.5 + clsx: 2.1.1 + react: 18.3.1 - /prismjs@1.29.0: - resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} - engines: {node: '>=6'} - dev: false - - /process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + prismjs@1.30.0: {} - /progress@2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} + process-nextick-args@2.0.1: {} - /promise@7.3.1: - resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} - dependencies: - asap: 2.0.6 - dev: false + progress@2.0.3: {} - /prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} + prompts@2.4.2: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 - /prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 - /property-information@5.6.0: - resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==} - dependencies: - xtend: 4.0.2 + property-information@6.5.0: {} - /proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} + property-information@7.0.0: {} + + proto-list@1.2.4: {} + + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 ipaddr.js: 1.9.1 - /pump@3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + pump@3.0.2: dependencies: end-of-stream: 1.4.4 once: 1.4.0 - /punycode@1.4.1: - resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} - - /punycode@2.1.1: - resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} - engines: {node: '>=6'} + punycode@2.3.1: {} - /pupa@2.1.1: - resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==} - engines: {node: '>=8'} + pupa@3.1.0: dependencies: - escape-goat: 2.1.1 + escape-goat: 4.0.0 - /pure-color@1.3.0: - resolution: {integrity: sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==} - dev: false + pushfeedback-react@0.1.50: + dependencies: + pushfeedback: 0.1.52 - /q@1.5.1: - resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} - engines: {node: '>=0.6.0', teleport: '>=0.2.0'} - dev: false + pushfeedback@0.1.52: + dependencies: + '@stencil/core': 2.22.3 + html2canvas: 1.4.1 - /qs@6.11.0: - resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} - engines: {node: '>=0.6'} + qs@6.13.0: dependencies: - side-channel: 1.0.4 + side-channel: 1.1.0 - /queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + queue-microtask@1.2.3: {} - /queue@6.0.2: - resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} + queue@6.0.2: dependencies: inherits: 2.0.4 - /quick-lru@4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} - dev: true + quick-lru@5.1.1: {} - /randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 - /range-parser@1.2.0: - resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==} - engines: {node: '>= 0.6'} + range-parser@1.2.0: {} - /range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} + range-parser@1.2.1: {} - /raw-body@2.5.1: - resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} - engines: {node: '>= 0.8'} + raw-body@2.5.2: dependencies: bytes: 3.1.2 http-errors: 2.0.0 iconv-lite: 0.4.24 unpipe: 1.0.0 - /rc-util@5.12.2(react-dom@17.0.2)(react@17.0.2): - resolution: {integrity: sha512-kzqG2lHY4oZsoj5Svov12K+9wi0xQHvGzfbLlsF1PDEH1aTbgdNTwlE7mejc3MGEr+7bNHa4+T5ZemCS8vQ1Gw==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' + rc-util@5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.19.0 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - react-is: 16.13.1 - shallowequal: 1.1.0 - dev: false + '@babel/runtime': 7.26.10 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 - /rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true + rc@1.2.8: dependencies: deep-extend: 0.6.0 ini: 1.3.8 minimist: 1.2.8 strip-json-comments: 2.0.1 - /react-base16-styling@0.6.0: - resolution: {integrity: sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ==} - dependencies: - base16: 1.0.0 - lodash.curry: 4.1.1 - lodash.flow: 3.5.0 - pure-color: 1.3.0 - dev: false - - /react-cookie-consent@6.4.1(react@17.0.2): - resolution: {integrity: sha512-dAgC2uSrUN0eXoNhEKDCHH/dL3RUs/84SPO8hRvJVz7HtPGmF+t/mQwd4V4qsGAvVqhbn0hzMdftEuiJS3iueQ==} - peerDependencies: - react: ^16.13.1 || ^17.0.0 - dependencies: - js-cookie: 2.2.1 - prop-types: 15.8.1 - react: 17.0.2 - dev: false - - /react-dev-utils@12.0.1(eslint@7.32.0)(typescript@4.9.5)(webpack@5.88.2): - resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=2.7' - webpack: '>=4' - peerDependenciesMeta: - typescript: - optional: true + react-dev-utils@12.0.1(eslint@7.32.0)(typescript@5.8.2)(webpack@5.98.0(@swc/core@1.11.9)): dependencies: - '@babel/code-frame': 7.22.13 + '@babel/code-frame': 7.26.2 address: 1.2.2 - browserslist: 4.21.10 + browserslist: 4.24.4 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 detect-port-alt: 1.1.6 escape-string-regexp: 4.0.0 filesize: 8.0.7 find-up: 5.0.0 - fork-ts-checker-webpack-plugin: 6.5.3(eslint@7.32.0)(typescript@4.9.5)(webpack@5.88.2) + fork-ts-checker-webpack-plugin: 6.5.3(eslint@7.32.0)(typescript@5.8.2)(webpack@5.98.0(@swc/core@1.11.9)) global-modules: 2.0.0 globby: 11.1.0 gzip-size: 6.0.0 immer: 9.0.21 is-root: 2.1.0 - loader-utils: 3.2.1 + loader-utils: 3.3.1 open: 8.4.2 pkg-up: 3.1.0 prompts: 2.4.2 - react-error-overlay: 6.0.11 + react-error-overlay: 6.1.0 recursive-readdir: 2.2.3 - shell-quote: 1.8.1 + shell-quote: 1.8.2 strip-ansi: 6.0.1 text-table: 0.2.0 - typescript: 4.9.5 - webpack: 5.88.2 + webpack: 5.98.0(@swc/core@1.11.9) + optionalDependencies: + typescript: 5.8.2 transitivePeerDependencies: - eslint - supports-color - vue-template-compiler - /react-dom@17.0.2(react@17.0.2): - resolution: {integrity: sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==} - peerDependencies: - react: 17.0.2 + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 - object-assign: 4.1.1 - react: 17.0.2 - scheduler: 0.20.2 - - /react-error-overlay@6.0.11: - resolution: {integrity: sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==} + react: 18.3.1 + scheduler: 0.23.2 - /react-fast-compare@3.2.2: - resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + react-error-overlay@6.1.0: {} - /react-fast-marquee@1.3.5(react-dom@17.0.2)(react@17.0.2): - resolution: {integrity: sha512-eOqLoz4iVVBvi2wN/web8hd2XX9y2Z6CYR7g++7nTVHlTOXBtqyARQJ9rYNpbp179hAzloMx0yBFAo8LpNYmKQ==} - peerDependencies: - react: '>= 16.8.0 || 18.0.0' - react-dom: '>= 16.8.0 || 18.0.0' - dependencies: - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - dev: false + react-fast-compare@3.2.2: {} - /react-helmet-async@1.3.0(react-dom@17.0.2)(react@17.0.2): - resolution: {integrity: sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==} - peerDependencies: - react: ^16.6.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0 + react-fast-marquee@1.6.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.22.11 - invariant: 2.2.4 - prop-types: 15.8.1 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - react-fast-compare: 3.2.2 - shallowequal: 1.1.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - /react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@16.13.1: {} - /react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - dev: false + react-is@18.3.1: {} - /react-json-view@1.21.3(react-dom@17.0.2)(react@17.0.2): - resolution: {integrity: sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==} - peerDependencies: - react: ^17.0.0 || ^16.3.0 || ^15.5.4 - react-dom: ^17.0.0 || ^16.3.0 || ^15.5.4 + react-json-view-lite@1.5.0(react@18.3.1): dependencies: - flux: 4.0.4(react@17.0.2) - react: 17.0.2 - react-base16-styling: 0.6.0 - react-dom: 17.0.2(react@17.0.2) - react-lifecycles-compat: 3.0.4 - react-textarea-autosize: 8.5.3(react@17.0.2) - transitivePeerDependencies: - - '@types/react' - - encoding - dev: false - - /react-lifecycles-compat@3.0.4: - resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} - dev: false + react: 18.3.1 - /react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@5.5.2)(webpack@5.88.2): - resolution: {integrity: sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==} - engines: {node: '>=10.13.0'} - peerDependencies: - react-loadable: '*' - webpack: '>=4.41.1 || 5.x' + react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@6.0.0(react@18.3.1))(webpack@5.98.0(@swc/core@1.11.9)): dependencies: - '@babel/runtime': 7.22.11 - react-loadable: /@docusaurus/react-loadable@5.5.2(react@17.0.2) - webpack: 5.88.2 + '@babel/runtime': 7.26.10 + react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.3.1)' + webpack: 5.98.0(@swc/core@1.11.9) - /react-router-config@5.1.1(react-router@5.3.4)(react@17.0.2): - resolution: {integrity: sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==} - peerDependencies: - react: '>=15' - react-router: '>=5' + react-router-config@5.1.1(react-router@5.3.4(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.22.11 - react: 17.0.2 - react-router: 5.3.4(react@17.0.2) + '@babel/runtime': 7.26.10 + react: 18.3.1 + react-router: 5.3.4(react@18.3.1) - /react-router-dom@5.3.4(react@17.0.2): - resolution: {integrity: sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==} - peerDependencies: - react: '>=15' + react-router-dom@5.3.4(react@18.3.1): dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.26.10 history: 4.10.1 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 17.0.2 - react-router: 5.3.4(react@17.0.2) - tiny-invariant: 1.3.1 + react: 18.3.1 + react-router: 5.3.4(react@18.3.1) + tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - /react-router@5.3.4(react@17.0.2): - resolution: {integrity: sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==} - peerDependencies: - react: '>=15' + react-router@5.3.4(react@18.3.1): dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.26.10 history: 4.10.1 hoist-non-react-statics: 3.3.2 loose-envify: 1.4.0 - path-to-regexp: 1.8.0 + path-to-regexp: 1.9.0 prop-types: 15.8.1 - react: 17.0.2 + react: 18.3.1 react-is: 16.13.1 - tiny-invariant: 1.3.1 + tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - /react-textarea-autosize@8.5.3(react@17.0.2): - resolution: {integrity: sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==} - engines: {node: '>=10'} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - '@babel/runtime': 7.22.11 - react: 17.0.2 - use-composed-ref: 1.3.0(react@17.0.2) - use-latest: 1.2.1(react@17.0.2) - transitivePeerDependencies: - - '@types/react' - dev: false - - /react-waypoint@10.3.0(react@17.0.2): - resolution: {integrity: sha512-iF1y2c1BsoXuEGz08NoahaLFIGI9gTUAAOKip96HUmylRT6DUtpgoBPjk/Y8dfcFVmfVDvUzWjNXpZyKTOV0SQ==} - peerDependencies: - react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-waypoint@10.3.0(react@18.3.1): dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.26.10 consolidated-events: 2.0.2 prop-types: 15.8.1 - react: 17.0.2 - react-is: 18.2.0 - dev: false + react: 18.3.1 + react-is: 18.3.1 - /react@17.0.2: - resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==} - engines: {node: '>=0.10.0'} + react@18.3.1: dependencies: loose-envify: 1.4.0 - object-assign: 4.1.1 - /read-pkg-up@3.0.0: - resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} - engines: {node: '>=4'} + read-pkg-up@3.0.0: dependencies: find-up: 2.1.0 read-pkg: 3.0.0 - dev: true - /read-pkg-up@7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} + read-pkg-up@7.0.1: dependencies: find-up: 4.1.0 read-pkg: 5.2.0 type-fest: 0.8.1 - dev: true - /read-pkg@3.0.0: - resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} - engines: {node: '>=4'} + read-pkg@3.0.0: dependencies: load-json-file: 4.0.0 normalize-package-data: 2.5.0 path-type: 3.0.0 - dev: true - /read-pkg@5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} + read-pkg@5.2.0: dependencies: - '@types/normalize-package-data': 2.4.0 + '@types/normalize-package-data': 2.4.4 normalize-package-data: 2.5.0 parse-json: 5.2.0 type-fest: 0.6.0 - dev: true - /readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 inherits: 2.0.4 @@ -10343,232 +14272,203 @@ packages: string_decoder: 1.1.1 util-deprecate: 1.0.2 - /readable-stream@3.6.0: - resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} - engines: {node: '>= 6'} - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - /readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} + readable-stream@3.6.2: dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 - /readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + readdirp@3.6.0: dependencies: picomatch: 2.3.1 - /reading-time@1.5.0: - resolution: {integrity: sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==} - dev: false + readdirp@4.1.2: {} - /rechoir@0.6.2: - resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} - engines: {node: '>= 0.10'} - dependencies: - resolve: 1.22.4 + reading-time@1.5.0: {} - /recursive-readdir@2.2.3: - resolution: {integrity: sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==} - engines: {node: '>=6.0.0'} + rechoir@0.6.2: dependencies: - minimatch: 3.1.2 + resolve: 1.22.10 - /redent@3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} + recma-build-jsx@1.0.0: dependencies: - indent-string: 4.0.0 - strip-indent: 3.0.0 - dev: true + '@types/estree': 1.0.6 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 - /regenerate-unicode-properties@10.1.0: - resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==} - engines: {node: '>=4'} + recma-jsx@1.0.0(acorn@7.4.1): dependencies: - regenerate: 1.4.2 + acorn-jsx: 5.3.2(acorn@7.4.1) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - acorn - /regenerate@1.4.2: - resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.6 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 - /regenerator-runtime@0.13.9: - resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.6 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 - /regenerator-runtime@0.14.0: - resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + recursive-readdir@2.2.3: + dependencies: + minimatch: 3.1.2 - /regenerator-transform@0.15.0: - resolution: {integrity: sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==} + reflect.getprototypeof@1.0.10: dependencies: - '@babel/runtime': 7.19.0 - dev: false + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 - /regenerator-transform@0.15.2: - resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} + regenerate-unicode-properties@10.2.0: dependencies: - '@babel/runtime': 7.22.11 + regenerate: 1.4.2 - /regexp-tree@0.1.23: - resolution: {integrity: sha512-+7HWfb4Bvu8Rs2eQTUIpX9I/PlQkYOuTNbRpKLJlQpSgwSkzFYh+pUj0gtvglnOZLKB6YgnIgRuJ2/IlpL48qw==} - hasBin: true - dev: true + regenerate@1.4.2: {} - /regexp.prototype.flags@1.3.1: - resolution: {integrity: sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - dev: true + regenerator-runtime@0.14.1: {} - /regexp.prototype.flags@1.5.0: - resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} - engines: {node: '>= 0.4'} + regenerator-transform@0.15.2: dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - functions-have-names: 1.2.3 - dev: true + '@babel/runtime': 7.26.10 - /regexpp@3.2.0: - resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} - engines: {node: '>=8'} + regexp-tree@0.1.27: {} - /regexpu-core@5.2.1: - resolution: {integrity: sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==} - engines: {node: '>=4'} + regexp.prototype.flags@1.5.4: dependencies: - regenerate: 1.4.2 - regenerate-unicode-properties: 10.1.0 - regjsgen: 0.7.1 - regjsparser: 0.9.1 - unicode-match-property-ecmascript: 2.0.0 - unicode-match-property-value-ecmascript: 2.0.0 - dev: false + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 - /regexpu-core@5.3.2: - resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} - engines: {node: '>=4'} + regexpp@3.2.0: {} + + regexpu-core@6.2.0: dependencies: - '@babel/regjsgen': 0.8.0 regenerate: 1.4.2 - regenerate-unicode-properties: 10.1.0 - regjsparser: 0.9.1 + regenerate-unicode-properties: 10.2.0 + regjsgen: 0.8.0 + regjsparser: 0.12.0 unicode-match-property-ecmascript: 2.0.0 - unicode-match-property-value-ecmascript: 2.1.0 + unicode-match-property-value-ecmascript: 2.2.0 - /registry-auth-token@4.2.2: - resolution: {integrity: sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==} - engines: {node: '>=6.0.0'} + registry-auth-token@5.1.0: dependencies: - rc: 1.2.8 + '@pnpm/npm-conf': 2.3.1 - /registry-url@5.1.0: - resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==} - engines: {node: '>=8'} + registry-url@6.0.1: dependencies: rc: 1.2.8 - /regjsgen@0.7.1: - resolution: {integrity: sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==} - dev: false + regjsgen@0.8.0: {} - /regjsparser@0.9.1: - resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} - hasBin: true + regjsparser@0.12.0: dependencies: - jsesc: 0.5.0 + jsesc: 3.0.2 - /relateurl@0.2.7: - resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} - engines: {node: '>= 0.10'} + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 - /remark-emoji@2.2.0: - resolution: {integrity: sha512-P3cj9s5ggsUvWw5fS2uzCHJMGuXYRb0NnZqYlNecewXt8QBU9n5vW3DUUKOhepS8F9CwdMx9B8a3i7pqFWAI5w==} + rehype-recma@1.0.0: dependencies: - emoticon: 3.2.0 - node-emoji: 1.11.0 - unist-util-visit: 2.0.3 + '@types/estree': 1.0.6 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color - /remark-footnotes@2.0.0: - resolution: {integrity: sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ==} + relateurl@0.2.7: {} - /remark-mdx@1.6.22: - resolution: {integrity: sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ==} + remark-directive@3.0.1: dependencies: - '@babel/core': 7.12.9 - '@babel/helper-plugin-utils': 7.10.4 - '@babel/plugin-proposal-object-rest-spread': 7.12.1(@babel/core@7.12.9) - '@babel/plugin-syntax-jsx': 7.12.1(@babel/core@7.12.9) - '@mdx-js/util': 1.6.22 - is-alphabetical: 1.0.4 - remark-parse: 8.0.3 - unified: 9.2.0 + '@types/mdast': 4.0.4 + mdast-util-directive: 3.1.0 + micromark-extension-directive: 3.0.2 + unified: 11.0.5 transitivePeerDependencies: - supports-color - /remark-parse@8.0.3: - resolution: {integrity: sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==} - dependencies: - ccount: 1.1.0 - collapse-white-space: 1.0.6 - is-alphabetical: 1.0.4 - is-decimal: 1.0.4 - is-whitespace-character: 1.0.4 - is-word-character: 1.0.4 - markdown-escapes: 1.0.4 - parse-entities: 2.0.0 - repeat-string: 1.6.1 - state-toggle: 1.0.3 - trim: 0.0.1 - trim-trailing-lines: 1.1.4 - unherit: 1.1.3 - unist-util-remove-position: 2.0.1 - vfile-location: 3.2.0 - xtend: 4.0.2 + remark-emoji@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + emoticon: 4.1.0 + mdast-util-find-and-replace: 3.0.2 + node-emoji: 2.2.0 + unified: 11.0.5 - /remark-parse@9.0.0: - resolution: {integrity: sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==} + remark-frontmatter@5.0.0: dependencies: - mdast-util-from-markdown: 0.8.5 + '@types/mdast': 4.0.4 + mdast-util-frontmatter: 2.0.1 + micromark-extension-frontmatter: 2.0.0 + unified: 11.0.5 transitivePeerDependencies: - supports-color - dev: true - /remark-squeeze-paragraphs@4.0.0: - resolution: {integrity: sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw==} + remark-gfm@4.0.1: dependencies: - mdast-squeeze-paragraphs: 4.0.0 + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color - /remark-stringify@9.0.1: - resolution: {integrity: sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==} + remark-mdx@3.1.0: dependencies: - mdast-util-to-markdown: 0.6.5 - dev: true + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color - /remark@13.0.0: - resolution: {integrity: sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==} + remark-parse@11.0.0: dependencies: - remark-parse: 9.0.0 - remark-stringify: 9.0.1 - unified: 9.2.2 + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 transitivePeerDependencies: - supports-color - dev: true - /remove-trailing-separator@1.1.0: - resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} - dev: true + remark-rehype@11.1.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.0 + unified: 11.0.5 + vfile: 6.0.3 - /renderkid@3.0.0: - resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==} + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + remove-trailing-separator@1.1.0: {} + + renderkid@3.0.0: dependencies: css-select: 4.3.0 dom-converter: 0.2.0 @@ -10576,307 +14476,168 @@ packages: lodash: 4.17.21 strip-ansi: 6.0.1 - /repeat-string@1.6.1: - resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} - engines: {node: '>=0.10'} + repeat-string@1.6.1: {} - /require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - dev: true + require-directory@2.1.1: {} - /require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} + require-from-string@2.0.2: {} - /require-like@0.1.2: - resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} + require-like@0.1.2: {} - /require-main-filename@2.0.0: - resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - dev: true + require-main-filename@2.0.0: {} - /requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + requires-port@1.0.0: {} - /reserved-words@0.1.2: - resolution: {integrity: sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw==} - dev: true + reserved-words@0.1.2: {} - /resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} + resolve-alpn@1.2.1: {} - /resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - dev: true + resolve-from@4.0.0: {} - /resolve-pathname@3.0.0: - resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==} + resolve-from@5.0.0: {} - /resolve@1.22.1: - resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} - hasBin: true - dependencies: - is-core-module: 2.10.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 + resolve-pathname@3.0.0: {} - /resolve@1.22.4: - resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} - hasBin: true + resolve@1.22.10: dependencies: - is-core-module: 2.13.0 + is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - /resolve@2.0.0-next.3: - resolution: {integrity: sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==} + resolve@2.0.0-next.5: dependencies: - is-core-module: 2.10.0 + is-core-module: 2.16.1 path-parse: 1.0.7 - dev: true + supports-preserve-symlinks-flag: 1.0.0 - /responselike@1.0.2: - resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==} + responselike@3.0.0: dependencies: - lowercase-keys: 1.0.1 + lowercase-keys: 3.0.0 - /restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} + restore-cursor@3.1.0: dependencies: onetime: 5.1.2 signal-exit: 3.0.7 - dev: true - /retry@0.13.1: - resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} - engines: {node: '>= 4'} + retry@0.13.1: {} - /reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + reusify@1.1.0: {} - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true + rimraf@3.0.2: dependencies: glob: 7.2.3 - /rtl-detect@1.0.4: - resolution: {integrity: sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ==} - - /rtlcss@3.5.0: - resolution: {integrity: sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A==} - hasBin: true + rtlcss@4.3.0: dependencies: - find-up: 5.0.0 - picocolors: 1.0.0 - postcss: 8.4.29 + escalade: 3.2.0 + picocolors: 1.1.1 + postcss: 8.5.3 strip-json-comments: 3.1.1 - dev: false - /run-async@2.4.1: - resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} - engines: {node: '>=0.12.0'} - dev: true + run-async@2.4.1: {} - /run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - /rxjs@6.6.7: - resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} - engines: {npm: '>=2.0.0'} + rxjs@6.6.7: dependencies: tslib: 1.14.1 - dev: true - - /rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - dependencies: - tslib: 2.6.2 - /safe-array-concat@1.0.0: - resolution: {integrity: sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==} - engines: {node: '>=0.4'} + safe-array-concat@1.1.3: dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - has-symbols: 1.0.3 + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 isarray: 2.0.5 - dev: true - /safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.1.2: {} - /safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-buffer@5.2.1: {} - /safe-regex-test@1.0.0: - resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} + safe-push-apply@1.0.0: dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - is-regex: 1.1.4 - dev: true + es-errors: 1.3.0 + isarray: 2.0.5 - /safe-regex@2.1.1: - resolution: {integrity: sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==} + safe-regex-test@1.1.0: dependencies: - regexp-tree: 0.1.23 - dev: true + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 - /safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - /sass-loader@10.2.0(sass@1.43.4)(webpack@5.88.2): - resolution: {integrity: sha512-kUceLzC1gIHz0zNJPpqRsJyisWatGYNFRmv2CKZK2/ngMJgLqxTbXwe/hJ85luyvZkgqU3VlJ33UVF2T/0g6mw==} - engines: {node: '>= 10.13.0'} - peerDependencies: - fibers: '>= 3.1.0' - node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 - sass: ^1.3.0 - webpack: ^4.36.0 || ^5.0.0 - peerDependenciesMeta: - fibers: - optional: true - node-sass: - optional: true - sass: - optional: true - dependencies: - klona: 2.0.5 - loader-utils: 2.0.2 - neo-async: 2.6.2 - sass: 1.43.4 - schema-utils: 3.1.1 - semver: 7.3.7 - webpack: 5.88.2 - dev: true - - /sass@1.43.4: - resolution: {integrity: sha512-/ptG7KE9lxpGSYiXn7Ar+lKOv37xfWsZRtFYal2QHNigyVQDx685VFT/h7ejVr+R8w7H4tmUgtulsKl5YpveOg==} - engines: {node: '>=8.9.0'} - hasBin: true + safe-regex@2.1.1: dependencies: - chokidar: 3.5.3 - dev: true + regexp-tree: 0.1.27 - /sax@1.2.4: - resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} - dev: false + safer-buffer@2.1.2: {} - /scheduler@0.20.2: - resolution: {integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==} + sass-loader@16.0.5(sass@1.85.1)(webpack@5.98.0(@swc/core@1.11.9)): dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 + neo-async: 2.6.2 + optionalDependencies: + sass: 1.85.1 + webpack: 5.98.0(@swc/core@1.11.9) - /schema-utils@2.7.0: - resolution: {integrity: sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==} - engines: {node: '>= 8.9.0'} + sass@1.85.1: dependencies: - '@types/json-schema': 7.0.12 - ajv: 6.12.6 - ajv-keywords: 3.5.2(ajv@6.12.6) + chokidar: 4.0.3 + immutable: 5.0.3 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.1 - /schema-utils@2.7.1: - resolution: {integrity: sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==} - engines: {node: '>= 8.9.0'} + sax@1.4.1: {} + + scheduler@0.23.2: dependencies: - '@types/json-schema': 7.0.12 - ajv: 6.12.6 - ajv-keywords: 3.5.2(ajv@6.12.6) + loose-envify: 1.4.0 - /schema-utils@3.1.1: - resolution: {integrity: sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==} - engines: {node: '>= 10.13.0'} + schema-utils@2.7.0: dependencies: - '@types/json-schema': 7.0.11 + '@types/json-schema': 7.0.15 ajv: 6.12.6 ajv-keywords: 3.5.2(ajv@6.12.6) - /schema-utils@3.3.0: - resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} - engines: {node: '>= 10.13.0'} + schema-utils@3.3.0: dependencies: - '@types/json-schema': 7.0.12 + '@types/json-schema': 7.0.15 ajv: 6.12.6 ajv-keywords: 3.5.2(ajv@6.12.6) - /schema-utils@4.2.0: - resolution: {integrity: sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==} - engines: {node: '>= 12.13.0'} + schema-utils@4.3.0: dependencies: - '@types/json-schema': 7.0.12 - ajv: 8.12.0 - ajv-formats: 2.1.1(ajv@8.12.0) - ajv-keywords: 5.1.0(ajv@8.12.0) + '@types/json-schema': 7.0.15 + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) + ajv-keywords: 5.1.0(ajv@8.17.1) - /search-insights@2.8.1: - resolution: {integrity: sha512-gxfqTdzjOxl/i5LtTvSFdolgnm3pUQD5Ae3V8N6tFQ2bsYeo4C3CmrQAsMt212ZV78P22XBUH/nM9IhcAI946Q==} - dev: false + search-insights@2.17.3: {} - /section-matter@1.0.0: - resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} - engines: {node: '>=4'} + section-matter@1.0.0: dependencies: extend-shallow: 2.0.1 kind-of: 6.0.3 - /select-hose@2.0.0: - resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} + select-hose@2.0.0: {} - /selfsigned@2.1.1: - resolution: {integrity: sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==} - engines: {node: '>=10'} + selfsigned@2.4.1: dependencies: + '@types/node-forge': 1.3.11 node-forge: 1.3.1 - /semver-diff@3.1.1: - resolution: {integrity: sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==} - engines: {node: '>=8'} + semver-diff@4.0.0: dependencies: - semver: 6.3.1 - - /semver@5.7.1: - resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} - hasBin: true - dev: true - - /semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true + semver: 7.7.1 - /semver@6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} - hasBin: true - - /semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true + semver@5.7.2: {} - /semver@7.3.7: - resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 + semver@6.3.1: {} - /semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 + semver@7.7.1: {} - /send@0.18.0: - resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} - engines: {node: '>= 0.8.0'} + send@0.19.0: dependencies: debug: 2.6.9 depd: 2.0.0 @@ -10894,26 +14655,21 @@ packages: transitivePeerDependencies: - supports-color - /serialize-javascript@6.0.1: - resolution: {integrity: sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==} + serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 - /serve-handler@6.1.5: - resolution: {integrity: sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==} + serve-handler@6.1.6: dependencies: bytes: 3.0.0 content-disposition: 0.5.2 - fast-url-parser: 1.1.3 mime-types: 2.1.18 minimatch: 3.1.2 path-is-inside: 1.0.2 - path-to-regexp: 2.2.1 + path-to-regexp: 3.3.0 range-parser: 1.2.0 - /serve-index@1.9.1: - resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==} - engines: {node: '>= 0.8.0'} + serve-index@1.9.1: dependencies: accepts: 1.3.8 batch: 0.6.1 @@ -10925,223 +14681,223 @@ packages: transitivePeerDependencies: - supports-color - /serve-static@1.15.0: - resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} - engines: {node: '>= 0.8.0'} + serve-static@1.16.2: dependencies: - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 0.18.0 + send: 0.19.0 transitivePeerDependencies: - supports-color - /set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - dev: true + set-blocking@2.0.0: {} - /setimmediate@1.0.5: - resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - dev: false + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 - /setprototypeof@1.1.0: - resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 - /setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 - /sha1@1.1.1: - resolution: {integrity: sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==} + setprototypeof@1.1.0: {} + + setprototypeof@1.2.0: {} + + sha1@1.1.1: dependencies: charenc: 0.0.2 crypt: 0.0.2 - dev: false - /shallow-clone@3.0.1: - resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} - engines: {node: '>=8'} + shallow-clone@3.0.1: dependencies: kind-of: 6.0.3 - /shallowequal@1.1.0: - resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + shallowequal@1.1.0: {} - /sharp@0.29.3: - resolution: {integrity: sha512-fKWUuOw77E4nhpyzCCJR1ayrttHoFHBT2U/kR/qEMRhvPEcluG4BKj324+SCO1e84+knXHwhJ1HHJGnUt4ElGA==} - engines: {node: '>=12.13.0'} - requiresBuild: true + sharp@0.32.6: dependencies: color: 4.2.3 - detect-libc: 1.0.3 - node-addon-api: 4.2.0 - prebuild-install: 7.1.1 - semver: 7.3.7 + detect-libc: 2.0.3 + node-addon-api: 6.1.0 + prebuild-install: 7.1.3 + semver: 7.7.1 simple-get: 4.0.1 - tar-fs: 2.1.1 + tar-fs: 3.0.8 tunnel-agent: 0.6.0 - dev: false + transitivePeerDependencies: + - bare-buffer - /sharp@0.30.7: - resolution: {integrity: sha512-G+MY2YW33jgflKPTXXptVO28HvNOo9G3j0MybYAHeEmby+QuD2U98dT6ueht9cv/XDqZspSpIhoSW+BAKJ7Hig==} - engines: {node: '>=12.13.0'} - requiresBuild: true + sharp@0.33.5: dependencies: color: 4.2.3 - detect-libc: 2.0.2 - node-addon-api: 5.1.0 - prebuild-install: 7.1.1 - semver: 7.5.4 - simple-get: 4.0.1 - tar-fs: 2.1.1 - tunnel-agent: 0.6.0 - dev: false - - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + detect-libc: 2.0.3 + semver: 7.7.1 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} + shebang-regex@3.0.0: {} - /shell-quote@1.8.1: - resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + shell-quote@1.8.2: {} - /shelljs@0.8.5: - resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} - engines: {node: '>=4'} - hasBin: true + shelljs@0.8.5: dependencies: glob: 7.2.3 interpret: 1.4.0 rechoir: 0.6.2 - /side-channel@1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + side-channel-list@1.0.0: dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.1.3 - object-inspect: 1.12.2 + es-errors: 1.3.0 + object-inspect: 1.13.4 - /signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 - /simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - dev: false + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 - /simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: dependencies: decompress-response: 6.0.0 once: 1.4.0 simple-concat: 1.0.1 - dev: false - /simple-swizzle@0.2.2: - resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 - dev: false - /sirv@2.0.3: - resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==} - engines: {node: '>= 10'} + sirv@2.0.4: dependencies: - '@polka/url': 1.0.0-next.21 - mrmime: 1.0.1 + '@polka/url': 1.0.0-next.28 + mrmime: 2.0.1 totalist: 3.0.1 - /sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + sisteransi@1.0.5: {} - /sitemap@7.1.1: - resolution: {integrity: sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==} - engines: {node: '>=12.0.0', npm: '>=5.6.0'} - hasBin: true + sitemap@7.1.2: dependencies: '@types/node': 17.0.45 - '@types/sax': 1.2.4 + '@types/sax': 1.2.7 arg: 5.0.2 - sax: 1.2.4 - dev: false + sax: 1.4.1 - /slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} + skin-tone@2.0.0: + dependencies: + unicode-emoji-modifier-base: 1.0.0 - /slash@4.0.0: - resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} - engines: {node: '>=12'} + slash@3.0.0: {} - /slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} + slash@4.0.0: {} + + slice-ansi@4.0.0: dependencies: ansi-styles: 4.3.0 astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 - /sockjs@0.3.24: - resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} + snake-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + + sockjs@0.3.24: dependencies: faye-websocket: 0.11.4 uuid: 8.3.2 websocket-driver: 0.7.4 - /sort-css-media-queries@2.1.0: - resolution: {integrity: sha512-IeWvo8NkNiY2vVYdPa27MCQiR0MN0M80johAYFVxWWXQ44KU84WNxjslwBHmc/7ZL2ccwkM7/e6S5aiKZXm7jA==} - engines: {node: '>= 6.3.0'} + sort-css-media-queries@2.2.0: {} - /source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} - engines: {node: '>=0.10.0'} + source-map-js@1.2.1: {} - /source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 - /source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} + source-map@0.6.1: {} - /source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} + source-map@0.7.4: {} - /space-separated-tokens@1.1.5: - resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==} + space-separated-tokens@2.0.2: {} - /spdx-correct@3.1.1: - resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.9 - dev: true + spdx-license-ids: 3.0.21 - /spdx-exceptions@2.3.0: - resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} - dev: true + spdx-exceptions@2.5.0: {} - /spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + spdx-expression-parse@3.0.1: dependencies: - spdx-exceptions: 2.3.0 - spdx-license-ids: 3.0.9 - dev: true + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.21 - /spdx-license-ids@3.0.9: - resolution: {integrity: sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==} - dev: true + spdx-license-ids@3.0.21: {} - /spdy-transport@3.0.0: - resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} + spdy-transport@3.0.0: dependencies: - debug: 4.3.4 + debug: 4.4.0 detect-node: 2.1.0 hpack.js: 2.1.6 obuf: 1.1.2 @@ -11150,11 +14906,9 @@ packages: transitivePeerDependencies: - supports-color - /spdy@4.0.2: - resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} - engines: {node: '>=6.0.0'} + spdy@4.0.2: dependencies: - debug: 4.3.4 + debug: 4.4.0 handle-thing: 2.0.1 http-deceiver: 1.2.7 select-hose: 2.0.0 @@ -11162,1089 +14916,693 @@ packages: transitivePeerDependencies: - supports-color - /specificity@0.4.1: - resolution: {integrity: sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==} - hasBin: true - dev: true - - /sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sprintf-js@1.0.3: {} - /stable@0.1.8: - resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} - deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + srcset@4.0.0: {} - /state-toggle@1.0.3: - resolution: {integrity: sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==} + statuses@1.5.0: {} - /statuses@1.5.0: - resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} - engines: {node: '>= 0.6'} + statuses@2.0.1: {} - /statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} - engines: {node: '>= 0.8'} + std-env@3.8.1: {} - /std-env@3.4.3: - resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==} + streamx@2.22.0: + dependencies: + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + optionalDependencies: + bare-events: 2.5.4 - /string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - /string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} + string-width@5.1.2: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - /string.prototype.codepointat@0.2.1: - resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==} - dev: false - - /string.prototype.matchall@4.0.5: - resolution: {integrity: sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q==} - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.18.3 - get-intrinsic: 1.1.3 - has-symbols: 1.0.3 - internal-slot: 1.0.3 - regexp.prototype.flags: 1.3.1 - side-channel: 1.0.4 - dev: true - - /string.prototype.trim@1.2.7: - resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 - dev: true + string.prototype.codepointat@0.2.1: {} - /string.prototype.trimend@1.0.4: - resolution: {integrity: sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==} + string.prototype.matchall@4.0.12: dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 - /string.prototype.trimend@1.0.6: - resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} + string.prototype.trim@1.2.10: dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 - dev: true + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 - /string.prototype.trimstart@1.0.4: - resolution: {integrity: sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==} + string.prototype.trimend@1.0.9: dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 - /string.prototype.trimstart@1.0.6: - resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} + string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 - dev: true + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 - /string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 - /string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 - /stringify-object@3.3.0: - resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} - engines: {node: '>=4'} + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + stringify-object@3.3.0: dependencies: get-own-enumerable-property-symbols: 3.0.2 is-obj: 1.0.1 is-regexp: 1.0.0 - /strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - /strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} + strip-ansi@7.1.0: dependencies: - ansi-regex: 6.0.1 + ansi-regex: 6.1.0 - /strip-bom-string@1.0.0: - resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} - engines: {node: '>=0.10.0'} + strip-bom-string@1.0.0: {} - /strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - dev: true + strip-bom@3.0.0: {} - /strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} + strip-final-newline@2.0.0: {} - /strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} + strip-json-comments@2.0.1: {} + + strip-json-comments@3.1.1: {} + + style-to-js@1.1.16: dependencies: - min-indent: 1.0.1 - dev: true + style-to-object: 1.0.8 - /strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} + style-to-object@1.0.8: + dependencies: + inline-style-parser: 0.2.4 - /strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} + stylehacks@6.1.1(postcss@8.5.3): + dependencies: + browserslist: 4.24.4 + postcss: 8.5.3 + postcss-selector-parser: 6.1.2 - /style-search@0.1.0: - resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==} - dev: true + stylelint-config-recess-order@5.1.1(stylelint@16.16.0(typescript@5.8.2)): + dependencies: + stylelint: 16.16.0(typescript@5.8.2) + stylelint-order: 6.0.4(stylelint@16.16.0(typescript@5.8.2)) - /style-to-object@0.3.0: - resolution: {integrity: sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==} + stylelint-config-recommended-scss@14.1.0(postcss@8.5.3)(stylelint@16.16.0(typescript@5.8.2)): dependencies: - inline-style-parser: 0.1.1 + postcss-scss: 4.0.9(postcss@8.5.3) + stylelint: 16.16.0(typescript@5.8.2) + stylelint-config-recommended: 14.0.1(stylelint@16.16.0(typescript@5.8.2)) + stylelint-scss: 6.11.1(stylelint@16.16.0(typescript@5.8.2)) + optionalDependencies: + postcss: 8.5.3 - /stylehacks@5.1.1(postcss@8.4.29): - resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + stylelint-config-recommended@14.0.1(stylelint@16.16.0(typescript@5.8.2)): dependencies: - browserslist: 4.21.10 - postcss: 8.4.29 - postcss-selector-parser: 6.0.13 + stylelint: 16.16.0(typescript@5.8.2) - /stylelint-config-recess-order@2.4.0(stylelint@13.13.1): - resolution: {integrity: sha512-+X+cb7WgmfCt5yoG+Ewg+fpn79hosjj7ESQ7ODTtwaZQGzQT+jD6AWLAhULMqDb9v2GVDTO5fu1rY/BR9EaZJA==} - peerDependencies: - stylelint: '>=9' + stylelint-config-standard-scss@13.1.0(postcss@8.5.3)(stylelint@16.16.0(typescript@5.8.2)): dependencies: - stylelint: 13.13.1 - stylelint-order: 4.1.0(stylelint@13.13.1) - dev: true + stylelint: 16.16.0(typescript@5.8.2) + stylelint-config-recommended-scss: 14.1.0(postcss@8.5.3)(stylelint@16.16.0(typescript@5.8.2)) + stylelint-config-standard: 36.0.1(stylelint@16.16.0(typescript@5.8.2)) + optionalDependencies: + postcss: 8.5.3 - /stylelint-config-recommended@5.0.0(stylelint@13.13.1): - resolution: {integrity: sha512-c8aubuARSu5A3vEHLBeOSJt1udOdS+1iue7BmJDTSXoCBmfEQmmWX+59vYIj3NQdJBY6a/QRv1ozVFpaB9jaqA==} - peerDependencies: - stylelint: ^13.13.0 + stylelint-config-standard@36.0.1(stylelint@16.16.0(typescript@5.8.2)): dependencies: - stylelint: 13.13.1 - dev: true + stylelint: 16.16.0(typescript@5.8.2) + stylelint-config-recommended: 14.0.1(stylelint@16.16.0(typescript@5.8.2)) - /stylelint-config-standard@22.0.0(stylelint@13.13.1): - resolution: {integrity: sha512-uQVNi87SHjqTm8+4NIP5NMAyY/arXrBgimaaT7skvRfE9u3JKXRK9KBkbr4pVmeciuCcs64kAdjlxfq6Rur7Hw==} - peerDependencies: - stylelint: ^13.13.0 + stylelint-order@6.0.4(stylelint@16.16.0(typescript@5.8.2)): dependencies: - stylelint: 13.13.1 - stylelint-config-recommended: 5.0.0(stylelint@13.13.1) - dev: true + postcss: 8.5.3 + postcss-sorting: 8.0.2(postcss@8.5.3) + stylelint: 16.16.0(typescript@5.8.2) - /stylelint-order@4.1.0(stylelint@13.13.1): - resolution: {integrity: sha512-sVTikaDvMqg2aJjh4r48jsdfmqLT+nqB1MOsaBnvM3OwLx4S+WXcsxsgk5w18h/OZoxZCxuyXMh61iBHcj9Qiw==} - peerDependencies: - stylelint: ^10.0.1 || ^11.0.0 || ^12.0.0 || ^13.0.0 + stylelint-scss@6.11.1(stylelint@16.16.0(typescript@5.8.2)): dependencies: - lodash: 4.17.21 - postcss: 7.0.36 - postcss-sorting: 5.0.1 - stylelint: 13.13.1 - dev: true + css-tree: 3.1.0 + is-plain-object: 5.0.0 + known-css-properties: 0.35.0 + mdn-data: 2.18.0 + postcss-media-query-parser: 0.2.3 + postcss-resolve-nested-selector: 0.1.6 + postcss-selector-parser: 7.1.0 + postcss-value-parser: 4.2.0 + stylelint: 16.16.0(typescript@5.8.2) - /stylelint@13.13.1: - resolution: {integrity: sha512-Mv+BQr5XTUrKqAXmpqm6Ddli6Ief+AiPZkRsIrAoUKFuq/ElkUh9ZMYxXD0iQNZ5ADghZKLOWz1h7hTClB7zgQ==} - engines: {node: '>=10.13.0'} - hasBin: true + stylelint@16.16.0(typescript@5.8.2): dependencies: - '@stylelint/postcss-css-in-js': 0.37.2(postcss-syntax@0.36.2)(postcss@7.0.36) - '@stylelint/postcss-markdown': 0.36.2(postcss-syntax@0.36.2)(postcss@7.0.36) - autoprefixer: 9.8.6 + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + '@dual-bundle/import-meta-resolve': 4.1.0 balanced-match: 2.0.0 - chalk: 4.1.2 - cosmiconfig: 7.0.1 - debug: 4.3.4 - execall: 2.0.0 - fast-glob: 3.2.12 - fastest-levenshtein: 1.0.12 - file-entry-cache: 6.0.1 - get-stdin: 8.0.0 + colord: 2.9.3 + cosmiconfig: 9.0.0(typescript@5.8.2) + css-functions-list: 3.2.3 + css-tree: 3.1.0 + debug: 4.4.0 + fast-glob: 3.3.3 + fastest-levenshtein: 1.0.16 + file-entry-cache: 10.0.7 global-modules: 2.0.0 globby: 11.1.0 globjoin: 0.1.4 - html-tags: 3.2.0 - ignore: 5.2.0 - import-lazy: 4.0.0 + html-tags: 3.3.1 + ignore: 7.0.3 imurmurhash: 0.1.4 - known-css-properties: 0.21.0 - lodash: 4.17.21 - log-symbols: 4.1.0 + is-plain-object: 5.0.0 + known-css-properties: 0.35.0 mathml-tag-names: 2.1.3 - meow: 9.0.0 - micromatch: 4.0.5 - normalize-selector: 0.2.0 - postcss: 7.0.36 - postcss-html: 0.36.0(postcss-syntax@0.36.2)(postcss@7.0.36) - postcss-less: 3.1.4 - postcss-media-query-parser: 0.2.3 - postcss-resolve-nested-selector: 0.1.1 - postcss-safe-parser: 4.0.2 - postcss-sass: 0.4.4 - postcss-scss: 2.1.1 - postcss-selector-parser: 6.0.10 - postcss-syntax: 0.36.2(postcss@8.4.29) + meow: 13.2.0 + micromatch: 4.0.8 + normalize-path: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.3 + postcss-resolve-nested-selector: 0.1.6 + postcss-safe-parser: 7.0.1(postcss@8.5.3) + postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 resolve-from: 5.0.0 - slash: 3.0.0 - specificity: 0.4.1 string-width: 4.2.3 - strip-ansi: 6.0.1 - style-search: 0.1.0 - sugarss: 2.0.0 + supports-hyperlinks: 3.2.0 svg-tags: 1.0.0 - table: 6.7.1 - v8-compile-cache: 2.3.0 - write-file-atomic: 3.0.3 + table: 6.9.0 + write-file-atomic: 5.0.1 transitivePeerDependencies: - - postcss-jsx - - postcss-markdown - supports-color - dev: true - - /sugarss@2.0.0: - resolution: {integrity: sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==} - dependencies: - postcss: 7.0.36 - dev: true + - typescript - /superstruct@0.15.3: - resolution: {integrity: sha512-wilec1Rg3FtKuRjRyCt70g5W29YUEuaLnybdVQUI+VQ7m0bw8k7TzrRv5iYmo6IpjLVrwxP5t3RgjAVqhYh4Fg==} - dev: false + superstruct@1.0.4: {} - /supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} + supports-color@5.5.0: dependencies: has-flag: 3.0.0 - /supports-color@6.1.0: - resolution: {integrity: sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==} - engines: {node: '>=6'} + supports-color@7.2.0: dependencies: - has-flag: 3.0.0 - dev: true + has-flag: 4.0.0 - /supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} + supports-color@8.1.1: dependencies: has-flag: 4.0.0 - /supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} + supports-hyperlinks@3.2.0: dependencies: has-flag: 4.0.0 + supports-color: 7.2.0 - /supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} + supports-preserve-symlinks-flag@1.0.0: {} - /svg-parser@2.0.4: - resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + svg-parser@2.0.4: {} - /svg-tags@1.0.0: - resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} - dev: true + svg-tags@1.0.0: {} - /svgo@1.3.2: - resolution: {integrity: sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==} - engines: {node: '>=4.0.0'} - deprecated: This SVGO version is no longer supported. Upgrade to v2.x.x. - hasBin: true - dependencies: - chalk: 2.4.2 - coa: 2.0.2 - css-select: 2.1.0 - css-select-base-adapter: 0.1.1 - css-tree: 1.0.0-alpha.37 - csso: 4.2.0 - js-yaml: 3.14.1 - mkdirp: 0.5.5 - object.values: 1.1.4 - sax: 1.2.4 - stable: 0.1.8 - unquote: 1.1.1 - util.promisify: 1.0.1 - dev: false - - /svgo@2.8.0: - resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} - engines: {node: '>=10.13.0'} - hasBin: true + svgo@3.3.2: dependencies: '@trysound/sax': 0.2.0 commander: 7.2.0 - css-select: 4.3.0 - css-tree: 1.1.3 - csso: 4.2.0 - picocolors: 1.0.0 - stable: 0.1.8 + css-select: 5.1.0 + css-tree: 2.3.1 + css-what: 6.1.0 + csso: 5.0.5 + picocolors: 1.1.1 - /table@6.7.1: - resolution: {integrity: sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==} - engines: {node: '>=10.0.0'} + swc-loader@0.2.6(@swc/core@1.11.9)(webpack@5.98.0(@swc/core@1.11.9)): + dependencies: + '@swc/core': 1.11.9 + '@swc/counter': 0.1.3 + webpack: 5.98.0(@swc/core@1.11.9) + + table@6.9.0: dependencies: - ajv: 8.11.0 - lodash.clonedeep: 4.5.0 + ajv: 8.17.1 lodash.truncate: 4.4.2 slice-ansi: 4.0.0 string-width: 4.2.3 strip-ansi: 6.0.1 - /tapable@1.1.3: - resolution: {integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==} - engines: {node: '>=6'} + tapable@1.1.3: {} - /tapable@2.2.1: - resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} - engines: {node: '>=6'} + tapable@2.2.1: {} - /tar-fs@2.1.1: - resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + tar-fs@2.1.2: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 - pump: 3.0.0 + pump: 3.0.2 tar-stream: 2.2.0 - dev: false - /tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} + tar-fs@3.0.8: + dependencies: + pump: 3.0.2 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.0.1 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-buffer + + tar-stream@2.2.0: dependencies: bl: 4.1.0 end-of-stream: 1.4.4 fs-constants: 1.0.0 inherits: 2.0.4 - readable-stream: 3.6.0 - dev: false + readable-stream: 3.6.2 - /terser-webpack-plugin@5.3.9(webpack@5.88.2): - resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} - engines: {node: '>= 10.13.0'} - peerDependencies: - '@swc/core': '*' - esbuild: '*' - uglify-js: '*' - webpack: ^5.1.0 - peerDependenciesMeta: - '@swc/core': - optional: true - esbuild: - optional: true - uglify-js: - optional: true + tar-stream@3.1.7: + dependencies: + b4a: 1.6.7 + fast-fifo: 1.3.2 + streamx: 2.22.0 + + terser-webpack-plugin@5.3.14(@swc/core@1.11.9)(webpack@5.98.0(@swc/core@1.11.9)): dependencies: - '@jridgewell/trace-mapping': 0.3.19 + '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 - schema-utils: 3.3.0 - serialize-javascript: 6.0.1 - terser: 5.19.3 - webpack: 5.88.2 + schema-utils: 4.3.0 + serialize-javascript: 6.0.2 + terser: 5.39.0 + webpack: 5.98.0(@swc/core@1.11.9) + optionalDependencies: + '@swc/core': 1.11.9 - /terser@5.19.3: - resolution: {integrity: sha512-pQzJ9UJzM0IgmT4FAtYI6+VqFf0lj/to58AV0Xfgg0Up37RyPG7Al+1cepC6/BVuAxR9oNb41/DL4DEoHJvTdg==} - engines: {node: '>=10'} - hasBin: true + terser@5.39.0: dependencies: - '@jridgewell/source-map': 0.3.5 - acorn: 8.10.0 + '@jridgewell/source-map': 0.3.6 + acorn: 8.14.1 commander: 2.20.3 source-map-support: 0.5.21 - /text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + text-decoder@1.2.3: + dependencies: + b4a: 1.6.7 + + text-segmentation@1.0.3: + dependencies: + utrie: 1.0.2 + + text-table@0.2.0: {} - /text-to-svg@3.1.5: - resolution: {integrity: sha512-mbeGhMz9MAFaGaZGE8n4Mh/iQV/UkVnYJXhXFrv0eWkcNTgflhpHR2a8nr2ci3NU4FekIHJYKh0N0qdc6yUpfA==} - hasBin: true + text-to-svg@3.1.5: dependencies: commander: 2.20.3 opentype.js: 0.11.0 - dev: false - /through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - dev: true + through@2.3.8: {} - /thunky@1.1.0: - resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} + thunky@1.1.0: {} - /tiny-inflate@1.0.3: - resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} - dev: false + tiny-inflate@1.0.3: {} - /tiny-invariant@1.3.1: - resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} + tiny-invariant@1.3.3: {} - /tiny-warning@1.0.3: - resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + tiny-warning@1.0.3: {} - /tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 - dev: true - - /to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - /to-readable-stream@1.0.0: - resolution: {integrity: sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==} - engines: {node: '>=6'} - - /to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - /toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - /totalist@3.0.1: - resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} - engines: {node: '>=6'} - - /tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + toidentifier@1.0.1: {} - /trim-newlines@3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} - dev: true + totalist@3.0.1: {} - /trim-trailing-lines@1.1.4: - resolution: {integrity: sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==} + tr46@0.0.3: {} - /trim@0.0.1: - resolution: {integrity: sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==} - deprecated: Use String.prototype.trim() instead + trim-lines@3.0.1: {} - /trough@1.0.5: - resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==} + trough@2.2.0: {} - /tsconfig-paths@3.14.2: - resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} + ts-api-utils@1.4.3(typescript@5.8.2): dependencies: - '@types/json5': 0.0.29 - json5: 1.0.2 - minimist: 1.2.8 - strip-bom: 3.0.0 - dev: true + typescript: 5.8.2 - /tsconfig-paths@3.9.0: - resolution: {integrity: sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==} + tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 - json5: 1.0.1 + json5: 1.0.2 minimist: 1.2.8 strip-bom: 3.0.0 - dev: true - - /tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - dev: true - /tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@1.14.1: {} - /tsutils@3.21.0(typescript@4.9.5): - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - dependencies: - tslib: 1.14.1 - typescript: 4.9.5 - dev: true + tslib@2.8.1: {} - /tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 - dev: false - /type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - /type-fest@0.18.1: - resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} - engines: {node: '>=10'} - dev: true + type-fest@0.20.2: {} - /type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} + type-fest@0.21.3: {} - /type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - dev: true + type-fest@0.6.0: {} - /type-fest@0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} - dev: true + type-fest@0.8.1: {} - /type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - dev: true + type-fest@1.4.0: {} - /type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} + type-fest@2.19.0: {} - /type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} + type-is@1.6.18: dependencies: media-typer: 0.3.0 mime-types: 2.1.35 - /typed-array-buffer@1.0.0: - resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} - engines: {node: '>= 0.4'} + typed-array-buffer@1.0.3: dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - is-typed-array: 1.1.12 - dev: true + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 - /typed-array-byte-length@1.0.0: - resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} - engines: {node: '>= 0.4'} + typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.2 - for-each: 0.3.3 - has-proto: 1.0.1 - is-typed-array: 1.1.12 - dev: true + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 - /typed-array-byte-offset@1.0.0: - resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} - engines: {node: '>= 0.4'} + typed-array-byte-offset@1.0.4: dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.2 - for-each: 0.3.3 - has-proto: 1.0.1 - is-typed-array: 1.1.12 - dev: true + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 - /typed-array-length@1.0.4: - resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + typed-array-length@1.0.7: dependencies: - call-bind: 1.0.2 - for-each: 0.3.3 - is-typed-array: 1.1.12 - dev: true + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 - /typedarray-to-buffer@3.1.5: - resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + typedarray-to-buffer@3.1.5: dependencies: is-typedarray: 1.0.0 - /typescript@4.9.5: - resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} - engines: {node: '>=4.2.0'} - hasBin: true - - /ua-parser-js@1.0.35: - resolution: {integrity: sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==} - dev: false + typescript@5.8.2: {} - /unbox-primitive@1.0.1: - resolution: {integrity: sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==} + unbox-primitive@1.1.0: dependencies: - function-bind: 1.1.1 - has-bigints: 1.0.1 - has-symbols: 1.0.3 - which-boxed-primitive: 1.0.2 + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 - /unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - dependencies: - call-bind: 1.0.2 - has-bigints: 1.0.2 - has-symbols: 1.0.3 - which-boxed-primitive: 1.0.2 - dev: true + undici-types@6.20.0: {} - /unherit@1.1.3: - resolution: {integrity: sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==} - dependencies: - inherits: 2.0.4 - xtend: 4.0.2 + unicode-canonical-property-names-ecmascript@2.0.1: {} - /unicode-canonical-property-names-ecmascript@2.0.0: - resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} - engines: {node: '>=4'} + unicode-emoji-modifier-base@1.0.0: {} - /unicode-match-property-ecmascript@2.0.0: - resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} - engines: {node: '>=4'} + unicode-match-property-ecmascript@2.0.0: dependencies: - unicode-canonical-property-names-ecmascript: 2.0.0 + unicode-canonical-property-names-ecmascript: 2.0.1 unicode-property-aliases-ecmascript: 2.1.0 - /unicode-match-property-value-ecmascript@2.0.0: - resolution: {integrity: sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==} - engines: {node: '>=4'} - dev: false - - /unicode-match-property-value-ecmascript@2.1.0: - resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} - engines: {node: '>=4'} - - /unicode-property-aliases-ecmascript@2.1.0: - resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} - engines: {node: '>=4'} + unicode-match-property-value-ecmascript@2.2.0: {} - /unified@9.2.0: - resolution: {integrity: sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==} - dependencies: - '@types/unist': 2.0.8 - bail: 1.0.5 - extend: 3.0.2 - is-buffer: 2.0.5 - is-plain-obj: 2.1.0 - trough: 1.0.5 - vfile: 4.2.1 + unicode-property-aliases-ecmascript@2.1.0: {} - /unified@9.2.2: - resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==} + unified@11.0.5: dependencies: - '@types/unist': 2.0.6 - bail: 1.0.5 + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 extend: 3.0.2 - is-buffer: 2.0.5 - is-plain-obj: 2.1.0 - trough: 1.0.5 - vfile: 4.2.1 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 - /unique-string@2.0.0: - resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} - engines: {node: '>=8'} + unique-string@3.0.0: dependencies: - crypto-random-string: 2.0.0 + crypto-random-string: 4.0.0 - /unist-builder@2.0.3: - resolution: {integrity: sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==} - - /unist-util-find-all-after@3.0.2: - resolution: {integrity: sha512-xaTC/AGZ0rIM2gM28YVRAFPIZpzbpDtU3dRmp7EXlNVA8ziQc4hY3H7BHXM1J49nEmiqc3svnqMReW+PGqbZKQ==} + unist-util-is@6.0.0: dependencies: - unist-util-is: 4.1.0 - dev: true - - /unist-util-generated@1.1.6: - resolution: {integrity: sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==} - - /unist-util-is@4.1.0: - resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} - - /unist-util-position@3.1.0: - resolution: {integrity: sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==} + '@types/unist': 3.0.3 - /unist-util-remove-position@2.0.1: - resolution: {integrity: sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==} + unist-util-position-from-estree@2.0.0: dependencies: - unist-util-visit: 2.0.3 + '@types/unist': 3.0.3 - /unist-util-remove@2.1.0: - resolution: {integrity: sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q==} + unist-util-position@5.0.0: dependencies: - unist-util-is: 4.1.0 + '@types/unist': 3.0.3 - /unist-util-stringify-position@2.0.3: - resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} + unist-util-stringify-position@4.0.0: dependencies: - '@types/unist': 2.0.6 + '@types/unist': 3.0.3 - /unist-util-visit-parents@3.1.1: - resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} + unist-util-visit-parents@6.0.1: dependencies: - '@types/unist': 2.0.8 - unist-util-is: 4.1.0 + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 - /unist-util-visit@2.0.3: - resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==} + unist-util-visit@5.0.0: dependencies: - '@types/unist': 2.0.8 - unist-util-is: 4.1.0 - unist-util-visit-parents: 3.1.1 - - /universalify@2.0.0: - resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} - engines: {node: '>= 10.0.0'} - - /unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 - /unquote@1.1.1: - resolution: {integrity: sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==} - dev: false + universalify@2.0.1: {} - /update-browserslist-db@1.0.11(browserslist@4.21.10): - resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.21.10 - escalade: 3.1.1 - picocolors: 1.0.0 + unpipe@1.0.0: {} - /update-browserslist-db@1.0.9(browserslist@4.21.4): - resolution: {integrity: sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' + update-browserslist-db@1.1.3(browserslist@4.24.4): dependencies: - browserslist: 4.21.4 - escalade: 3.1.1 - picocolors: 1.0.0 + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 - /update-notifier@5.1.0: - resolution: {integrity: sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==} - engines: {node: '>=10'} + update-notifier@6.0.2: dependencies: - boxen: 5.1.2 - chalk: 4.1.2 - configstore: 5.0.1 - has-yarn: 2.1.0 - import-lazy: 2.1.0 - is-ci: 2.0.0 + boxen: 7.1.1 + chalk: 5.4.1 + configstore: 6.0.0 + has-yarn: 3.0.0 + import-lazy: 4.0.0 + is-ci: 3.0.1 is-installed-globally: 0.4.0 - is-npm: 5.0.0 - is-yarn-global: 0.3.0 - latest-version: 5.1.0 - pupa: 2.1.1 - semver: 7.5.4 - semver-diff: 3.1.1 - xdg-basedir: 4.0.0 - - /uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - dependencies: - punycode: 2.1.1 - - /url-loader@4.1.1(file-loader@6.2.0)(webpack@5.88.2): - resolution: {integrity: sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==} - engines: {node: '>= 10.13.0'} - peerDependencies: - file-loader: '*' - webpack: ^4.0.0 || ^5.0.0 - peerDependenciesMeta: - file-loader: - optional: true - dependencies: - file-loader: 6.2.0(webpack@5.88.2) - loader-utils: 2.0.2 - mime-types: 2.1.35 - schema-utils: 3.1.1 - webpack: 5.88.2 + is-npm: 6.0.0 + is-yarn-global: 0.4.1 + latest-version: 7.0.0 + pupa: 3.1.0 + semver: 7.7.1 + semver-diff: 4.0.0 + xdg-basedir: 5.1.0 - /url-parse-lax@3.0.0: - resolution: {integrity: sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==} - engines: {node: '>=4'} + uri-js@4.4.1: dependencies: - prepend-http: 2.0.0 + punycode: 2.3.1 - /use-composed-ref@1.3.0(react@17.0.2): - resolution: {integrity: sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + url-loader@4.1.1(file-loader@6.2.0(webpack@5.98.0(@swc/core@1.11.9)))(webpack@5.98.0(@swc/core@1.11.9)): dependencies: - react: 17.0.2 - dev: false + loader-utils: 2.0.4 + mime-types: 2.1.35 + schema-utils: 3.3.0 + webpack: 5.98.0(@swc/core@1.11.9) + optionalDependencies: + file-loader: 6.2.0(webpack@5.98.0(@swc/core@1.11.9)) - /use-isomorphic-layout-effect@1.1.2(react@17.0.2): - resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - react: 17.0.2 - dev: false + util-deprecate@1.0.2: {} - /use-latest@1.2.1(react@17.0.2): - resolution: {integrity: sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - react: 17.0.2 - use-isomorphic-layout-effect: 1.1.2(react@17.0.2) - dev: false + utila@0.4.0: {} - /use-sync-external-store@1.2.0(react@17.0.2): - resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 17.0.2 - dev: false + utility-types@3.11.0: {} - /util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + utils-merge@1.0.1: {} - /util.promisify@1.0.1: - resolution: {integrity: sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==} + utrie@1.0.2: dependencies: - define-properties: 1.1.4 - es-abstract: 1.18.3 - has-symbols: 1.0.3 - object.getownpropertydescriptors: 2.1.2 - dev: false - - /utila@0.4.0: - resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} - - /utility-types@3.10.0: - resolution: {integrity: sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==} - engines: {node: '>= 4'} - - /utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} + base64-arraybuffer: 1.0.2 - /uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true + uuid@8.3.2: {} - /v8-compile-cache@2.3.0: - resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} + v8-compile-cache@2.4.0: {} - /validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + validate-npm-package-license@3.0.4: dependencies: - spdx-correct: 3.1.1 + spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - dev: true - - /value-equal@1.0.1: - resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==} - /vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} + value-equal@1.0.1: {} - /vfile-location@3.2.0: - resolution: {integrity: sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA==} + vary@1.1.2: {} - /vfile-message@2.0.4: - resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==} + vfile-location@5.0.3: dependencies: - '@types/unist': 2.0.6 - unist-util-stringify-position: 2.0.3 + '@types/unist': 3.0.3 + vfile: 6.0.3 - /vfile@4.2.1: - resolution: {integrity: sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==} + vfile-message@4.0.2: dependencies: - '@types/unist': 2.0.6 - is-buffer: 2.0.5 - unist-util-stringify-position: 2.0.3 - vfile-message: 2.0.4 + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 - /wait-on@6.0.1: - resolution: {integrity: sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==} - engines: {node: '>=10.0.0'} - hasBin: true + vfile@6.0.3: dependencies: - axios: 0.25.0 - joi: 17.10.1 - lodash: 4.17.21 - minimist: 1.2.8 - rxjs: 7.8.1 - transitivePeerDependencies: - - debug + '@types/unist': 3.0.3 + vfile-message: 4.0.2 - /watchpack@2.4.0: - resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} - engines: {node: '>=10.13.0'} + watchpack@2.4.2: dependencies: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - /wbuf@1.7.3: - resolution: {integrity: sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==} + wbuf@1.7.3: dependencies: minimalistic-assert: 1.0.1 - /web-namespaces@1.1.4: - resolution: {integrity: sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==} + web-namespaces@2.0.1: {} - /webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@3.0.1: {} - /webpack-bundle-analyzer@4.9.1: - resolution: {integrity: sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w==} - engines: {node: '>= 10.13.0'} - hasBin: true + webpack-bundle-analyzer@4.10.2: dependencies: '@discoveryjs/json-ext': 0.5.7 - acorn: 8.10.0 - acorn-walk: 8.2.0 + acorn: 8.14.1 + acorn-walk: 8.3.4 commander: 7.2.0 + debounce: 1.2.1 escape-string-regexp: 4.0.0 gzip-size: 6.0.0 - is-plain-object: 5.0.0 - lodash.debounce: 4.0.8 - lodash.escape: 4.0.1 - lodash.flatten: 4.4.0 - lodash.invokemap: 4.6.0 - lodash.pullall: 4.2.0 - lodash.uniqby: 4.7.0 + html-escaper: 2.0.2 opener: 1.5.2 - picocolors: 1.0.0 - sirv: 2.0.3 - ws: 7.5.9 + picocolors: 1.1.1 + sirv: 2.0.4 + ws: 7.5.10 transitivePeerDependencies: - bufferutil - utf-8-validate - /webpack-dev-middleware@5.3.3(webpack@5.88.2): - resolution: {integrity: sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==} - engines: {node: '>= 12.13.0'} - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 + webpack-dev-middleware@5.3.4(webpack@5.98.0(@swc/core@1.11.9)): dependencies: colorette: 2.0.20 memfs: 3.5.3 mime-types: 2.1.35 range-parser: 1.2.1 - schema-utils: 4.2.0 - webpack: 5.88.2 - - /webpack-dev-server@4.15.1(webpack@5.88.2): - resolution: {integrity: sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==} - engines: {node: '>= 12.13.0'} - hasBin: true - peerDependencies: - webpack: ^4.37.0 || ^5.0.0 - webpack-cli: '*' - peerDependenciesMeta: - webpack: - optional: true - webpack-cli: - optional: true - dependencies: - '@types/bonjour': 3.5.10 - '@types/connect-history-api-fallback': 1.5.0 - '@types/express': 4.17.17 - '@types/serve-index': 1.9.1 - '@types/serve-static': 1.15.2 - '@types/sockjs': 0.3.33 - '@types/ws': 8.5.5 + schema-utils: 4.3.0 + webpack: 5.98.0(@swc/core@1.11.9) + + webpack-dev-server@4.15.2(webpack@5.98.0(@swc/core@1.11.9)): + dependencies: + '@types/bonjour': 3.5.13 + '@types/connect-history-api-fallback': 1.5.4 + '@types/express': 4.17.21 + '@types/serve-index': 1.9.4 + '@types/serve-static': 1.15.7 + '@types/sockjs': 0.3.36 + '@types/ws': 8.18.0 ansi-html-community: 0.0.8 - bonjour-service: 1.1.1 - chokidar: 3.5.3 + bonjour-service: 1.3.0 + chokidar: 3.6.0 colorette: 2.0.20 - compression: 1.7.4 + compression: 1.8.0 connect-history-api-fallback: 2.0.0 default-gateway: 6.0.3 - express: 4.18.2 + express: 4.21.2 graceful-fs: 4.2.11 - html-entities: 2.4.0 - http-proxy-middleware: 2.0.6(@types/express@4.17.17) - ipaddr.js: 2.1.0 - launch-editor: 2.6.0 + html-entities: 2.5.2 + http-proxy-middleware: 2.0.7(@types/express@4.17.21) + ipaddr.js: 2.2.0 + launch-editor: 2.10.0 open: 8.4.2 p-retry: 4.6.2 rimraf: 3.0.2 - schema-utils: 4.2.0 - selfsigned: 2.1.1 + schema-utils: 4.3.0 + selfsigned: 2.4.1 serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack: 5.88.2 - webpack-dev-middleware: 5.3.3(webpack@5.88.2) - ws: 8.13.0 + webpack-dev-middleware: 5.3.4(webpack@5.98.0(@swc/core@1.11.9)) + ws: 8.18.1 + optionalDependencies: + webpack: 5.98.0(@swc/core@1.11.9) transitivePeerDependencies: - bufferutil - debug - supports-color - utf-8-validate - /webpack-merge@5.9.0: - resolution: {integrity: sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==} - engines: {node: '>=10.0.0'} + webpack-merge@5.10.0: dependencies: clone-deep: 4.0.1 + flat: 5.0.2 wildcard: 2.0.1 - /webpack-sources@3.2.3: - resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} - engines: {node: '>=10.13.0'} + webpack-merge@6.0.1: + dependencies: + clone-deep: 4.0.1 + flat: 5.0.2 + wildcard: 2.0.1 - /webpack@5.88.2: - resolution: {integrity: sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true + webpack-sources@3.2.3: {} + + webpack@5.98.0(@swc/core@1.11.9): dependencies: - '@types/eslint-scope': 3.7.4 - '@types/estree': 1.0.1 - '@webassemblyjs/ast': 1.11.6 - '@webassemblyjs/wasm-edit': 1.11.6 - '@webassemblyjs/wasm-parser': 1.11.6 - acorn: 8.10.0 - acorn-import-assertions: 1.9.0(acorn@8.10.0) - browserslist: 4.21.10 - chrome-trace-event: 1.0.3 - enhanced-resolve: 5.15.0 - es-module-lexer: 1.3.0 + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.6 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.14.1 + browserslist: 4.24.4 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.18.1 + es-module-lexer: 1.6.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -12253,207 +15611,154 @@ packages: loader-runner: 4.3.0 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 3.3.0 + schema-utils: 4.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(webpack@5.88.2) - watchpack: 2.4.0 + terser-webpack-plugin: 5.3.14(@swc/core@1.11.9)(webpack@5.98.0(@swc/core@1.11.9)) + watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' - esbuild - uglify-js - /webpackbar@5.0.2(webpack@5.88.2): - resolution: {integrity: sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==} - engines: {node: '>=12'} - peerDependencies: - webpack: 3 || 4 || 5 + webpackbar@6.0.1(webpack@5.98.0(@swc/core@1.11.9)): dependencies: + ansi-escapes: 4.3.2 chalk: 4.1.2 - consola: 2.15.3 + consola: 3.4.0 + figures: 3.2.0 + markdown-table: 2.0.0 pretty-time: 1.1.0 - std-env: 3.4.3 - webpack: 5.88.2 + std-env: 3.8.1 + webpack: 5.98.0(@swc/core@1.11.9) + wrap-ansi: 7.0.0 - /websocket-driver@0.7.4: - resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} - engines: {node: '>=0.8.0'} + websocket-driver@0.7.4: dependencies: - http-parser-js: 0.5.8 + http-parser-js: 0.5.9 safe-buffer: 5.2.1 websocket-extensions: 0.1.4 - /websocket-extensions@0.1.4: - resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} - engines: {node: '>=0.8.0'} + websocket-extensions@0.1.4: {} - /whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 - /which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: dependencies: - is-bigint: 1.0.2 - is-boolean-object: 1.1.1 - is-number-object: 1.0.5 - is-string: 1.0.6 - is-symbol: 1.0.4 + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 - /which-module@2.0.0: - resolution: {integrity: sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==} - dev: true + which-module@2.0.1: {} - /which-typed-array@1.1.11: - resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==} - engines: {node: '>= 0.4'} + which-typed-array@1.1.19: dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.2 - for-each: 0.3.3 - gopd: 1.0.1 - has-tostringtag: 1.0.0 - dev: true + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 - /which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true + which@1.3.1: dependencies: isexe: 2.0.0 - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + which@2.0.2: dependencies: isexe: 2.0.0 - /widest-line@3.1.0: - resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} - engines: {node: '>=8'} - dependencies: - string-width: 4.2.3 - - /widest-line@4.0.1: - resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} - engines: {node: '>=12'} + widest-line@4.0.1: dependencies: string-width: 5.1.2 - /wildcard@2.0.1: - resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} + wildcard@2.0.1: {} - /word-wrap@1.2.3: - resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} - engines: {node: '>=0.10.0'} + word-wrap@1.2.5: {} - /wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true - /wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - /wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} + wrap-ansi@8.1.0: dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + wrappy@1.0.2: {} - /write-file-atomic@3.0.3: - resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} + write-file-atomic@3.0.3: dependencies: imurmurhash: 0.1.4 is-typedarray: 1.0.0 signal-exit: 3.0.7 typedarray-to-buffer: 3.1.5 - /ws@7.5.9: - resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - /ws@8.13.0: - resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 - /xdg-basedir@4.0.0: - resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} - engines: {node: '>=8'} + ws@7.5.10: {} - /xml-js@1.6.11: - resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} - hasBin: true - dependencies: - sax: 1.2.4 - dev: false + ws@8.18.1: {} - /xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} + xdg-basedir@5.1.0: {} - /y18n@4.0.3: - resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - dev: true + xml-js@1.6.11: + dependencies: + sax: 1.4.1 - /yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + y18n@4.0.3: {} - /yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@3.1.1: {} - /yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} + yaml@1.10.2: {} - /yargs-parser@18.1.3: - resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} - engines: {node: '>=6'} + yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 decamelize: 1.2.0 - dev: true - - /yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - dev: true - /yargs@15.4.1: - resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} - engines: {node: '>=8'} + yargs@15.4.1: dependencies: cliui: 6.0.0 decamelize: 1.2.0 @@ -12463,14 +15768,12 @@ packages: require-main-filename: 2.0.0 set-blocking: 2.0.0 string-width: 4.2.3 - which-module: 2.0.0 + which-module: 2.0.1 y18n: 4.0.3 yargs-parser: 18.1.3 - dev: true - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} + yocto-queue@0.1.0: {} + + yocto-queue@1.2.0: {} - /zwitch@1.0.5: - resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} + zwitch@2.0.4: {} diff --git a/sandbox.config.json b/sandbox.config.json index 95df40889a..b98a7c87c5 100644 --- a/sandbox.config.json +++ b/sandbox.config.json @@ -1,10 +1,10 @@ { - "infiniteLoopProtection": true, - "hardReloadOnChange": true, - "view": "browser", - "template": "docusaurus", - "node": "14", - "container": { - "node": "14" - } + "infiniteLoopProtection": true, + "hardReloadOnChange": true, + "view": "browser", + "template": "docusaurus", + "node": "14", + "container": { + "node": "14" + } } diff --git a/src/app/doc-item.scss b/src/app/doc-item.scss index b7e16da62f..7624878fc8 100644 --- a/src/app/doc-item.scss +++ b/src/app/doc-item.scss @@ -1,4 +1,4 @@ -@import "./theme.scss"; +@use "./theme"; html { /** @@ -15,7 +15,7 @@ html { } & img[alt*="bordered"] { - border: 1px solid rgb(211, 213, 216); + border: 1px solid rgb(211 213 216); border-radius: 16px; } @@ -28,22 +28,22 @@ html { } } -:root[data-theme=dark] [src$="#light-mode-only"] { +:root[data-theme="dark"] [src$="#light-mode-only"] { display: none; } -:root[data-theme=light] [src$="#dark-mode-only"] { +:root[data-theme="light"] [src$="#dark-mode-only"] { display: none; } @media (prefers-color-scheme: dark) { - :root[data-theme=auto] [src$="#light-mode-only"] { + :root[data-theme="auto"] [src$="#light-mode-only"] { display: none; } } @media (prefers-color-scheme: light) { - :root[data-theme=auto] [src$="#dark-mode-only"] { + :root[data-theme="auto"] [src$="#dark-mode-only"] { display: none; } } @@ -68,7 +68,7 @@ mark { display: block; padding: 0 var(--ifm-pre-padding); margin: 0 calc(-1 * var(--ifm-pre-padding)); - background-color: rgb(72, 77, 91); + background-color: rgb(72 77 91); } .sidebar-item--wip { diff --git a/src/app/index.scss b/src/app/index.scss index 4312c42766..4b20256db6 100644 --- a/src/app/index.scss +++ b/src/app/index.scss @@ -6,12 +6,22 @@ * work well for content-centric websites. */ -@import "./theme.scss"; -@import "./scroll.scss"; -@import "./navbar.scss"; -@import "./doc-item.scss"; +@use "./theme"; +@use "./scroll"; +@use "./navbar"; +@use "./doc-item"; +@use "../features/feedback/doc/pushfeedback"; -@media screen and (max-width: 450px) { +h1, +h2, +h3, +h4, +h5, +h6 { + letter-spacing: -0.5px; +} + +@media screen and (width <= 450px) { .button-group { display: block; } @@ -21,3 +31,8 @@ border-radius: var(--ifm-button-border-radius) !important; } } + +.theme-back-to-top-button { + // The PushFeedback button is positioned in the same place, so we need to move the "back to top" button higher + bottom: 3.5rem !important; +} diff --git a/src/app/navbar.scss b/src/app/navbar.scss index c2c95587ab..86d882aad6 100644 --- a/src/app/navbar.scss +++ b/src/app/navbar.scss @@ -1,4 +1,4 @@ -@import "./theme.scss"; +@use "./theme"; .navbar { &__brand { @@ -36,11 +36,11 @@ } &.github::before { - background: url(assets/github.svg) no-repeat; + background: url("assets/github.svg") no-repeat; } &.discord::before { - background: url(assets/discord.svg) no-repeat; + background: url("assets/discord.svg") no-repeat; } } @@ -49,12 +49,6 @@ html[data-theme="dark"] .ext-link::before { filter: invert(1); } -[role="banner"] { - --docusaurus-announcement-bar-height: 42px; // #nowar - - font-size: 1.2rem; // #nowar -} - /* Debug only, for simulating searchBox on devMode .navbar__items:last-child { border-right: 161px solid black; @@ -62,7 +56,7 @@ html[data-theme="dark"] .ext-link::before { /* FIXME: temp hack for adaptive-layout! */ -@media (max-width: 1180px) { +@media (width <= 1180px) { .navbar__brand { margin-right: 0.5rem; } @@ -79,24 +73,18 @@ html[data-theme="dark"] .ext-link::before { /* I love adaptive styles... */ /* FIXME: add unified adaptive breakpoints */ -@media (max-width: 1080px) { +@media (width <= 1080px) { .navbar__item { padding: var(--ifm-navbar-item-padding-vertical) 0.25rem; } } -@media (max-width: 996px) { +@media (width <= 996px) { .navbar__brand .navbar__title { display: block; } } -@media (max-width: 600px) { - [role="banner"] { - font-size: 1rem; // #nowar - } -} - .medium-zoom-overlay, .medium-zoom-image--opened { z-index: 999; diff --git a/src/app/scroll.scss b/src/app/scroll.scss index a71dfa4c84..595a17ec4e 100644 --- a/src/app/scroll.scss +++ b/src/app/scroll.scss @@ -1,4 +1,4 @@ -@import "./theme.scss"; +@use "./theme"; /* Inherited from docusaurus scrollbar stylesheets for inner containers */ html { diff --git a/src/app/theme.scss b/src/app/theme.scss index 6bc51f5f29..6941179d77 100644 --- a/src/app/theme.scss +++ b/src/app/theme.scss @@ -1,36 +1,9 @@ -@import "@fontsource/overpass/400.css"; -@import "@fontsource/ubuntu/400.css"; -@import "@fontsource/ubuntu/700.css"; - /* https://docusaurus.io/docs/styling-layout */ :root { // Base styles --ifm-code-font-size: 95%; - --ifm-h2-font-size: 1.75rem; --ifm-button-color: #ffffff; - - // Default colors - - /* --ifm-color-primary: #25c2a0; - --ifm-color-primary-dark: rgb(33, 175, 144); - --ifm-color-primary-darker: rgb(31, 165, 136); - --ifm-color-primary-darkest: rgb(26, 136, 112); - --ifm-color-primary-light: rgb(70, 203, 174); - --ifm-color-primary-lighter: rgb(102, 212, 189); - --ifm-color-primary-lightest: rgb(146, 224, 208); */ - - // 2.0.0-beta theme (legacy) - - /* --ifm-color-primary: #5c9cb5; - --ifm-color-primary-dark: #4d8fa9; - --ifm-color-primary-darker: #4887a0; - --ifm-color-primary-darkest: #3c6f83; - --ifm-color-primary-light: #6fa7be; - --ifm-color-primary-lighter: #78adc2; - --ifm-color-primary-lightest: #94becf; */ - - // 2.0.0-stable theme (current) --ifm-color-primary: #2b74d4; --ifm-color-primary-dark: #2768bf; --ifm-color-primary-darker: #2563b4; @@ -41,13 +14,13 @@ --ifm-color-primary-grad1: #29bedc; --ifm-color-primary-grad2: #517aed; --ifm-color-primary-neutral: #4a7ec2c6; - --ifm-font-family-base: 'Ubuntu', system-ui, -apple-system, segoe ui, roboto, ubuntu, cantarell, noto sans, sans-serif, blinkmacsystemfont, 'Segoe UI', helvetica, arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; - --ifm-heading-font-family: 'Overpass', var(--ifm-font-family-base); + --ifm-heading-font-family: + "Overpass Variable", var(--ifm-font-family-base, sans-serif); --ifm-color-violet: #5c21dd; } // Restored values from old dark theme -html[data-theme=dark] { +html[data-theme="dark"] { --ifm-background-color: #18191a; --ifm-background-surface-color: #242526; @@ -61,3 +34,26 @@ html[data-theme=dark] { --ifm-badge-background-color: var(--ifm-color-violet); --ifm-badge-border-color: var(--ifm-badge-background-color); } + +img { + height: unset; +} + +.alert.file-tree { + padding-top: 0; + padding-bottom: 0; + margin-bottom: 0; + background: none; + border: 0; + box-shadow: none; + + .file-tree & { + padding: 0; + } + + & summary + div > div { + padding-top: 0; + margin-top: 0; + border: 0; + } +} diff --git a/src/entities/example/index.tsx b/src/entities/example/index.tsx index 789ccb8c63..d42b9cf7f8 100644 --- a/src/entities/example/index.tsx +++ b/src/entities/example/index.tsx @@ -1,7 +1,6 @@ -import React, { useCallback } from "react"; +import React from "react"; import clsx from "clsx"; import Image from "@theme/IdealImage"; -import { ga } from "@site/src/shared/lib/ga"; import { date } from "@site/src/shared/lib/date"; import styles from "./styles.module.scss"; @@ -22,20 +21,6 @@ type Props = { export const ExampleCard: React.FC = ({ className, data }) => { const isNew = date.getDiffDays(new Date(data.updatedAt), new Date()) <= 14; - const handleWebsiteClick = useCallback(() => { - ga.sendEvent({ - category: ga.CATEGORIES.full, - action: "Example:Click", - label: `${data.title} [website]`, - }); - }, []); - const handleSourceClick = useCallback(() => { - ga.sendEvent({ - category: ga.CATEGORIES.full, - action: "Example:Click", - label: `${data.title} [source]`, - }); - }, []); return (
@@ -45,7 +30,10 @@ export const ExampleCard: React.FC = ({ className, data }) => {
{isNew && ( - + NEW:{" "} )} @@ -55,7 +43,10 @@ export const ExampleCard: React.FC = ({ className, data }) => {
{data.tech.map((techItem) => ( {techItem} @@ -72,7 +63,6 @@ export const ExampleCard: React.FC = ({ className, data }) => { href={data.website} target="_blank" rel="noreferrer noopener" - onClick={handleWebsiteClick} > Website @@ -83,7 +73,6 @@ export const ExampleCard: React.FC = ({ className, data }) => { href={data.source} target="_blank" rel="noreferrer noopener" - onClick={handleSourceClick} > Source diff --git a/src/entities/exp/index.ts b/src/entities/exp/index.ts deleted file mode 100644 index ab5e11a1d7..0000000000 --- a/src/entities/exp/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import cookies from "js-cookie"; - -/** - * Inner lib for silent deployment of new features - * @remark For example - for "checking on production", before global deployment - * @module - */ - -const EXP_COOKIE_KEY = "exp_flag"; - -/** - * @ticket FEEDBACK-309 - */ -export const DOC_FEEDBACK_WIDGET = "doc_feedback_widget"; - -export const hasExp = (expFlag: string) => { - return cookies.get(EXP_COOKIE_KEY, expFlag); -}; diff --git a/src/features/cookie-consent/index.tsx b/src/features/cookie-consent/index.tsx deleted file mode 100644 index f6709ea725..0000000000 --- a/src/features/cookie-consent/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from "react"; -import ReactCookieConsent from "react-cookie-consent"; -import Link from "@docusaurus/Link"; -import { translate } from "@docusaurus/Translate"; - -import styles from "./styles.module.scss"; - -export const CookieConsent = () => { - return ( - - {translate({ id: "features.cookie-consent.alert" })} - ({translate({ id: "features.cookie-consent.reason" })}) - - ); -}; diff --git a/src/features/cookie-consent/styles.module.scss b/src/features/cookie-consent/styles.module.scss deleted file mode 100644 index 61e2fc74e0..0000000000 --- a/src/features/cookie-consent/styles.module.scss +++ /dev/null @@ -1,32 +0,0 @@ -/* !important overridings because of react-cookie-consent selectors-specifity */ - -.root { - color: var(--ifm-font-color-base) !important; - background-color: var(--ifm-background-surface-color) !important; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.3); -} - -.button { - padding: var(--ifm-button-padding-vertical) var(--ifm-button-padding-horizontal) !important; - margin: 8px !important; - color: var(--ifm-button-color) !important; - background: var(--ifm-color-primary-dark) !important; - border-radius: var(--ifm-button-border-radius) !important; - transition: 0.25s; - - &:hover { - opacity: 0.7; - } -} - -@media (max-width: 460px) { - .buttonWrapper { - flex-grow: 1; - text-align: center; - } - - .content { - margin-bottom: 4px !important; - text-align: center; - } -} diff --git a/src/features/feedback/doc/index.tsx b/src/features/feedback/doc/index.tsx index 53e4adcbfc..b6b0a9b200 100644 --- a/src/features/feedback/doc/index.tsx +++ b/src/features/feedback/doc/index.tsx @@ -1,85 +1,67 @@ -import React, { useState, useCallback } from "react"; -import clsx from "clsx"; -import { LikeFilled, DislikeFilled } from "@ant-design/icons"; +import React, { useEffect } from "react"; import { translate } from "@docusaurus/Translate"; +import { FeedbackButton } from "pushfeedback-react"; +import { defineCustomElements } from "pushfeedback/loader"; -import { ga } from "@site/src/shared/lib/ga"; -import styles from "./styles.module.scss"; +import "pushfeedback/dist/pushfeedback/pushfeedback.css"; -/** - * Send feedback to Google Analytics - * @see https://developers.google.com/analytics/devguides/collection/analyticsjs/events - */ -const sendFeedback = (value: number) => { - // For a while - send feedback in both format - // Later will keep and maintain only one - ga.sendEvent({ - category: ga.CATEGORIES.full, - action: "Docs:Helpful", - label: window.location.href, - value, - }); - ga.sendEvent({ - category: ga.CATEGORIES.mixed, - action: "Docs:Helpful", - label: String(value), - value, - }); - ga.sendEvent({ - category: ga.CATEGORIES.short, - label: window.location.href, - value, - }); -}; +export function FeedbackWidget({ projectId }: { projectId: string }) { + useEffect(() => { + if (typeof window !== "undefined") { + defineCustomElements(window); + } + }, []); -type Props = { - className?: string; -}; - -/** - * DocItem feedback widget - * @see https://docusaurus.io/feature-requests/p/feedback-widget - * @ticket FEEDBACK-309 - * @terminateTicket FEEDBACK-325 - * TODO: Add emojiis (Bad, OK, Good) - * TODO: Add comment input - */ -export const DocFeedback: React.FC = ({ className }) => { - const [feedbackSent, setFeedbackSent] = useState(false); - - const handleFeedback = useCallback( - (value: number) => () => { - setFeedbackSent(true); - sendFeedback(value); - }, - [], - ); - - if (feedbackSent) { - return ( -
- {translate({ id: "features.feedback-doc.thanks" })} -
- ); - } return ( -
-
- - {translate({ id: "features.feedback-doc.title" })} - - - -
-
- {translate({ id: "features.feedback-doc.subtitle" })} -
+
+ + {translate({ id: "features.feedback-doc.button-text" })} +
); -}; +} diff --git a/src/features/feedback/doc/pushfeedback.css b/src/features/feedback/doc/pushfeedback.css new file mode 100644 index 0000000000..1321a29fd5 --- /dev/null +++ b/src/features/feedback/doc/pushfeedback.css @@ -0,0 +1,4 @@ +:root { + --feedback-font-family: var(--ifm-font-family-base, sans-serif) !important; + --feedback-modal-screnshot-z-index: 201 !important; +} diff --git a/src/features/feedback/doc/styles.module.scss b/src/features/feedback/doc/styles.module.scss deleted file mode 100644 index be3966529e..0000000000 --- a/src/features/feedback/doc/styles.module.scss +++ /dev/null @@ -1,73 +0,0 @@ -.root { - --height: 4rem; - - height: var(--height); - text-align: center; - opacity: 0.5; - - &Thanks { - /* - Hack for easy impl "friendly non-shaking UX" - https://github.com/feature-sliced/documentation/pull/339#discussion_r742779255 - */ - line-height: var(--height); - } -} - -/* title */ - -.title { - font-size: 1.4rem; - - &Label { - margin-right: 0.5rem; - } -} - -.subtitle { - font-size: 0.9rem; -} - -/* actions */ - -.action { - padding: 0.5rem; - margin: 0 0.2rem; - background-color: var(--background-color-action); - border-radius: 50%; - transition: 0.25s; - - &Like:hover { - color: var(--color-like); - } - - &Dislike:hover { - color: var(--color-dislike); - } -} - -/* theme */ - -html { - &[data-theme="light"] .root { - --color-like: var(--ifm-color-success); - --color-dislike: var(--ifm-color-danger); - --background-color-action: var(--ifm-color-gray-300); - } - - &[data-theme="dark"] .action { - --color-like: var(--ifm-color-success-lighter); - --color-dislike: var(--ifm-color-danger-lighter); - --background-color-action: var(--ifm-color-gray-800); - } -} - -@media (max-width: 420px) { - .root { - --height: 7rem; - } - - .titleLabel { - display: block; - } -} diff --git a/src/features/hero/index.tsx b/src/features/hero/index.tsx index 4934acea78..690fa3f993 100644 --- a/src/features/hero/index.tsx +++ b/src/features/hero/index.tsx @@ -11,18 +11,30 @@ export function Hero() {

{siteConfig.title}

-

{translate({ id: "features.hero.tagline" })}

+

+ {translate({ id: "features.hero.tagline" })} +

- + {translate({ id: "features.hero.get_started" })} - + {translate({ id: "features.hero.examples" })}
- - {translate({ id: "features.hero.previous" })} (feature-slices@v1) + + {translate({ id: "features.hero.previous" })}{" "} + (feature-slices@v1)
diff --git a/src/features/hero/styles.module.scss b/src/features/hero/styles.module.scss index 780e0a55d5..4afd06fc6f 100644 --- a/src/features/hero/styles.module.scss +++ b/src/features/hero/styles.module.scss @@ -15,10 +15,14 @@ /* height: 500px; */ text-align: center; - background: linear-gradient(45deg, var(--ifm-color-primary-grad1), var(--ifm-color-primary-grad2)); + background: linear-gradient( + 45deg, + var(--ifm-color-primary-grad1), + var(--ifm-color-primary-grad2) + ); } -@media screen and (max-width: 966px) { +@media screen and (width <= 966px) { .heroBanner { padding: 2rem; } diff --git a/src/pages/_home/_config.tsx b/src/pages/_home/_config.tsx index c5b728616c..aed374670e 100644 --- a/src/pages/_home/_config.tsx +++ b/src/pages/_home/_config.tsx @@ -18,7 +18,9 @@ export const features = [ { title: translate({ id: "pages.home.features.adaptability.title" }), Icon: LoginOutlined, - description: translate({ id: "pages.home.features.adaptability.description" }), + description: translate({ + id: "pages.home.features.adaptability.description", + }), }, { title: translate({ id: "pages.home.features.debt.title" }), @@ -28,7 +30,9 @@ export const features = [ { title: translate({ id: "pages.home.features.shared.title" }), Icon: BuildOutlined, - description: translate({ id: "pages.home.features.shared.description" }), + description: translate({ + id: "pages.home.features.shared.description", + }), }, ]; @@ -36,12 +40,16 @@ export const concepts = [ { title: translate({ id: "pages.home.concepts.public.title" }), Icon: ApiOutlined, - description: translate({ id: "pages.home.concepts.public.description" }), + description: translate({ + id: "pages.home.concepts.public.description", + }), }, { title: translate({ id: "pages.home.concepts.isolation.title" }), Icon: BlockOutlined, - description: translate({ id: "pages.home.concepts.isolation.description" }), + description: translate({ + id: "pages.home.concepts.isolation.description", + }), }, { title: translate({ id: "pages.home.concepts.needs.title" }), diff --git a/src/pages/_home/companies/_config.tsx b/src/pages/_home/companies/_config.tsx index 931ab70098..4a7f25ce4e 100644 --- a/src/pages/_home/companies/_config.tsx +++ b/src/pages/_home/companies/_config.tsx @@ -1,4 +1,4 @@ -type Company = { +export type Company = { /* URL to your company website */ url: string; /* Image path to your company logo */ @@ -107,4 +107,26 @@ export const companies: Company[] = [ src: "express24.svg", alt: "Express24.uz", }, + { + url: "https://blindtyping.com/", + src: "blindtyping.svg", + alt: "Blindtyping", + }, +]; + +const japaneseCompanies: Company[] = [ + { + url: "https://hapins.net/", + src: "ja/hapins.png", + alt: "HapInS", + }, ]; + +const koreanCompanies: Company[] = []; + +export const localeToCompaniesMap: Record = { + ru: companies, + en: companies, + ja: japaneseCompanies, + ko: koreanCompanies, +}; diff --git a/src/pages/_home/companies/index.tsx b/src/pages/_home/companies/index.tsx index 2eb9134863..9678048f80 100644 --- a/src/pages/_home/companies/index.tsx +++ b/src/pages/_home/companies/index.tsx @@ -1,16 +1,15 @@ -import React, { useMemo } from "react"; +import React from "react"; // It's utility, not hook =) import getBaseUrl from "@docusaurus/useBaseUrl"; import { translate } from "@docusaurus/Translate"; import Marquee from "react-fast-marquee"; -import { shuffle } from "lodash-es"; import { Section } from "@site/src/shared/ui"; -import { companies } from "./_config"; import styles from "./styles.module.scss"; +import { useCompanies } from "./use-companies"; export const Companies = () => { - const companiesShuffled = useMemo(() => shuffle(companies), []); + const companies = useCompanies(); return (
{ containerClass={styles.rootContainer} > - {companiesShuffled.map(({ url, src, alt }) => ( + {companies.map(({ url, src, alt }) => ( { + const { i18n } = useDocusaurusContext(); + + const shuffledLocaleCompanies = useMemo(() => { + const localeCompanies = + localeToCompaniesMap[i18n.currentLocale] || companies; + + const isEnoughLocaleCompanies = + localeCompanies.length >= MIN_COMPANIES_AMOUNT; + + const isDefaultLocale = i18n.currentLocale === DEFAULT_LOCALE; + + const isNeedAddDefaultLocaleCompanies = + !isEnoughLocaleCompanies && !isDefaultLocale; + + const fullLocaleCompanies = isNeedAddDefaultLocaleCompanies + ? [ + ...localeCompanies, + ...companies.slice( + 0, + MIN_COMPANIES_AMOUNT - localeCompanies.length, + ), + ] + : localeCompanies; + + return shuffle(fullLocaleCompanies); + }, [i18n.currentLocale]); + + return shuffledLocaleCompanies; +}; diff --git a/src/pages/_home/index.tsx b/src/pages/_home/index.tsx index 92c1da5588..83004835b3 100644 --- a/src/pages/_home/index.tsx +++ b/src/pages/_home/index.tsx @@ -16,17 +16,31 @@ export default function HomePage() { // NOTE: use siteConfig for getting config // const {siteConfig} = useDocusaurusContext(); return ( - +
{features.map((feature) => ( - + ))}
-
+
{concepts.map((concept) => ( - + ))}
- 🙏 {translate({ id: "pages.examples.add_me.title" })} + 🙏{" "} + {translate({ id: "pages.examples.add_me.title" })}
{examples.map((data) => ( -
+
))} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 099afb2157..4db176618c 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1 +1,3 @@ +import "@fontsource-variable/overpass"; + export { default } from "./_home"; diff --git a/src/pages/nav/index.tsx b/src/pages/nav/index.tsx index a63a4efc65..23883ee02a 100644 --- a/src/pages/nav/index.tsx +++ b/src/pages/nav/index.tsx @@ -9,7 +9,10 @@ import styles from "./styles.module.scss"; const NavPage = () => { return ( - +

{translate({ id: "pages.nav.title" })}

@@ -33,7 +36,9 @@ const GroupItems = () => { {(legacyRoutes as any).map((routesBatch) => (

{routesBatch.group}

-

⚡️ {routesBatch.details}

+

+ ⚡️ {routesBatch.details} +

{routesBatch.children.map((route) => ( { description={
- old: {flattenFrom(route.from)} + old:{" "} + {flattenFrom(route.from)}
new: {route.to} diff --git a/src/pages/nav/styles.module.scss b/src/pages/nav/styles.module.scss index bc763ef0b8..9e8947991b 100644 --- a/src/pages/nav/styles.module.scss +++ b/src/pages/nav/styles.module.scss @@ -31,7 +31,7 @@ opacity: 0.5; } -@media (max-width: 600px) { +@media (width <= 600px) { .groupItems { grid-template-columns: 1fr; } diff --git a/src/pages/versions/index.tsx b/src/pages/versions/index.tsx index ee7f7fd234..cb5d5ec40d 100644 --- a/src/pages/versions/index.tsx +++ b/src/pages/versions/index.tsx @@ -28,12 +28,12 @@ function Version() { {latestVersion && (
-

Feature-Sliced Design v2.0.0 (Current)

+

Feature-Sliced Design v2.1 (Current)

{translate({ id: "pages.versions.current" })}

+
@@ -44,6 +44,11 @@ function Version() { Migration from v1 + + Migration from v2.0 + +
@@ -51,23 +56,33 @@ function Version() {

Feature Slices v1 (Legacy)

-

{translate({ id: "pages.versions.legacy" }, { of: "feature-slices" })}

+

+ {translate( + { id: "pages.versions.legacy" }, + { of: "feature-slices" }, + )} +

Feature Driven (Legacy)

-

{translate({ id: "pages.versions.legacy" }, { of: "feature-driven" })}

+

+ {translate( + { id: "pages.versions.legacy" }, + { of: "feature-driven" }, + )} +

{ const dayInSeconds = 1000 * 60 * 60 * 24; - return Math.floor(Math.abs(firstDate.getTime() - secondDate.getTime()) / dayInSeconds); + return Math.floor( + Math.abs(firstDate.getTime() - secondDate.getTime()) / dayInSeconds, + ); }; export const date = { diff --git a/src/shared/lib/ga/ga.ts b/src/shared/lib/ga/ga.ts deleted file mode 100644 index 54d16c8132..0000000000 --- a/src/shared/lib/ga/ga.ts +++ /dev/null @@ -1,25 +0,0 @@ -type EventOptions = { - category: string; - action?: string; - label: string; - value?: number; -}; - -export const sendEvent = ({ category, action, label, value }: EventOptions) => { - if (typeof window === undefined) return; - if (!window.ga) return; - - return window.ga("send", { - hitType: "event", - eventCategory: category, - eventAction: action, - eventLabel: label, - eventValue: value, - }); -}; - -export const CATEGORIES = { - full: "Feedback 1.2 (full)", - mixed: "Feedback 1.2 (mixed)", - short: "Feedback 1.2 (short)", -}; diff --git a/src/shared/lib/ga/index.ts b/src/shared/lib/ga/index.ts deleted file mode 100644 index 4d5303778f..0000000000 --- a/src/shared/lib/ga/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * as ga from "./ga"; diff --git a/src/shared/ui/nav-card/index.tsx b/src/shared/ui/nav-card/index.tsx index ec6c8cbbcf..e69b9d2aa5 100644 --- a/src/shared/ui/nav-card/index.tsx +++ b/src/shared/ui/nav-card/index.tsx @@ -1,8 +1,7 @@ -import React, { useCallback } from "react"; +import React from "react"; import clsx from "clsx"; import Link from "@docusaurus/Link"; -import { ga } from "@site/src/shared/lib/ga"; import styles from "./styles.module.scss"; type Props = { @@ -21,15 +20,16 @@ type Props = { * @see https://docusaurus.io/docs/next/markdown-features/react#importing-markdown */ export const NavCard: React.FC = (props) => { - const { title, description, to, Icon, tags, className, disabled, theme = "default" } = props; - const handleClick = useCallback(() => { - ga.sendEvent({ - category: ga.CATEGORIES.full, - // FIXME: get later from props - action: "NavRow:Click", - label: to, - }); - }, [to]); + const { + title, + description, + to, + Icon, + tags, + className, + disabled, + theme = "default", + } = props; return ( = (props) => { styles[`${theme}Theme`], )} to={to} - onClick={handleClick} >
@@ -48,7 +47,9 @@ export const NavCard: React.FC = (props) => { {title}

{description}

- {tags &&
{tags.join(" • ")}
} + {tags && ( +
{tags.join(" • ")}
+ )} ); diff --git a/src/shared/ui/nav-card/styles.module.scss b/src/shared/ui/nav-card/styles.module.scss index de4b658fa5..a4254a4cdf 100644 --- a/src/shared/ui/nav-card/styles.module.scss +++ b/src/shared/ui/nav-card/styles.module.scss @@ -3,12 +3,12 @@ padding: 40px; color: var(--ifm-font-color-base); border-radius: var(--ifm-alert-border-radius); - box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 8px 0 rgb(0 0 0 / 10%); transition: 0.25s; &:hover { text-decoration: none; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%); } & + .root { diff --git a/src/shared/ui/section/index.tsx b/src/shared/ui/section/index.tsx index 818bea0d47..1839bfb314 100644 --- a/src/shared/ui/section/index.tsx +++ b/src/shared/ui/section/index.tsx @@ -21,7 +21,11 @@ export const Section: React.FC = ({ return (

{title}

diff --git a/src/shared/ui/table/index.tsx b/src/shared/ui/table/index.tsx index b640a920fe..1620f41150 100644 --- a/src/shared/ui/table/index.tsx +++ b/src/shared/ui/table/index.tsx @@ -6,7 +6,12 @@ type TableRowProps = React.PropsWithChildren<{ th: React.ReactNode; }>; -const TableRow: React.FC = ({ href, hrefTitle, th, children }) => { +const TableRow: React.FC = ({ + href, + hrefTitle, + th, + children, +}) => { return (
@@ -19,9 +24,9 @@ const TableRow: React.FC = ({ href, hrefTitle, th, children }) => ); }; -export const Table: React.FC & { Row: typeof TableRow } = ({ - children, -}) => { +export const Table: React.FC & { + Row: typeof TableRow; +} = ({ children }) => { return (
{th}
{children} diff --git a/src/shared/ui/wip/index.tsx b/src/shared/ui/wip/index.tsx index 841a772e7e..6bd0250c7a 100644 --- a/src/shared/ui/wip/index.tsx +++ b/src/shared/ui/wip/index.tsx @@ -27,7 +27,9 @@ export const WIP: React.FC = ({ ticket }) => {
  • {translate({ id: "shared.wip.var.feedback.base" })} - {translate({ id: "shared.wip.var.feedback.link" })} + + {translate({ id: "shared.wip.var.feedback.link" })} +
  • {translate({ id: "shared.wip.var.material.base" })} diff --git a/src/theme/DocItem/Footer/index.js b/src/theme/DocItem/Footer/index.js deleted file mode 100644 index e40ca91139..0000000000 --- a/src/theme/DocItem/Footer/index.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react"; -import Footer from "@theme-original/DocItem/Footer"; -import { DocFeedback } from "@site/src/features/feedback/doc"; -import styles from "./styles.module.scss"; - -/** - * DocItemFooter - * @swizzled unsafe: higher breaking change risk (keep actual!) - * @remark Couple to original DocItemFooter for more stability - * @see https://docusaurus.io/docs/next/using-themes#for-site-owners - */ -export default function FooterWrapper(props) { - return ( - <> -
    - - - ); -} diff --git a/src/theme/DocItem/Footer/index.tsx b/src/theme/DocItem/Footer/index.tsx new file mode 100644 index 0000000000..87e8262ce0 --- /dev/null +++ b/src/theme/DocItem/Footer/index.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import DocItemFooter from "@theme-original/DocItem/Footer"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import BrowserOnly from "@docusaurus/BrowserOnly"; +import { FeedbackWidget } from "@site/src/features/feedback/doc"; +import type DocItemFooterType from "@theme/DocItem/Footer"; +// eslint-disable-next-line import/no-unresolved +import type { WrapperProps } from "@docusaurus/types"; + +export default function FooterWrapper( + props: WrapperProps, +) { + const { + siteConfig: { url, customFields }, + } = useDocusaurusContext(); + + return ( + <> + + + {() => + typeof customFields.pushFeedbackProjectId === "string" && + window.location.hostname === new URL(url).hostname && ( + + ) + } + + + ); +} diff --git a/src/theme/DocItem/Footer/styles.module.scss b/src/theme/DocItem/Footer/styles.module.scss deleted file mode 100644 index 8c6a9fb174..0000000000 --- a/src/theme/DocItem/Footer/styles.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -.feedback { - /* For "sticking" to pagination */ - margin-top: 2rem; - margin-bottom: -2rem; -} diff --git a/src/theme/DocItem/lib/index.js b/src/theme/DocItem/lib/index.js index 35c015c891..186ea69895 100644 --- a/src/theme/DocItem/lib/index.js +++ b/src/theme/DocItem/lib/index.js @@ -7,6 +7,8 @@ export function useDocOGUrl(metadata, buildOGPath = "assets/og/") { /* OG Preview images build with locale prefix (.../en/assets/...) for not default locales */ return `${siteConfig.url}${ - i18n.currentLocale !== i18n.defaultLocale ? `/${i18n.currentLocale}` : "" + i18n.currentLocale !== i18n.defaultLocale + ? `/${i18n.currentLocale}` + : "" }/${buildOGPath}${hashFileName}.jpg`; } diff --git a/src/theme/README.md b/src/theme/README.md index e23da83738..ec176d54e4 100644 --- a/src/theme/README.md +++ b/src/theme/README.md @@ -6,5 +6,5 @@ This folder contains theme components overriding ## See also -- -- +- [https://docusaurus.io/docs/using-themes#wrapper-your-site-with-root](https://docusaurus.io/docs/using-themes#wrapper-your-site-with-root) +- [https://docusaurus.io/docs/using-themes#theme-components](https://docusaurus.io/docs/using-themes#theme-components) diff --git a/src/theme/Root.js b/src/theme/Root.js deleted file mode 100644 index db88b17537..0000000000 --- a/src/theme/Root.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react"; -import { CookieConsent } from "@site/src/features/cookie-consent"; - -// Default implementation, that you can customize -// https://docusaurus.io/docs/using-themes#wrapper-your-site-with-root - -function Root({ children }) { - return ( - <> - {children} - {/* NOTE: exp with HotJar feedback widget (FEEDBACK-325) */} - {/* */} - - - ); -} - -export default Root; diff --git a/static/CNAME b/static/CNAME deleted file mode 100644 index f806fe67d0..0000000000 --- a/static/CNAME +++ /dev/null @@ -1 +0,0 @@ -feature-sliced.design diff --git a/static/img/approaches.png b/static/img/approaches.png deleted file mode 100644 index 82472f5759..0000000000 Binary files a/static/img/approaches.png and /dev/null differ diff --git a/static/img/circular-import-dark.svg b/static/img/circular-import-dark.svg new file mode 100644 index 0000000000..14630e1882 --- /dev/null +++ b/static/img/circular-import-dark.svg @@ -0,0 +1 @@ +📄 fileA.js📄 fileB.js📄 fileC.js \ No newline at end of file diff --git a/static/img/circular-import-light.svg b/static/img/circular-import-light.svg new file mode 100644 index 0000000000..1c302f119b --- /dev/null +++ b/static/img/circular-import-light.svg @@ -0,0 +1 @@ +📄 fileA.js📄 fileB.js📄 fileC.js \ No newline at end of file diff --git a/static/img/companies/blindtyping.svg b/static/img/companies/blindtyping.svg new file mode 100644 index 0000000000..17f6c04a96 --- /dev/null +++ b/static/img/companies/blindtyping.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/static/img/companies/ja/hapins.png b/static/img/companies/ja/hapins.png new file mode 100644 index 0000000000..767ab546af Binary files /dev/null and b/static/img/companies/ja/hapins.png differ diff --git a/static/img/coupling-cohesion-dark.svg b/static/img/coupling-cohesion-dark.svg new file mode 100644 index 0000000000..c5c85a5f42 --- /dev/null +++ b/static/img/coupling-cohesion-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/coupling-cohesion-light.svg b/static/img/coupling-cohesion-light.svg new file mode 100644 index 0000000000..218bf05c0d --- /dev/null +++ b/static/img/coupling-cohesion-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/coupling.png b/static/img/coupling.png deleted file mode 100644 index 5ef1edda77..0000000000 Binary files a/static/img/coupling.png and /dev/null differ diff --git a/static/img/tutorial/conduit-banner.jpg b/static/img/tutorial/conduit-banner.jpg new file mode 100644 index 0000000000..af27fe0872 Binary files /dev/null and b/static/img/tutorial/conduit-banner.jpg differ diff --git a/static/img/tutorial/realworld-article-editor.jpg b/static/img/tutorial/realworld-article-editor.jpg new file mode 100644 index 0000000000..fb78edb52e Binary files /dev/null and b/static/img/tutorial/realworld-article-editor.jpg differ diff --git a/static/img/tutorial/realworld-article-reader.jpg b/static/img/tutorial/realworld-article-reader.jpg new file mode 100644 index 0000000000..08960961c8 Binary files /dev/null and b/static/img/tutorial/realworld-article-reader.jpg differ diff --git a/static/img/tutorial/realworld-editor-authenticated.jpg b/static/img/tutorial/realworld-editor-authenticated.jpg new file mode 100644 index 0000000000..c880ef3c18 Binary files /dev/null and b/static/img/tutorial/realworld-editor-authenticated.jpg differ diff --git a/static/img/tutorial/realworld-feed-anonymous.jpg b/static/img/tutorial/realworld-feed-anonymous.jpg new file mode 100644 index 0000000000..ccdeb45037 Binary files /dev/null and b/static/img/tutorial/realworld-feed-anonymous.jpg differ diff --git a/static/img/tutorial/realworld-feed-authenticated.jpg b/static/img/tutorial/realworld-feed-authenticated.jpg new file mode 100644 index 0000000000..21aeb7e7d2 Binary files /dev/null and b/static/img/tutorial/realworld-feed-authenticated.jpg differ diff --git a/static/img/tutorial/realworld-feed-without-tabs.jpg b/static/img/tutorial/realworld-feed-without-tabs.jpg new file mode 100644 index 0000000000..47b7afbf91 Binary files /dev/null and b/static/img/tutorial/realworld-feed-without-tabs.jpg differ diff --git a/static/robots.txt b/static/robots.txt new file mode 100644 index 0000000000..02e7cf6bc9 --- /dev/null +++ b/static/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Disallow: + +Sitemap: https://feature-sliced.github.io/documentation/sitemap.xml diff --git a/tsconfig.json b/tsconfig.json index a4416b3911..fd4988854b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "@tsconfig/docusaurus/tsconfig.json", + "extends": "@docusaurus/tsconfig", "compilerOptions": { - "baseUrl": ".", - "typeRoots": ["declaration.d.ts", "global.d.ts", "./node_modules/@types", "node", "@docusaurus/module-type-aliases", "@docusaurus/theme-classic"] + "baseUrl": ".", + "lib": ["DOM", "ESNext"] } }