Compare commits
10 Commits
41b17925df
...
feat/hones
| Author | SHA1 | Date | |
|---|---|---|---|
| adcca7cbc4 | |||
| d126dc3ef0 | |||
| 0198c8fcf2 | |||
| 24075dceb7 | |||
| d621baae45 | |||
| ed9d2c1b1f | |||
| 7f73343f3f | |||
| 0d4ffa7411 | |||
| c24e30f14c | |||
| d43564481d |
@@ -1,64 +0,0 @@
|
|||||||
# Cursor AI Agents Configuration
|
|
||||||
|
|
||||||
## Роли и правила поведения ИИ
|
|
||||||
|
|
||||||
### Основная роль: Senior Full-Stack Developer
|
|
||||||
|
|
||||||
Вы - опытный full-stack разработчик, специализирующийся на:
|
|
||||||
|
|
||||||
- OpenCart модульной разработке
|
|
||||||
- Кастомных фреймворках (OpenCart Framework)
|
|
||||||
- PHP 7.4+ с современными практиками
|
|
||||||
- Vue.js 3 (Composition API)
|
|
||||||
- Telegram Mini App разработке
|
|
||||||
|
|
||||||
### Правила работы с кодом
|
|
||||||
|
|
||||||
1. **Всегда используй существующие паттерны проекта**
|
|
||||||
2. **Не создавай дубликаты - используй существующие утилиты**
|
|
||||||
3. **Следуй соглашениям именования проекта**
|
|
||||||
4. **Тестируй изменения перед коммитом**
|
|
||||||
5. **Документируй публичные API**
|
|
||||||
6. **Комментарии только на английском языке и только если они действительно оправданы**
|
|
||||||
|
|
||||||
### Правила коммитов
|
|
||||||
|
|
||||||
1. **Следование Conventional Commits**
|
|
||||||
- Используй префиксы: `feat:`, `fix:`, `chore:`, `refactor:`, `style:`, `test:`, `docs:`
|
|
||||||
- Формат: `<type>: <subject>` (первая строка до 72 символов)
|
|
||||||
- После пустой строки - подробное описание изменений
|
|
||||||
|
|
||||||
2. **Язык коммитов**
|
|
||||||
- Все коммиты на **английском языке**
|
|
||||||
- Подробное описание изменений в теле коммита
|
|
||||||
- Перечисляй все измененные файлы и ключевые изменения
|
|
||||||
|
|
||||||
3. **Примеры правильных коммитов**
|
|
||||||
```
|
|
||||||
feat: add setting to control category products button visibility
|
|
||||||
|
|
||||||
- Add show_category_products_button field to StoreDTO
|
|
||||||
- Update SettingsSerializerService to support new field
|
|
||||||
- Add setting in admin panel on 'Store' tab with toggle
|
|
||||||
- Pass setting to SPA through SettingsHandler
|
|
||||||
- Button displays only for categories with child categories
|
|
||||||
- Add default value true to configuration
|
|
||||||
```
|
|
||||||
|
|
||||||
### Запрещено
|
|
||||||
|
|
||||||
- Хардкод значений (используй конфиги/настройки)
|
|
||||||
- Игнорирование обработки ошибок
|
|
||||||
- Создание циклических зависимостей
|
|
||||||
|
|
||||||
Для разработки FrontEnd используй:
|
|
||||||
|
|
||||||
- Vue.js 3 (Composition API)
|
|
||||||
- Старайся избегать функций watch там, где это возможно и где можно сделать более красиво.
|
|
||||||
- Для frontend/admin используй Tailwind 4 с префиксом `tw:`.
|
|
||||||
- Для frontend/spa используй Tailwind 4 без префикса.
|
|
||||||
- Для frontend/admin используй иконки от FontAwesome 4, потому что это уже встроено в OpenCart 3.
|
|
||||||
- Для frontend/admin используй компоненты VuePrime 4.
|
|
||||||
- Для frontend/spa используй Daisy UI.
|
|
||||||
- Чтобы получить название стандартной таблицы OpenCart, используй хелпер `db_table`, либо добавляй константу DB_PREFIX перед названием таблицы. Так ты получишь название таблицы с префиксом.
|
|
||||||
- Все таблицы моего модуля TeleCart начинаются с префикса `telecart_`. Примеры миграций лежат в `module/oc_telegram_shop/upload/oc_telegram_shop/database/migrations`
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"rules": {
|
|
||||||
"preferCompositionAPI": true,
|
|
||||||
"strictTypes": true,
|
|
||||||
"noHardcodedValues": true,
|
|
||||||
"useDependencyInjection": true
|
|
||||||
},
|
|
||||||
"paths": {
|
|
||||||
"telecart_module": "module/oc_telegram_shop/upload/oc_telegram_shop",
|
|
||||||
"frontendAdmin": "frontend/admin",
|
|
||||||
"telegramShopSpa": "frontend/spa",
|
|
||||||
"migrations": "module/oc_telegram_shop/upload/oc_telegram_shop/database/migrations",
|
|
||||||
"telecartHandlers": "module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers",
|
|
||||||
"adminHandlers": "module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers",
|
|
||||||
"models": "module/oc_telegram_shop/upload/oc_telegram_shop/src/Models",
|
|
||||||
"framework": "module/oc_telegram_shop/upload/oc_telegram_shop/framework"
|
|
||||||
},
|
|
||||||
"naming": {
|
|
||||||
"classes": "PascalCase",
|
|
||||||
"methods": "camelCase",
|
|
||||||
"variables": "camelCase",
|
|
||||||
"constants": "UPPER_SNAKE_CASE",
|
|
||||||
"files": "PascalCase for classes, kebab-case for others",
|
|
||||||
"tables": "snake_case with telecart_ prefix"
|
|
||||||
},
|
|
||||||
"php": {
|
|
||||||
"version": "7.4+",
|
|
||||||
"preferVersion": "7.4+",
|
|
||||||
"psr12": true
|
|
||||||
},
|
|
||||||
"javascript": {
|
|
||||||
"version": "ES2020+",
|
|
||||||
"framework": "Vue 3 Composition API",
|
|
||||||
"stateManagement": "Pinia",
|
|
||||||
"uiLibrary": "PrimeVue (admin), Tailwind (spa)"
|
|
||||||
},
|
|
||||||
"database": {
|
|
||||||
"queryBuilder": true,
|
|
||||||
"migrations": true,
|
|
||||||
"tablePrefix": "telecart_",
|
|
||||||
"noForeignKeys": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
## TeleCart Pulse Heartbeat Telemetry
|
|
||||||
|
|
||||||
### Цель
|
|
||||||
Раз в час отправлять телеметрию (heartbeat) на TeleCart Pulse, чтобы фиксировать состояние магазина и версии окружения без участия пользователя.
|
|
||||||
|
|
||||||
### Backend (`module/oc_telegram_shop/upload/oc_telegram_shop`)
|
|
||||||
- `framework/TeleCartPulse/TeleCartPulseService.php`
|
|
||||||
- Новый метод `handleHeartbeat()` собирает данные: домен (через `Utils::getCurrentDomain()`), username бота (через `TelegramService::getMe()`), версии PHP, модуля (из `composer.json`), OpenCart (`VERSION` и `VERSION_CORE`), текущий UTC timestamp.
|
|
||||||
- Последний успешный пинг кешируется (ключ `telecart_pulse_heartbeat`, TTL 1 час) через существующий `CacheInterface`.
|
|
||||||
- Подпись heartbeat выполняется через отдельный `PayloadSigner`, который использует секрет `pulse.heartbeat_secret`/`PULSE_HEARTBEAT_SECRET`. Логируются предупреждения при ошибках кеша/бота/подписи.
|
|
||||||
- Отправка идет на эндпоинт `heartbeat` с таймаутом 2 секунды и заголовком `X-TELECART-VERSION`, взятым из `composer.json`.
|
|
||||||
- `framework/TeleCartPulse/TeleCartPulseServiceProvider.php`
|
|
||||||
- Регистрирует основной `PayloadSigner` (по `pulse.api_key`) и отдельный heartbeat signer (по `pulse.heartbeat_secret` или `PULSE_HEARTBEAT_SECRET`), инжектит `LoggerInterface`.
|
|
||||||
- `src/Handlers/TelemetryHandler.php` + `src/routes.php`
|
|
||||||
- Добавлен маршрут `heartbeat`, который вызывает `handleHeartbeat()` и возвращает `{ status: "ok" }`. Логгер пишет warning при проблемах.
|
|
||||||
|
|
||||||
### Frontend (`frontend/spa`)
|
|
||||||
- `src/utils/ftch.js`: новая функция `heartbeat()` вызывает `api_action=heartbeat`.
|
|
||||||
- `src/stores/Pulse.js`: добавлен action `heartbeat`, использующий новую API-функцию и логирующий результат.
|
|
||||||
- `src/main.js`: после `pulse.ingest(...)` вызывается `pulse.heartbeat()` без блокировки цепочки.
|
|
||||||
|
|
||||||
### Конфигурация / ENV
|
|
||||||
- `PULSE_API_HOST` — базовый URL TeleCart Pulse (используется и для events, и для heartbeat).
|
|
||||||
- `PULSE_TIMEOUT` — общий таймаут HTTP (для heartbeat принудительно 2 секунды).
|
|
||||||
- `PULSE_HEARTBEAT_SECRET` (или `pulse.heartbeat_secret` в настройках) — общий секрет для подписания heartbeat. Обязателен, иначе heartbeat не будет отправляться.
|
|
||||||
- `pulse.api_key` — прежний API ключ, используется только для event-инджеста.
|
|
||||||
|
|
||||||
### Поведение
|
|
||||||
1. Frontend (SPA) вызывает `heartbeat` при инициализации приложения (fire-and-forget).
|
|
||||||
2. Backend проверяет кеш. Если часа еще не прошло, `handleHeartbeat()` возвращает без запросов.
|
|
||||||
3. При необходимости собираются данные, подписываются через heartbeat signer и отправляются POST-запросом на `/heartbeat`.
|
|
||||||
4. Любые сбои (bot info, подпись, HTTP) логируются как warning, чтобы не тревожить пользователей.
|
|
||||||
|
|
||||||
### TODO / Возможные улучшения
|
|
||||||
- При необходимости вынести heartbeat запуск в крон/CLI, чтобы не зависеть от фронтенда.
|
|
||||||
- Добавить метрики успешности heartbeat в админку.
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# Промпты для генерации API
|
|
||||||
|
|
||||||
## Создание нового API endpoint
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай новый API endpoint [ENDPOINT_NAME] для [DESCRIPTION]:
|
|
||||||
|
|
||||||
1. Handler в [HANDLER_PATH]:
|
|
||||||
- Метод handle() принимает Request
|
|
||||||
- Валидация входных данных
|
|
||||||
- Использование Service для бизнес-логики
|
|
||||||
- Возврат JsonResponse с правильной структурой
|
|
||||||
- Обработка ошибок с логированием
|
|
||||||
|
|
||||||
2. Service в [SERVICE_PATH]:
|
|
||||||
- Бизнес-логика
|
|
||||||
- Работа с Model
|
|
||||||
- Валидация данных
|
|
||||||
- Обработка исключений
|
|
||||||
|
|
||||||
3. Model в [MODEL_PATH] (если нужен):
|
|
||||||
- Методы для работы с БД
|
|
||||||
- Использование Query Builder
|
|
||||||
- Типизация методов
|
|
||||||
|
|
||||||
4. Route в routes.php:
|
|
||||||
- Добавь маршрут с правильным именем
|
|
||||||
|
|
||||||
5. Миграция (если нужна новая таблица):
|
|
||||||
- Создай миграцию в database/migrations/
|
|
||||||
- Используй фиксированный префикс telecart_
|
|
||||||
- Добавь индексы где необходимо
|
|
||||||
|
|
||||||
Следуй архитектуре MVC-L проекта и используй существующие паттерны.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание CRUD API
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай полный CRUD API для сущности [ENTITY_NAME]:
|
|
||||||
|
|
||||||
1. Handler с методами:
|
|
||||||
- list() - список с пагинацией и фильтрацией
|
|
||||||
- get() - получение одной записи
|
|
||||||
- create() - создание
|
|
||||||
- update() - обновление
|
|
||||||
- delete() - удаление
|
|
||||||
|
|
||||||
2. Service с бизнес-логикой для всех операций
|
|
||||||
|
|
||||||
3. Model с методами:
|
|
||||||
- findAll() - список
|
|
||||||
- findById() - по ID
|
|
||||||
- create() - создание
|
|
||||||
- update() - обновление
|
|
||||||
- delete() - удаление
|
|
||||||
|
|
||||||
4. DTO для валидации данных
|
|
||||||
|
|
||||||
5. Миграция для таблицы [TABLE_NAME]
|
|
||||||
|
|
||||||
6. Routes для всех endpoints
|
|
||||||
|
|
||||||
Используй серверную пагинацию, фильтрацию и сортировку для list().
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание Admin API endpoint
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай Admin API endpoint [ENDPOINT_NAME] в bastion/Handlers/:
|
|
||||||
|
|
||||||
1. Handler в bastion/Handlers/[HANDLER_NAME].php:
|
|
||||||
- Используй Request для получения параметров
|
|
||||||
- Валидация данных
|
|
||||||
- Работа через Service
|
|
||||||
- Возврат JsonResponse с структурой { data: { data: [...], totalRecords: ... } }
|
|
||||||
- Обработка ошибок
|
|
||||||
|
|
||||||
2. Service в bastion/Services/ (если нужен):
|
|
||||||
- Бизнес-логика для админки
|
|
||||||
- Работа с Models
|
|
||||||
|
|
||||||
3. Route в bastion/routes.php
|
|
||||||
|
|
||||||
4. Frontend компонент (если нужен UI):
|
|
||||||
- Vue компонент в frontend/admin/src/views/
|
|
||||||
- Используй PrimeVue компоненты
|
|
||||||
- Серверная пагинация/фильтрация
|
|
||||||
- Обработка ошибок с toast уведомлениями
|
|
||||||
|
|
||||||
Следуй существующим паттернам проекта.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание Frontend API клиента
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай функцию для работы с API endpoint [ENDPOINT_NAME]:
|
|
||||||
|
|
||||||
1. В frontend/[admin|spa]/src/utils/http.js:
|
|
||||||
- Функция api[Method] для вызова endpoint
|
|
||||||
- Правильная обработка ошибок
|
|
||||||
- Возврат структурированного ответа
|
|
||||||
|
|
||||||
2. Использование:
|
|
||||||
- В компонентах через import
|
|
||||||
- Обработка loading states
|
|
||||||
- Toast уведомления для ошибок
|
|
||||||
|
|
||||||
Следуй существующим паттернам в http.js.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание миграции
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай миграцию для таблицы [TABLE_NAME]:
|
|
||||||
|
|
||||||
1. Файл: database/migrations/[TIMESTAMP]_[DESCRIPTION].php
|
|
||||||
2. Используй фиксированный префикс telecart_ для таблицы
|
|
||||||
3. Добавь все необходимые поля с правильными типами
|
|
||||||
4. Добавь индексы для часто используемых полей
|
|
||||||
5. Используй utf8mb4_unicode_ci collation
|
|
||||||
6. Используй InnoDB engine
|
|
||||||
7. Добавь created_at и updated_at timestamps
|
|
||||||
8. Не создавай foreign keys (используй только индексы)
|
|
||||||
|
|
||||||
Следуй структуре существующих миграций.
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
# Правила оформления Changelog для документации
|
|
||||||
|
|
||||||
## Общие требования
|
|
||||||
|
|
||||||
- **Формат**: Markdown
|
|
||||||
- **Структура**: Одна версия = одна страница
|
|
||||||
- **Стиль**: Профессиональный, лаконичный, без маркетинговых лозунгов
|
|
||||||
- **Язык**: Русский
|
|
||||||
- **Целевая аудитория**: Разработчики и владельцы магазинов
|
|
||||||
- **Содержимое**: Только ключевые изменения, без лишних технических деталей
|
|
||||||
|
|
||||||
## Структура страницы
|
|
||||||
|
|
||||||
### Вводный абзац
|
|
||||||
- Краткое описание релиза (1-2 предложения)
|
|
||||||
- Дополнить 1-2 предложениями о ключевых изменениях
|
|
||||||
- Упоминать основные фичи и улучшения
|
|
||||||
|
|
||||||
### Разделы (строго в этом порядке)
|
|
||||||
|
|
||||||
1. **🚀 Добавлено** - новые функции и возможности
|
|
||||||
2. **✨ Улучшено** - улучшения существующих функций
|
|
||||||
3. **🐞 Исправлено** - исправленные ошибки
|
|
||||||
4. **⚠️ Несовместимые изменения** - изменения без обратной совместимости
|
|
||||||
5. **🔧 Технические изменения** - технические изменения (отдельный раздел)
|
|
||||||
|
|
||||||
### Правила для разделов
|
|
||||||
|
|
||||||
- Раздел **НЕ** добавлять, если в нём нет пунктов
|
|
||||||
- Использовать маркдаун-списки, без нумерации
|
|
||||||
- Не добавлять ссылок, если они явно не указаны
|
|
||||||
- Не добавлять лишних пояснений, выводов и заключений
|
|
||||||
- Выводить только содержимое Markdown-файла, без комментариев
|
|
||||||
|
|
||||||
## Разделение изменений
|
|
||||||
|
|
||||||
### Бизнес-логика и процессы
|
|
||||||
Разделы "Добавлено", "Улучшено", "Исправлено", "Несовместимые изменения" содержат только изменения, связанные с:
|
|
||||||
- Бизнес-процессами магазина
|
|
||||||
- Пользовательским опытом
|
|
||||||
- Функциональностью для владельцев магазинов
|
|
||||||
- Продающими фичами
|
|
||||||
|
|
||||||
### Технические изменения
|
|
||||||
Все технические изменения выносятся в отдельный раздел **🔧 Технические изменения**:
|
|
||||||
- Без подразделов, всё в одном списке
|
|
||||||
- Только самые ключевые технические изменения
|
|
||||||
- Не расписывать детали, которые не интересны пользователю
|
|
||||||
|
|
||||||
## Стиль написания
|
|
||||||
|
|
||||||
### Терминология
|
|
||||||
- Английские термины писать по-английски (например: "navbar", а не "навбар")
|
|
||||||
- Избегать технических терминов в бизнес-разделах (например: "customer_id" → "автоматическое связывание покупателей")
|
|
||||||
|
|
||||||
### Описания
|
|
||||||
|
|
||||||
**Для ключевых продающих фич:**
|
|
||||||
- Давать подробное описание с деталями
|
|
||||||
- Указывать возможности и преимущества
|
|
||||||
- Подчеркивать ценность для пользователя
|
|
||||||
|
|
||||||
**Для технических изменений:**
|
|
||||||
- Кратко, только ключевое
|
|
||||||
- Без лишних деталей
|
|
||||||
|
|
||||||
**Для обычных функций:**
|
|
||||||
- Лаконично, но информативно
|
|
||||||
- Фокус на пользе для пользователя
|
|
||||||
|
|
||||||
## Порядок размещения
|
|
||||||
|
|
||||||
### В разделе "Добавлено"
|
|
||||||
Ключевые продающие фичи размещать **первыми**:
|
|
||||||
1. Система конфигурации главной страницы через блоки
|
|
||||||
2. Конструктор форм на базе FormKit
|
|
||||||
3. Интеграция с Яндекс.Метрикой
|
|
||||||
4. Политика конфиденциальности
|
|
||||||
5. Поддержка купонов и подарочных сертификатов
|
|
||||||
6. Остальные функции
|
|
||||||
|
|
||||||
## Примеры правильных формулировок
|
|
||||||
|
|
||||||
### ✅ Хорошо
|
|
||||||
- "Автоматическое связывание покупателей Telegram с покупателями OpenCart: система автоматически находит и связывает покупателей из Telegram-магазина с существующими покупателями в OpenCart по email или номеру телефона, создавая единую базу клиентов"
|
|
||||||
|
|
||||||
### ❌ Плохо
|
|
||||||
- "Сохранение customer_id вместе с заказом для связи с клиентами OpenCart: автоматическая связь заказов из Telegram с покупателями в OpenCart, единая база клиентов"
|
|
||||||
|
|
||||||
### ✅ Хорошо
|
|
||||||
- "Navbar с логотипом и названием приложения"
|
|
||||||
|
|
||||||
### ❌ Плохо
|
|
||||||
- "Навбар с логотипом и названием приложения"
|
|
||||||
|
|
||||||
## Что не включать
|
|
||||||
|
|
||||||
- Внутренние детали разработки (тесты, статический анализ, обфускация)
|
|
||||||
- Технические детали, не интересные пользователям
|
|
||||||
- Избыточные технические описания
|
|
||||||
- Маркетинговые лозунги и призывы
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
# Промпты для документирования
|
|
||||||
|
|
||||||
## Документирование класса
|
|
||||||
|
|
||||||
```
|
|
||||||
Добавь PHPDoc документацию для класса [CLASS_NAME]:
|
|
||||||
1. Описание класса и его назначения
|
|
||||||
2. @package тег
|
|
||||||
3. @author тег
|
|
||||||
4. Документация для всех публичных методов
|
|
||||||
5. Документация для публичных свойств
|
|
||||||
6. Примеры использования где уместно
|
|
||||||
```
|
|
||||||
|
|
||||||
## Документирование метода
|
|
||||||
|
|
||||||
```
|
|
||||||
Добавь PHPDoc для метода [METHOD_NAME]:
|
|
||||||
1. Описание метода
|
|
||||||
2. @param для всех параметров с типами
|
|
||||||
3. @return с типом возвращаемого значения
|
|
||||||
4. @throws для всех исключений
|
|
||||||
5. Примеры использования если сложная логика
|
|
||||||
```
|
|
||||||
|
|
||||||
## Документирование API endpoint
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай документацию для API endpoint [ENDPOINT_NAME]:
|
|
||||||
1. Описание назначения
|
|
||||||
2. HTTP метод и путь
|
|
||||||
3. Параметры запроса (query/body)
|
|
||||||
4. Формат ответа (JSON структура)
|
|
||||||
5. Коды ошибок
|
|
||||||
6. Примеры запросов/ответов
|
|
||||||
7. Требования к авторизации
|
|
||||||
```
|
|
||||||
|
|
||||||
## Документирование Vue компонента
|
|
||||||
|
|
||||||
```
|
|
||||||
Добавь документацию для Vue компонента [COMPONENT_NAME]:
|
|
||||||
1. Описание компонента
|
|
||||||
2. Props с типами и описаниями
|
|
||||||
3. Emits с описаниями
|
|
||||||
4. Slots если есть
|
|
||||||
5. Примеры использования
|
|
||||||
6. Зависимости от других компонентов
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание README
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай README.md для [MODULE/COMPONENT]:
|
|
||||||
1. Описание назначения
|
|
||||||
2. Установка/настройка
|
|
||||||
3. Использование с примерами
|
|
||||||
4. API документация
|
|
||||||
5. Конфигурация
|
|
||||||
6. Troubleshooting
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
# Промпты для рефакторинга
|
|
||||||
|
|
||||||
## Общий рефакторинг
|
|
||||||
|
|
||||||
```
|
|
||||||
Проанализируй код в файле [FILE_PATH] и выполни рефакторинг:
|
|
||||||
1. Убери дублирование кода
|
|
||||||
2. Улучши читаемость
|
|
||||||
3. Примени принципы SOLID
|
|
||||||
4. Добавь обработку ошибок где необходимо
|
|
||||||
5. Улучши типизацию
|
|
||||||
6. Добавь документацию для публичных методов
|
|
||||||
7. Убедись что код следует архитектуре MVC-L проекта
|
|
||||||
8. Используй существующие утилиты и сервисы проекта вместо создания новых
|
|
||||||
```
|
|
||||||
|
|
||||||
## Рефакторинг Handler
|
|
||||||
|
|
||||||
```
|
|
||||||
Рефакторинг Handler [HANDLER_NAME]:
|
|
||||||
1. Вынеси бизнес-логику в отдельный Service
|
|
||||||
2. Добавь валидацию входных данных
|
|
||||||
3. Улучши обработку ошибок с логированием
|
|
||||||
4. Используй DTO для передачи данных
|
|
||||||
5. Добавь PHPDoc комментарии
|
|
||||||
6. Убедись что используется Dependency Injection
|
|
||||||
7. Оптимизируй запросы к БД если необходимо
|
|
||||||
```
|
|
||||||
|
|
||||||
## Рефакторинг Model
|
|
||||||
|
|
||||||
```
|
|
||||||
Рефакторинг Model [MODEL_NAME]:
|
|
||||||
1. Убедись что все запросы используют Query Builder
|
|
||||||
2. Добавь методы для частых операций (findBy, findAll, create, update)
|
|
||||||
3. Добавь валидацию данных перед сохранением
|
|
||||||
4. Улучши типизацию методов
|
|
||||||
5. Добавь PHPDoc комментарии
|
|
||||||
6. Используй транзакции для сложных операций
|
|
||||||
```
|
|
||||||
|
|
||||||
## Рефакторинг Vue компонента
|
|
||||||
|
|
||||||
```
|
|
||||||
Рефакторинг Vue компонента [COMPONENT_NAME]:
|
|
||||||
1. Вынеси логику в composable функции
|
|
||||||
2. Улучши типизацию props и emits
|
|
||||||
3. Оптимизируй computed properties
|
|
||||||
4. Добавь обработку ошибок
|
|
||||||
5. Улучши структуру template
|
|
||||||
6. Добавь loading states
|
|
||||||
7. Используй существующие утилиты проекта
|
|
||||||
```
|
|
||||||
|
|
||||||
## Удаление дублирования
|
|
||||||
|
|
||||||
```
|
|
||||||
Найди и устрани дублирование кода в:
|
|
||||||
- [FILE_PATH_1]
|
|
||||||
- [FILE_PATH_2]
|
|
||||||
- [FILE_PATH_3]
|
|
||||||
|
|
||||||
Создай общие утилиты/сервисы где необходимо, следуя архитектуре проекта.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Улучшение производительности
|
|
||||||
|
|
||||||
```
|
|
||||||
Проанализируй производительность кода в [FILE_PATH]:
|
|
||||||
1. Оптимизируй запросы к БД (используй индексы, избегай N+1)
|
|
||||||
2. Добавь кэширование где уместно
|
|
||||||
3. Оптимизируй алгоритмы
|
|
||||||
4. Уменьши количество запросов к API
|
|
||||||
5. Используй ленивую загрузку на фронтенде
|
|
||||||
```
|
|
||||||
|
|
||||||
## Улучшение безопасности
|
|
||||||
|
|
||||||
```
|
|
||||||
Улучши безопасность кода в [FILE_PATH]:
|
|
||||||
1. Добавь валидацию всех входных данных
|
|
||||||
2. Используй prepared statements (Query Builder)
|
|
||||||
3. Добавь CSRF защиту где необходимо
|
|
||||||
4. Валидируй права доступа
|
|
||||||
5. Санитизируй выходные данные
|
|
||||||
6. Добавь rate limiting где необходимо
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
# Промпты для тестирования
|
|
||||||
|
|
||||||
## Создание unit теста
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай unit тест для [CLASS_NAME] в tests/Unit/:
|
|
||||||
|
|
||||||
1. Используй PHPUnit
|
|
||||||
2. Покрой все публичные методы
|
|
||||||
3. Тестируй успешные сценарии
|
|
||||||
4. Тестируй обработку ошибок
|
|
||||||
5. Используй моки для зависимостей
|
|
||||||
6. Следуй структуре существующих тестов
|
|
||||||
7. Используй TestCase базовый класс проекта
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание integration теста
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай integration тест для [FEATURE_NAME] в tests/Integration/:
|
|
||||||
|
|
||||||
1. Тестируй полный flow от запроса до ответа
|
|
||||||
2. Используй тестовую БД
|
|
||||||
3. Очищай данные после тестов
|
|
||||||
4. Тестируй реальные сценарии использования
|
|
||||||
5. Проверяй валидацию данных
|
|
||||||
6. Проверяй обработку ошибок
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание Vue компонент теста
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай тест для Vue компонента [COMPONENT_NAME] в frontend/[admin|spa]/tests/:
|
|
||||||
|
|
||||||
1. Используй Vitest
|
|
||||||
2. Тестируй рендеринг компонента
|
|
||||||
3. Тестируй props
|
|
||||||
4. Тестируй события (emits)
|
|
||||||
5. Тестируй пользовательские взаимодействия
|
|
||||||
6. Используй моки для API вызовов
|
|
||||||
7. Следуй структуре существующих тестов
|
|
||||||
```
|
|
||||||
|
|
||||||
## Покрытие тестами
|
|
||||||
|
|
||||||
```
|
|
||||||
Проанализируй покрытие тестами для [FILE_PATH]:
|
|
||||||
1. Определи какие методы не покрыты тестами
|
|
||||||
2. Создай тесты для критичных методов
|
|
||||||
3. Убедись что тестируются граничные случаи
|
|
||||||
4. Добавь тесты для обработки ошибок
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
# Архитектурные правила
|
|
||||||
|
|
||||||
## OpenCart Framework Architecture
|
|
||||||
|
|
||||||
### MVC-L Pattern
|
|
||||||
|
|
||||||
Проект использует модифицированный паттерн MVC-L (Model-View-Controller-Language):
|
|
||||||
|
|
||||||
- **Model**: Классы в `src/Models/` - работа с данными, доступ к БД
|
|
||||||
- **View**: Vue компоненты на фронтенде, JSON ответы на бэкенде
|
|
||||||
- **Controller**: Handlers в `src/Handlers/` и `bastion/Handlers/`
|
|
||||||
- **Language**: Переводчик в `framework/Translator/`
|
|
||||||
|
|
||||||
### Dependency Injection
|
|
||||||
|
|
||||||
Все зависимости внедряются через Container:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Правильно
|
|
||||||
public function __construct(
|
|
||||||
private Builder $builder,
|
|
||||||
private TelegramCustomer $telegramCustomerModel
|
|
||||||
) {}
|
|
||||||
|
|
||||||
// ❌ Неправильно
|
|
||||||
public function __construct() {
|
|
||||||
$this->builder = new Builder(...);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Service Providers
|
|
||||||
|
|
||||||
Регистрация сервисов через Service Providers:
|
|
||||||
|
|
||||||
```php
|
|
||||||
class MyServiceProvider extends ServiceProvider
|
|
||||||
{
|
|
||||||
public function register(): void
|
|
||||||
{
|
|
||||||
$this->app->singleton(MyService::class, function ($app) {
|
|
||||||
return new MyService($app->get(Dependency::class));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Routes
|
|
||||||
|
|
||||||
Маршруты определяются в `routes.php`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
return [
|
|
||||||
'actionName' => [HandlerClass::class, 'methodName'],
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
### Handlers (Controllers)
|
|
||||||
|
|
||||||
Handlers обрабатывают HTTP запросы:
|
|
||||||
|
|
||||||
```php
|
|
||||||
class MyHandler
|
|
||||||
{
|
|
||||||
public function handle(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
// Валидация
|
|
||||||
// Бизнес-логика через Services
|
|
||||||
// Возврат JsonResponse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Models
|
|
||||||
|
|
||||||
Models работают с данными:
|
|
||||||
|
|
||||||
```php
|
|
||||||
class MyModel
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private ConnectionInterface $database,
|
|
||||||
private Builder $builder
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function findById(int $id): ?array
|
|
||||||
{
|
|
||||||
return $this->builder->newQuery()
|
|
||||||
->from($this->tableName)
|
|
||||||
->where('id', '=', $id)
|
|
||||||
->firstOrNull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Services
|
|
||||||
|
|
||||||
Services содержат бизнес-логику:
|
|
||||||
|
|
||||||
```php
|
|
||||||
class MyService
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private MyModel $model
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function doSomething(array $data): array
|
|
||||||
{
|
|
||||||
// Бизнес-логика
|
|
||||||
return $this->model->create($data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Migrations
|
|
||||||
|
|
||||||
Миграции в `database/migrations/`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
return new class extends Migration {
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->database->statement('CREATE TABLE ...');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Query Builder
|
|
||||||
|
|
||||||
Всегда используй Query Builder вместо прямых SQL:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Правильно
|
|
||||||
$query = $this->builder->newQuery()
|
|
||||||
->select(['id', 'name'])
|
|
||||||
->from('table_name')
|
|
||||||
->where('status', '=', 'active')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
// ❌ Неправильно
|
|
||||||
$result = $this->database->query("SELECT * FROM table_name WHERE status = 'active'");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Frontend Architecture
|
|
||||||
|
|
||||||
#### Admin Panel (Vue 3)
|
|
||||||
|
|
||||||
- Composition API
|
|
||||||
- Pinia для state management
|
|
||||||
- PrimeVue для UI компонентов
|
|
||||||
- Axios для HTTP запросов
|
|
||||||
- Vue Router для навигации
|
|
||||||
|
|
||||||
#### SPA (Telegram Mini App)
|
|
||||||
|
|
||||||
- Composition API
|
|
||||||
- Pinia stores
|
|
||||||
- Tailwind CSS для стилей
|
|
||||||
- Telegram WebApp API
|
|
||||||
- Vue Router
|
|
||||||
|
|
||||||
### Naming Conventions
|
|
||||||
|
|
||||||
- **Classes**: PascalCase (`TelegramCustomerService`)
|
|
||||||
- **Methods**: camelCase (`getCustomers`)
|
|
||||||
- **Variables**: camelCase (`$customerData`)
|
|
||||||
- **Constants**: UPPER_SNAKE_CASE (`MAX_RETRIES`)
|
|
||||||
- **Files**: PascalCase для классов, kebab-case для остального
|
|
||||||
- **Tables**: snake_case с префиксом `telecart_`
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
|
|
||||||
Всегда обрабатывай ошибки:
|
|
||||||
|
|
||||||
```php
|
|
||||||
try {
|
|
||||||
$result = $this->service->doSomething();
|
|
||||||
} catch (SpecificException $e) {
|
|
||||||
$this->logger->error('Error message', ['exception' => $e]);
|
|
||||||
throw new UserFriendlyException('User message');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
Используй конфигурационные файлы в `configs/`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$config = $this->app->getConfigValue('app.setting_name');
|
|
||||||
```
|
|
||||||
|
|
||||||
### Caching
|
|
||||||
|
|
||||||
Используй Cache Service для кэширования:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache = $this->app->get(CacheInterface::class);
|
|
||||||
$value = $cache->get('key', function() {
|
|
||||||
return expensiveOperation();
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
# FormBuilder System Context
|
|
||||||
|
|
||||||
## Architectural Overview
|
|
||||||
The FormBuilder ecosystem is a strictly typed Vue 3 application module designed to generate standard FormKit Schema JSON. It eschews internal DTOs in favor of direct schema manipulation.
|
|
||||||
|
|
||||||
### Core Components
|
|
||||||
1. **FormBuilderView (`views/FormBuilderView.vue`)**:
|
|
||||||
* **Role**: Smart container / Data fetcher.
|
|
||||||
* **Responsibility**: Fetches form data from API (`GET /api/admin/forms/{alias}`), handles loading states, and passes data to `FormBuilder`.
|
|
||||||
* **Contract**: Expects API response `{ data: { schema: Array, is_custom: Boolean, ... } }`.
|
|
||||||
|
|
||||||
2. **FormBuilder (`components/FormBuilder/FormBuilder.vue`)**:
|
|
||||||
* **Role**: Main Orchestrator / State Owner.
|
|
||||||
* **Responsibility**: Manages `v-model` (schema), mode switching (Visual/Code/Preview), and provides state to children.
|
|
||||||
* **State Management**: Uses `defineModel` for `formFields` (schema) and `isCustom` (mode flag). Uses `provide('formFields')` and `provide('selectedFieldId')` for deep dependency injection.
|
|
||||||
* **Modes**:
|
|
||||||
* **Visual**: Drag-and-drop interface using `vuedraggable`.
|
|
||||||
* **Code**: Direct JSON editing of the FormKit schema. Sets `isCustom = true`.
|
|
||||||
* **Preview**: Renders the current schema using `FormKit`.
|
|
||||||
|
|
||||||
3. **FormCanvas (`components/FormBuilder/FormCanvas.vue`)**:
|
|
||||||
* **Role**: Visual Editor Surface.
|
|
||||||
* **Responsibility**: Renders the draggable list of fields.
|
|
||||||
* **Implementation**: Uses `vuedraggable` bound to `formFields`.
|
|
||||||
* **UX**: Implements "Ghost" and "Drag" classes for visual feedback. Handles selection logic.
|
|
||||||
|
|
||||||
4. **FieldsPanel (`components/FormBuilder/FieldsPanel.vue`)**:
|
|
||||||
* **Role**: Component Palette.
|
|
||||||
* **Responsibility**: Source of truth for available field types.
|
|
||||||
* **Implementation**: Uses `vuedraggable` with `pull: 'clone', put: false` to spawn new fields.
|
|
||||||
|
|
||||||
5. **FieldSettings (`components/FormBuilder/FieldSettings.vue`)**:
|
|
||||||
* **Role**: Property Editor.
|
|
||||||
* **Responsibility**: Edits properties of the `selectedFieldId`.
|
|
||||||
* **Constraint**: Must use PrimeVue components for all inputs.
|
|
||||||
|
|
||||||
## Data Flow & Invariants
|
|
||||||
1. **Schema Authority**: The FormKit Schema JSON is the single source of truth. There is no "internal model" separate from the schema.
|
|
||||||
2. **Reactivity**:
|
|
||||||
* `formFields` is an Array of Objects.
|
|
||||||
* Mutations must preserve reactivity. When using `v-model` or `provide/inject`, ensure array methods (splice, push, filter) are used correctly or replace the entire array reference if needed to trigger watchers.
|
|
||||||
* **Immutability**: `useFormFields` composable uses immutable patterns (returning new array references) to ensure `defineModel` in parent detects changes.
|
|
||||||
3. **Mode Logic**:
|
|
||||||
* Switching to **Code** mode sets `isCustom = true`.
|
|
||||||
* Switching to **Visual** mode sets `isCustom = false`.
|
|
||||||
* **Safety**: Switching modes triggers JSON validation. Invalid JSON prevents mode switch.
|
|
||||||
4. **Drag and Drop**:
|
|
||||||
* Powered by `vuedraggable` (Sortable.js).
|
|
||||||
* **Clone Logic**: `FieldsPanel` clones from `availableFields`. `FormCanvas` receives the clone.
|
|
||||||
* **ID Generation**: Unique IDs are generated upon cloning/addition to ensure key stability.
|
|
||||||
|
|
||||||
## Naming & Conventions
|
|
||||||
* **Tailwind**: Use `tw:` prefix for all utility classes (e.g., `tw:flex`, `tw:p-4`).
|
|
||||||
* **Components**: PrimeVue components are the standard UI kit (Button, Panel, InputText, etc.).
|
|
||||||
* **Icons**: FontAwesome (`fa fa-*`).
|
|
||||||
* **Files**: PascalCase for components (`FormBuilder.vue`), camelCase for logic (`useFormFields.js`).
|
|
||||||
|
|
||||||
## Integration Rules
|
|
||||||
* **Backend**: The backend stores the JSON blob directly. `FormBuilder` does not transform data before save; it emits the raw schema.
|
|
||||||
* **API**: `useFormsStore` handles API communication.
|
|
||||||
|
|
||||||
## Pitfalls & Warnings
|
|
||||||
* **vuedraggable vs @formkit/drag-and-drop**: We strictly use `vuedraggable`. Do not re-introduce `@formkit/drag-and-drop`.
|
|
||||||
* **Watchers**: Avoid `watch` where `computed` or event handlers suffice, to prevent infinite loops in bidirectional data flow.
|
|
||||||
* **Tailwind Config**: Do not use `@apply` with `tw:` prefixed classes in `<style>` blocks; standard CSS properties should be used if custom classes are needed.
|
|
||||||
|
|
||||||
## Future Modifications
|
|
||||||
* **Adding Fields**: Update `constants/availableFields.js` and ensure `utils/fieldHelpers.js` supports the new type.
|
|
||||||
* **Validation**: FormKit validation rules string (e.g., "required|email") is edited as a raw string in `FieldSettings`. Complex validation builders would require a new UI component.
|
|
||||||
|
|
||||||
@@ -1,332 +0,0 @@
|
|||||||
# JavaScript/TypeScript Code Style Rules
|
|
||||||
|
|
||||||
## JavaScript Version
|
|
||||||
|
|
||||||
- ES2020+ features
|
|
||||||
- Modern async/await
|
|
||||||
- Optional chaining (`?.`)
|
|
||||||
- Nullish coalescing (`??`)
|
|
||||||
- Template literals
|
|
||||||
|
|
||||||
## Code Style
|
|
||||||
|
|
||||||
### Variable Declarations
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй const по умолчанию
|
|
||||||
const customers = [];
|
|
||||||
const totalRecords = 0;
|
|
||||||
|
|
||||||
// ✅ let только когда нужно переназначение
|
|
||||||
let currentPage = 1;
|
|
||||||
currentPage = 2;
|
|
||||||
|
|
||||||
// ❌ Не используй var
|
|
||||||
var oldVariable = 'bad';
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arrow Functions
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Предпочтительно для коротких функций
|
|
||||||
const filtered = items.filter(item => item.isActive);
|
|
||||||
|
|
||||||
// ✅ Для методов объектов
|
|
||||||
const api = {
|
|
||||||
get: async (url) => {
|
|
||||||
return await fetch(url);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ✅ Для сложной логики - обычные функции
|
|
||||||
function complexCalculation(data) {
|
|
||||||
// много строк кода
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Template Literals
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Предпочтительно
|
|
||||||
const message = `User ${userId} not found`;
|
|
||||||
const url = `${baseUrl}/api/${endpoint}`;
|
|
||||||
|
|
||||||
// ❌ Не используй конкатенацию
|
|
||||||
const message = 'User ' + userId + ' not found';
|
|
||||||
```
|
|
||||||
|
|
||||||
### Optional Chaining
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй optional chaining
|
|
||||||
const name = user?.profile?.name;
|
|
||||||
const count = data?.items?.length ?? 0;
|
|
||||||
|
|
||||||
// ❌ Избегай длинных проверок
|
|
||||||
const name = user && user.profile && user.profile.name;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nullish Coalescing
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй ?? для значений по умолчанию
|
|
||||||
const page = params.page ?? 1;
|
|
||||||
const name = user.name ?? 'Unknown';
|
|
||||||
|
|
||||||
// ❌ Не используй || для чисел/булевых
|
|
||||||
const page = params.page || 1; // 0 будет заменено на 1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Destructuring
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй деструктуризацию
|
|
||||||
const { data, totalRecords } = response.data;
|
|
||||||
const [first, second] = items;
|
|
||||||
|
|
||||||
// ✅ В параметрах функций
|
|
||||||
function processUser({ id, name, email }) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ С значениями по умолчанию
|
|
||||||
const { page = 1, limit = 20 } = params;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Async/Await
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Предпочтительно
|
|
||||||
async function loadCustomers() {
|
|
||||||
try {
|
|
||||||
const response = await apiGet('getCustomers', params);
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ❌ Избегай .then() цепочек
|
|
||||||
function loadCustomers() {
|
|
||||||
return apiGet('getCustomers', params)
|
|
||||||
.then(response => response.data)
|
|
||||||
.catch(error => console.error(error));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Vue.js 3 Composition API
|
|
||||||
|
|
||||||
### Script Setup
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Используй <script setup>
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
import { apiGet } from '@/utils/http.js';
|
|
||||||
|
|
||||||
const customers = ref([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
const totalRecords = computed(() => customers.value.length);
|
|
||||||
|
|
||||||
async function loadCustomers() {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const result = await apiGet('getCustomers');
|
|
||||||
customers.value = result.data || [];
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadCustomers();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Reactive State
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй ref для примитивов
|
|
||||||
const count = ref(0);
|
|
||||||
const name = ref('');
|
|
||||||
|
|
||||||
// ✅ Используй reactive для объектов
|
|
||||||
import { reactive } from 'vue';
|
|
||||||
const state = reactive({
|
|
||||||
customers: [],
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// ✅ Или ref для объектов (предпочтительно)
|
|
||||||
const state = ref({
|
|
||||||
customers: [],
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Computed Properties
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй computed для производных значений
|
|
||||||
const filteredCustomers = computed(() => {
|
|
||||||
return customers.value.filter(c => c.isActive);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ❌ Не используй методы для вычислений
|
|
||||||
function filteredCustomers() {
|
|
||||||
return customers.value.filter(c => c.isActive);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Props
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Определяй props с типами
|
|
||||||
const props = defineProps({
|
|
||||||
customerId: {
|
|
||||||
type: Number,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
showDetails: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Emits
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Определяй emits
|
|
||||||
const emit = defineEmits(['update', 'delete']);
|
|
||||||
|
|
||||||
function handleUpdate() {
|
|
||||||
emit('update', data);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Pinia Stores
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй setup stores
|
|
||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref, computed } from 'vue';
|
|
||||||
|
|
||||||
export const useCustomersStore = defineStore('customers', () => {
|
|
||||||
const customers = ref([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
const totalRecords = computed(() => customers.value.length);
|
|
||||||
|
|
||||||
async function loadCustomers() {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const result = await apiGet('getCustomers');
|
|
||||||
customers.value = result.data || [];
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
customers,
|
|
||||||
loading,
|
|
||||||
totalRecords,
|
|
||||||
loadCustomers
|
|
||||||
};
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Всегда обрабатывай ошибки
|
|
||||||
async function loadData() {
|
|
||||||
try {
|
|
||||||
const result = await apiGet('endpoint');
|
|
||||||
if (result.success) {
|
|
||||||
return result.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(result.error);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load data:', error);
|
|
||||||
toast.error('Не удалось загрузить данные');
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Naming Conventions
|
|
||||||
|
|
||||||
### Variables and Functions
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ camelCase
|
|
||||||
const customerData = {};
|
|
||||||
const totalRecords = 0;
|
|
||||||
function loadCustomers() {}
|
|
||||||
|
|
||||||
// ✅ Константы UPPER_SNAKE_CASE
|
|
||||||
const MAX_RETRIES = 3;
|
|
||||||
const API_BASE_URL = '/api';
|
|
||||||
```
|
|
||||||
|
|
||||||
### Components
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<!-- ✅ PascalCase для компонентов -->
|
|
||||||
<CustomerCard />
|
|
||||||
<ProductsList />
|
|
||||||
```
|
|
||||||
|
|
||||||
### Files
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ kebab-case для файлов
|
|
||||||
// customers-view.vue
|
|
||||||
// http-utils.js
|
|
||||||
// customer-service.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## Imports
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Группируй импорты
|
|
||||||
// 1. Vue core
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
|
|
||||||
// 2. Third-party
|
|
||||||
import { apiGet } from '@/utils/http.js';
|
|
||||||
import { useToast } from 'primevue';
|
|
||||||
|
|
||||||
// 3. Local components
|
|
||||||
import CustomerCard from '@/components/CustomerCard.vue';
|
|
||||||
|
|
||||||
// 4. Types (если TypeScript)
|
|
||||||
import type { Customer } from '@/types';
|
|
||||||
```
|
|
||||||
|
|
||||||
## TypeScript (где используется)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ✅ Используй типы
|
|
||||||
interface Customer {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
email?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCustomer(id: number): Promise<Customer> {
|
|
||||||
return apiGet(`customers/${id}`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
# PHP Code Style Rules
|
|
||||||
|
|
||||||
## PHP Version
|
|
||||||
|
|
||||||
Проект поддерживает PHP 7.4+
|
|
||||||
|
|
||||||
## PSR Standards
|
|
||||||
|
|
||||||
- **PSR-1**: Basic Coding Standard
|
|
||||||
- **PSR-4**: Autoloading Standard
|
|
||||||
- **PSR-12**: Extended Coding Style
|
|
||||||
|
|
||||||
## Code Style
|
|
||||||
|
|
||||||
### Type Declarations
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Правильно - строгая типизация
|
|
||||||
public function getCustomers(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
$id = (int) $request->get('id');
|
|
||||||
return new JsonResponse(['data' => $customers]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ❌ Неправильно - без типов
|
|
||||||
public function getCustomers($request)
|
|
||||||
{
|
|
||||||
return ['data' => $customers];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nullable Types
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Правильно
|
|
||||||
public function findById(?int $id): ?array
|
|
||||||
{
|
|
||||||
if ($id === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return $this->query->where('id', '=', $id)->firstOrNull();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Strict Types
|
|
||||||
|
|
||||||
Всегда используй `declare(strict_types=1);`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Services;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Array Syntax
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Предпочтительно - короткий синтаксис
|
|
||||||
$array = ['key' => 'value'];
|
|
||||||
|
|
||||||
// ❌ Не использовать
|
|
||||||
$array = array('key' => 'value');
|
|
||||||
```
|
|
||||||
|
|
||||||
### String Interpolation
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Предпочтительно
|
|
||||||
$message = "User {$userId} not found";
|
|
||||||
|
|
||||||
// ✅ Альтернатива
|
|
||||||
$message = sprintf('User %d not found', $userId);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arrow Functions (PHP 7.4+)
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Для простых операций
|
|
||||||
$filtered = array_filter($items, fn($item) => $item->isActive());
|
|
||||||
|
|
||||||
// ❌ Для сложной логики - используй обычные функции
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nullsafe Operator (PHP 8.0+)
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Для PHP 7.4
|
|
||||||
$name = $user && $user->profile ? $user->profile->name : null;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Naming Conventions
|
|
||||||
|
|
||||||
### Classes
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ PascalCase
|
|
||||||
class TelegramCustomerService {}
|
|
||||||
class UserRepository {}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Methods
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ camelCase
|
|
||||||
public function getCustomers(): array {}
|
|
||||||
public function saveOrUpdate(array $data): array {}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Variables
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ camelCase
|
|
||||||
$customerData = [];
|
|
||||||
$totalRecords = 0;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Constants
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ UPPER_SNAKE_CASE
|
|
||||||
private const MAX_RETRIES = 3;
|
|
||||||
public const DEFAULT_PAGE_SIZE = 20;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Private Properties
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ camelCase с модификатором доступа
|
|
||||||
private string $tableName;
|
|
||||||
private Builder $builder;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
### PHPDoc
|
|
||||||
|
|
||||||
```php
|
|
||||||
/**
|
|
||||||
* @throws ValidationException Если параметры невалидны
|
|
||||||
*/
|
|
||||||
public function getCustomers(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Inline Comments
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Полезные комментарии
|
|
||||||
// Применяем фильтры для подсчета общего количества записей
|
|
||||||
$countQuery = $this->buildCountQuery($filters);
|
|
||||||
|
|
||||||
// ❌ Очевидные комментарии
|
|
||||||
// Получаем данные
|
|
||||||
$data = $this->getData();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
### Exceptions
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Специфичные исключения
|
|
||||||
if (!$userId) {
|
|
||||||
throw new InvalidArgumentException('User ID is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Логирование
|
|
||||||
try {
|
|
||||||
$result = $this->service->process();
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->logger->error('Processing failed', [
|
|
||||||
'exception' => $e,
|
|
||||||
'context' => $context,
|
|
||||||
]);
|
|
||||||
throw new ProcessingException('Failed to process', 0, $e);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Query Builder Usage
|
|
||||||
|
|
||||||
### Always Use Query Builder
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Правильно
|
|
||||||
$customers = $this->builder->newQuery()
|
|
||||||
->select(['id', 'name', 'email'])
|
|
||||||
->from('telecart_customers')
|
|
||||||
->where('status', '=', 'active')
|
|
||||||
->orderBy('created_at', 'DESC')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
// В крайних случаях можно использовать прямые SQL
|
|
||||||
$result = $this->database->query("SELECT * FROM telecart_customers");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parameter Binding
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Query Builder автоматически биндит параметры
|
|
||||||
$query->where('name', 'LIKE', "%{$search}%");
|
|
||||||
|
|
||||||
// ❌ Никогда не конкатенируй значения в SQL, избегай SQL Injection.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Array Access
|
|
||||||
|
|
||||||
### Safe Array Access
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Используй Arr::get()
|
|
||||||
use Openguru\OpenCartFramework\Support\Arr;
|
|
||||||
|
|
||||||
$value = Arr::get($data, 'key', 'default');
|
|
||||||
|
|
||||||
// ❌ Небезопасно
|
|
||||||
$value = $data['key']; // может вызвать ошибку
|
|
||||||
```
|
|
||||||
|
|
||||||
## Return Types
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Всегда указывай return type
|
|
||||||
public function getData(): array {}
|
|
||||||
public function findById(int $id): ?array {}
|
|
||||||
public function process(): void {}
|
|
||||||
|
|
||||||
// ❌ Без типа
|
|
||||||
public function getData() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Visibility Modifiers
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Всегда указывай модификатор доступа
|
|
||||||
private string $tableName;
|
|
||||||
protected Builder $builder;
|
|
||||||
public function getData(): array {}
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,370 +0,0 @@
|
|||||||
# Vue.js 3 Rules
|
|
||||||
|
|
||||||
## Component Structure
|
|
||||||
|
|
||||||
### Template
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- ✅ Логическая структура -->
|
|
||||||
<div class="container">
|
|
||||||
<header>
|
|
||||||
<h2>{{ title }}</h2>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<DataTable :value="items" />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<Button @click="handleSave">Save</Button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Script Setup
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Всегда используй <script setup>
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
import DataTable from 'primevue/datatable';
|
|
||||||
import { apiGet } from '@/utils/http.js';
|
|
||||||
|
|
||||||
// Props
|
|
||||||
const props = defineProps({
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Emits
|
|
||||||
const emit = defineEmits(['update', 'delete']);
|
|
||||||
|
|
||||||
// State
|
|
||||||
const items = ref([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
// Computed
|
|
||||||
const totalItems = computed(() => items.value.length);
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
async function loadItems() {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const result = await apiGet('getItems');
|
|
||||||
items.value = result.data || [];
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lifecycle
|
|
||||||
onMounted(() => {
|
|
||||||
loadItems();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Styles
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<style scoped>
|
|
||||||
/* ✅ Используй scoped стили */
|
|
||||||
.container {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ✅ Используй :deep() для доступа к дочерним компонентам */
|
|
||||||
:deep(.p-datatable) {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Component Naming
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<!-- ✅ PascalCase для компонентов -->
|
|
||||||
<CustomerCard />
|
|
||||||
<ProductsList />
|
|
||||||
<OrderDetails />
|
|
||||||
|
|
||||||
<!-- ✅ kebab-case в шаблоне тоже работает -->
|
|
||||||
<customer-card />
|
|
||||||
```
|
|
||||||
|
|
||||||
## Props
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Всегда определяй типы и валидацию
|
|
||||||
const props = defineProps({
|
|
||||||
customerId: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
validator: (value) => value > 0
|
|
||||||
},
|
|
||||||
showDetails: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Emits
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Определяй emits с типами
|
|
||||||
const emit = defineEmits<{
|
|
||||||
update: [id: number, data: object];
|
|
||||||
delete: [id: number];
|
|
||||||
cancel: [];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
// ✅ Или с валидацией
|
|
||||||
const emit = defineEmits({
|
|
||||||
update: (id: number, data: object) => {
|
|
||||||
if (id > 0 && typeof data === 'object') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
console.warn('Invalid emit arguments');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Reactive State
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ ref для примитивов
|
|
||||||
const count = ref(0);
|
|
||||||
const name = ref('');
|
|
||||||
|
|
||||||
// ✅ ref для объектов (предпочтительно)
|
|
||||||
const customer = ref({
|
|
||||||
id: null,
|
|
||||||
name: '',
|
|
||||||
email: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
// ✅ reactive только если нужно
|
|
||||||
import { reactive } from 'vue';
|
|
||||||
const state = reactive({
|
|
||||||
items: [],
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Computed Properties
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Используй computed для производных значений
|
|
||||||
const filteredItems = computed(() => {
|
|
||||||
return items.value.filter(item => item.isActive);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ✅ Computed с getter/setter
|
|
||||||
const fullName = computed({
|
|
||||||
get: () => `${firstName.value} ${lastName.value}`,
|
|
||||||
set: (value) => {
|
|
||||||
const parts = value.split(' ');
|
|
||||||
firstName.value = parts[0];
|
|
||||||
lastName.value = parts[1];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Event Handlers
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- ✅ Используй kebab-case для событий -->
|
|
||||||
<Button @click="handleClick" />
|
|
||||||
<Input @input="handleInput" />
|
|
||||||
<Form @submit.prevent="handleSubmit" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
// ✅ Именуй обработчики с префиксом handle
|
|
||||||
function handleClick() {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleInput(event) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSubmit() {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Conditional Rendering
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- ✅ Используй v-if для условного рендеринга -->
|
|
||||||
<div v-if="loading">
|
|
||||||
<LoadingSpinner />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ✅ v-show для частых переключений -->
|
|
||||||
<div v-show="hasItems">
|
|
||||||
<ItemsList :items="items" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ✅ v-else для альтернатив -->
|
|
||||||
<div v-else>
|
|
||||||
<EmptyState />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Lists
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- ✅ Всегда используй :key -->
|
|
||||||
<div v-for="item in items" :key="item.id">
|
|
||||||
{{ item.name }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ✅ Для индексов -->
|
|
||||||
<div v-for="(item, index) in items" :key="`item-${index}`">
|
|
||||||
{{ item.name }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Form Handling
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<form @submit.prevent="handleSubmit">
|
|
||||||
<!-- ✅ Используй v-model -->
|
|
||||||
<InputText v-model="form.name" />
|
|
||||||
<Textarea v-model="form.description" />
|
|
||||||
|
|
||||||
<!-- ✅ Для кастомных компонентов -->
|
|
||||||
<CustomInput v-model="form.email" />
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const form = ref({
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
email: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleSubmit() {
|
|
||||||
// Валидация и отправка
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## PrimeVue Components
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- ✅ Используй PrimeVue компоненты в админке -->
|
|
||||||
<DataTable
|
|
||||||
:value="customers"
|
|
||||||
:loading="loading"
|
|
||||||
paginator
|
|
||||||
:rows="20"
|
|
||||||
@page="onPage"
|
|
||||||
>
|
|
||||||
<Column field="name" header="Name" sortable />
|
|
||||||
</DataTable>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Styling
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<style scoped>
|
|
||||||
/* ✅ Используй scoped -->
|
|
||||||
.container {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ✅ :deep() для дочерних компонентов */
|
|
||||||
:deep(.p-datatable) {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ✅ :slotted() для слотов */
|
|
||||||
:slotted(.header) {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Composition Functions
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Выноси сложную логику в composables
|
|
||||||
import { useCustomers } from '@/composables/useCustomers.js';
|
|
||||||
|
|
||||||
const {
|
|
||||||
customers,
|
|
||||||
loading,
|
|
||||||
loadCustomers,
|
|
||||||
totalRecords
|
|
||||||
} = useCustomers();
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadCustomers();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
import { useToast } from 'primevue';
|
|
||||||
|
|
||||||
const toast = useToast();
|
|
||||||
|
|
||||||
async function loadData() {
|
|
||||||
try {
|
|
||||||
const result = await apiGet('endpoint');
|
|
||||||
if (result.success) {
|
|
||||||
data.value = result.data;
|
|
||||||
} else {
|
|
||||||
toast.add({
|
|
||||||
severity: 'error',
|
|
||||||
summary: 'Ошибка',
|
|
||||||
detail: result.error
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
toast.add({
|
|
||||||
severity: 'error',
|
|
||||||
summary: 'Ошибка',
|
|
||||||
detail: 'Не удалось загрузить данные'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
28
CHANGELOG.md
28
CHANGELOG.md
@@ -4,6 +4,34 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
<!--- END HEADER -->
|
<!--- END HEADER -->
|
||||||
|
|
||||||
|
## [2.3.0](https://github.com/telecart-labs/telecart/compare/v2.2.2...v2.3.0) (2026-05-25)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
|
||||||
|
##### Admin
|
||||||
|
|
||||||
|
* Telegram settings improvements (#63) ([80c246](https://github.com/telecart-labs/telecart/commit/80c2467b4a7c2cb1c4101c483fe3ac31a5ad0cf1))
|
||||||
|
|
||||||
|
##### Ocmod
|
||||||
|
|
||||||
|
* Add TeleCart admin sidebar menu and local dev setup (#57) ([28c8ce](https://github.com/telecart-labs/telecart/commit/28c8ce3b6841ca1d4cc98c8e7bb275e1ad3adcf3))
|
||||||
|
|
||||||
|
##### Spa
|
||||||
|
|
||||||
|
* Increase max page limit to product lists (#88) ([a3f54a](https://github.com/telecart-labs/telecart/commit/a3f54a87dc8799b3355b294fc155c3cf9d22c3eb))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
|
||||||
|
##### Spa
|
||||||
|
|
||||||
|
* Handle empty category and products in carousel block ([d43564](https://github.com/telecart-labs/telecart/commit/d43564481d1e35ea8bb92310d20bff2989ca6ea7))
|
||||||
|
* Visual fixes for SPA (#106) ([c24e30](https://github.com/telecart-labs/telecart/commit/c24e30f14cef8a4b6956aaa2ada5dc9b12fbb9cb))
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [2.2.2](https://github.com/telecart-labs/telecart/compare/v2.2.1...v2.2.2) (2026-03-15)
|
## [2.2.2](https://github.com/telecart-labs/telecart/compare/v2.2.1...v2.2.2) (2026-03-15)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
147
CLAUDE.md
Normal file
147
CLAUDE.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
TeleCart — коммерческий модуль для OpenCart, превращающий магазин в Telegram Mini App. Это монорепозиторий из трёх
|
||||||
|
частей:
|
||||||
|
|
||||||
|
- **PHP-модуль OpenCart** — `module/oc_telegram_shop/upload/` (вся бизнес-логика и собственный фреймворк).
|
||||||
|
- **SPA** — `frontend/spa/` (Telegram Mini App, витрина для покупателей).
|
||||||
|
- **Admin** — `frontend/admin/` (панель управления настройками модуля).
|
||||||
|
|
||||||
|
`src/` в корне — это распакованная установка OpenCart (генерируется скриптами, **не редактировать**; код модуля
|
||||||
|
«линкуется» в неё через `make link`).
|
||||||
|
|
||||||
|
## Документация по фичам (`docs/features/`)
|
||||||
|
|
||||||
|
Контекстная документация хранится в `docs/features/` — по одной поддиректории на фичу, внутри индексный
|
||||||
|
`CLAUDE.md`. Вложенные `CLAUDE.md` Claude Code подхватывает автоматически при чтении файлов из их директории,
|
||||||
|
поэтому индекс фичи читается «по требованию», когда работа касается этой фичи.
|
||||||
|
|
||||||
|
Перед тем как трогать незнакомую часть системы — загляни в её папку в `docs/features/`. Конвенции, шаблон новой
|
||||||
|
фичи и **реестр всех фич с прямыми путями** — в [`docs/features/CLAUDE.md`](docs/features/CLAUDE.md). Шаблон для
|
||||||
|
новой фичи — `docs/features/_TEMPLATE.md`. При изменении фичи поддерживай её доку в актуальном состоянии (код —
|
||||||
|
источник истины; при расхождении правь доку).
|
||||||
|
|
||||||
|
## Где живёт код модуля
|
||||||
|
|
||||||
|
Сердце проекта — `module/oc_telegram_shop/upload/oc_telegram_shop/` (далее «корень модуля»). Его структура:
|
||||||
|
|
||||||
|
| Каталог | Namespace | Назначение |
|
||||||
|
|------------------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `framework/` | `Openguru\OpenCartFramework\` | Собственный Laravel-подобный фреймворк: Container (DI), Router, QueryBuilder, Cache, Migrations, Telegram, Http, Translator |
|
||||||
|
| `src/` | `App\` | API витрины/SPA (catalog). Handlers, Services, Models, DTO, Filters, ServiceProviders |
|
||||||
|
| `bastion/` | `Bastion\` | API админ-панели. Handlers, Services, Tasks, ScheduledTasks |
|
||||||
|
| `console/` | `Console\` | CLI-команды (Symfony Console), точка входа `upload/cli.php` |
|
||||||
|
| `database/migrations/` | — | Миграции БД |
|
||||||
|
| `configs/` | — | `app.php`, `maintenance.php`, `schedule.php` |
|
||||||
|
| `tests/` | `Tests\` | `Unit/`, `Integration/`, `Telegram/` |
|
||||||
|
|
||||||
|
**Маршрутизация.** Запросы приходят через action-параметр и резолвятся по двум картам:
|
||||||
|
|
||||||
|
- `src/routes.php` — действия SPA (`products`, `getCart`, `checkout`, `webhook`, …)
|
||||||
|
- `bastion/routes.php` — действия админки (`getSettingsForm`, `getTelegramCustomers`, `getDashboardStats`, …)
|
||||||
|
|
||||||
|
Каждая запись — `'actionName' => [HandlerClass::class, 'method']`.
|
||||||
|
|
||||||
|
**Точки входа OpenCart** (тонкие обёртки, грузят autoload и поднимают `App\ApplicationFactory`):
|
||||||
|
|
||||||
|
- `module/.../upload/catalog/controller/extension/tgshop/handle.php` — SPA API
|
||||||
|
- `module/.../upload/admin/controller/extension/module/tgshop.php` — админка
|
||||||
|
|
||||||
|
В продакшене модуль собирается в `oc_telegram_shop.phar` и подключается из `system/library/oc_telegram_shop/`; в dev —
|
||||||
|
из исходников через `vendor/autoload.php`.
|
||||||
|
|
||||||
|
## Архитектурные правила (бэкенд)
|
||||||
|
|
||||||
|
Паттерн — MVC-L поверх собственного фреймворка. Соблюдай строго:
|
||||||
|
|
||||||
|
- **Совместимость с PHP 7.4.** Минимальные требования OpenCart и модуля — PHP 7.4 (`composer.json`: `^7.4`). Не используй синтаксис и функции PHP 8.0+ (named arguments, `match`, constructor property promotion, `enum`, `readonly`, nullsafe `?->`, union types и т.п.).
|
||||||
|
- **Dependency Injection через конструктор.** Никогда не создавай зависимости через `new` внутри классов — внедряй их.
|
||||||
|
Регистрация сервисов идёт в Service Providers (`src/ServiceProviders/`) через `$this->app->singleton(...)`.
|
||||||
|
- **Query Builder вместо сырого SQL.** Используй `$this->builder->newQuery()->from(...)->where(...)->get()`. Прямой
|
||||||
|
SQL — только в крайних случаях; никогда не конкатенируй значения в запрос (SQL injection).
|
||||||
|
- **Слои:** Handler (валидация + вызов сервиса + `JsonResponse`) → Service (бизнес-логика) → Model (доступ к данным). Не
|
||||||
|
смешивай.
|
||||||
|
- **Строгая типизация:** `declare(strict_types=1)`, типы аргументов и возвращаемых значений, nullable где нужно. PSR-12.
|
||||||
|
- **Безопасный доступ к массивам:** `Arr::get($data, 'key', $default)` вместо `$data['key']`.
|
||||||
|
- **Таблицы:** префикс OpenCart (`oc_`) + собственные таблицы модуля.
|
||||||
|
|
||||||
|
## Архитектура фронтенда
|
||||||
|
|
||||||
|
Оба приложения — Vue 3, **только `<script setup>` + Composition API**, Pinia (setup-stores), Vue Router.
|
||||||
|
|
||||||
|
- **SPA** (`frontend/spa/`): Tailwind CSS, FormKit, Swiper, `ofetch`. Сборка →
|
||||||
|
`module/.../upload/image/catalog/tgshopspa/`. Тесты — Vitest.
|
||||||
|
- **Admin** (`frontend/admin/`): PrimeVue (основной UI-кит), Tailwind/DaisyUI, axios, CodeMirror, `vuedraggable`.
|
||||||
|
Сборка → `module/.../upload/admin/view/javascript/telecart/`.
|
||||||
|
|
||||||
|
Конвенции: компоненты — PascalCase, файлы логики — camelCase; обработчики событий с префиксом `handle`; `computed` для
|
||||||
|
производных значений; `??` (не `||`) для дефолтов чисел/булевых; сложную логику выносить в composables. Ошибки API
|
||||||
|
ловить и показывать через toast (admin: `useToast` из PrimeVue).
|
||||||
|
|
||||||
|
**FormBuilder** (admin) — отдельная подсистема визуального конструктора форм. Единственный источник истины — FormKit
|
||||||
|
Schema JSON (без промежуточных DTO). Drag-and-drop строго на `vuedraggable` (не `@formkit/drag-and-drop`).
|
||||||
|
Tailwind-классы там идут с префиксом `tw:`.
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
|
||||||
|
Все `make`-цели выполняются **внутри Docker-контейнера `web`** — нужен запущенный `docker compose`.
|
||||||
|
|
||||||
|
**Старт / окружение:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make setup # первичная установка: скачать OpenCart, установить, поднять, link
|
||||||
|
make start # поднять контейнеры
|
||||||
|
make dev # link + параллельно vite dev для SPA и Admin
|
||||||
|
make dev-spa # только SPA (vite)
|
||||||
|
make dev-admin # только Admin (vite)
|
||||||
|
make link # «слинковать» код модуля в установку OpenCart (после правок структуры)
|
||||||
|
make ssh # шелл в контейнер в каталоге модуля
|
||||||
|
```
|
||||||
|
|
||||||
|
**Тесты (PHP, PHPUnit в контейнере):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test # все тесты (--testdox)
|
||||||
|
make test-unit # tests/Unit
|
||||||
|
make test-integration # tests/Integration
|
||||||
|
make test-telegram # tests/Telegram
|
||||||
|
make test-coverage # HTML-покрытие в coverage/
|
||||||
|
```
|
||||||
|
|
||||||
|
Запуск одного теста — через ssh в контейнер:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make ssh
|
||||||
|
./vendor/bin/phpunit --filter testMethodName tests/Unit/SomeTest.php
|
||||||
|
```
|
||||||
|
|
||||||
|
**Тесты фронтенда (SPA):** `cd frontend/spa && npm run test` (Vitest), `npm run test:run` для однократного прогона.
|
||||||
|
|
||||||
|
**Линтеры / статический анализ:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make lint # PHPStan (level 1) по src, bastion, framework, console
|
||||||
|
make phpcs # PHP_CodeSniffer PSR-12 (проверка)
|
||||||
|
make phpcbf # PHP_CodeSniffer PSR-12 (автофикс)
|
||||||
|
```
|
||||||
|
|
||||||
|
Admin-фронтенд: `cd frontend/admin && npm run lint` (oxlint + eslint), `npm run format` (prettier).
|
||||||
|
|
||||||
|
**Сборка / релиз:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make phar # собрать oc_telegram_shop.phar (через docker/build.dockerfile)
|
||||||
|
make ocmod # применить OCMOD-модификации
|
||||||
|
make cli ARGS="..." # запустить console-команду модуля
|
||||||
|
make changelog # сгенерировать CHANGELOG из conventional commits
|
||||||
|
make release # changelog + commit релиза
|
||||||
|
```
|
||||||
|
|
||||||
|
## Коммиты
|
||||||
|
|
||||||
|
Используются **Conventional Commits** (`feat:`, `fix:`, `chore:`, `build(deps):` …) — CHANGELOG и версии генерируются
|
||||||
|
автоматически. **Не делать `git commit`/`git push` без явного указания пользователя.**
|
||||||
45
docs/features/CLAUDE.md
Normal file
45
docs/features/CLAUDE.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# docs/features — документация по фичам
|
||||||
|
|
||||||
|
Эта директория хранит описания **фич** проекта TeleCart: по одной поддиректории на фичу.
|
||||||
|
Цель — дать агенту (и человеку) возможность быстро восстановить контекст по конкретной
|
||||||
|
части системы, не перечитывая весь код: что фича делает, через какие точки входит, какие
|
||||||
|
файлы за неё отвечают и как данные текут по слоям.
|
||||||
|
|
||||||
|
Вложенные `CLAUDE.md` подхватываются Claude Code автоматически при чтении файлов из их
|
||||||
|
директории — поэтому индекс фичи читается «по требованию», когда работа касается этой фичи.
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
```
|
||||||
|
docs/features/
|
||||||
|
├── CLAUDE.md # этот файл: конвенции + реестр фич
|
||||||
|
├── _TEMPLATE.md # шаблон для новой фичи (копировать при описании)
|
||||||
|
└── <feature-slug>/
|
||||||
|
└── CLAUDE.md # индексный файл конкретной фичи
|
||||||
|
```
|
||||||
|
|
||||||
|
## Конвенции
|
||||||
|
|
||||||
|
- **Имя папки** — `kebab-case`, по сути фичи: `cart`, `telegram-webhook`, `form-builder`.
|
||||||
|
- **Один индекс на фичу** — `<feature-slug>/CLAUDE.md`, заполняется по `_TEMPLATE.md`.
|
||||||
|
- **Ссылки на код — обязательны.** Указывай реальные пути (`module/.../src/Handlers/CartHandler.php`)
|
||||||
|
и `action` из карт маршрутов. Код — источник истины; документация лишь навигирует к нему.
|
||||||
|
- **Не дублируй код.** Описывай назначение, поток и нюансы, а не построчный пересказ — он устареет.
|
||||||
|
- **Связывай фичи** через ссылки на их папки (например `[checkout](../checkout/CLAUDE.md)`).
|
||||||
|
- Если описанное в доке расходится с кодом — прав код; обнови доку.
|
||||||
|
|
||||||
|
## Как добавить фичу
|
||||||
|
|
||||||
|
1. `cp _TEMPLATE.md <feature-slug>/CLAUDE.md` (создав папку).
|
||||||
|
2. Заполни секции шаблона, опираясь на реальный код.
|
||||||
|
3. Добавь строку в реестр ниже.
|
||||||
|
|
||||||
|
## Реестр фич
|
||||||
|
|
||||||
|
| Фича | Путь | Краткое описание |
|
||||||
|
|----------------|--------------------------------------------------------|--------------------------------------------------------------------------|
|
||||||
|
| cart | [cart/CLAUDE.md](cart/CLAUDE.md) | Корзина: наполнение, просмотр, передача в оформление заказа |
|
||||||
|
| settings | [settings/CLAUDE.md](settings/CLAUDE.md) | Каркас настроек: форма в админке → хранение → чтение на backend |
|
||||||
|
| telecart-pulse | [telecart-pulse/CLAUDE.md](telecart-pulse/CLAUDE.md) | Связка с SaaS: события кампаний и heartbeat (телеметрия окружения) |
|
||||||
|
|
||||||
|
<!-- Новые фичи добавляй строкой в таблицу выше, по алфавиту. -->
|
||||||
38
docs/features/_TEMPLATE.md
Normal file
38
docs/features/_TEMPLATE.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# <Название фичи>
|
||||||
|
|
||||||
|
> Скопируй этот файл в `docs/features/<feature-slug>/CLAUDE.md` и заполни секции.
|
||||||
|
> Удали подсказки в кавычках после заполнения. Не оставляй пустых «TODO».
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
> 1–3 предложения: какую задачу решает фича и для кого (SPA-покупатель / админ / системное).
|
||||||
|
|
||||||
|
## Точки входа
|
||||||
|
|
||||||
|
> `action` из карт маршрутов и где они объявлены. Таблица:
|
||||||
|
|
||||||
|
| action | Метод | Где объявлен |
|
||||||
|
|-----------------|-------------------|-------------------------------------------|
|
||||||
|
| `exampleAction` | `Handler::method` | `src/routes.php` или `bastion/routes.php` |
|
||||||
|
|
||||||
|
## Ключевые файлы по слоям
|
||||||
|
|
||||||
|
> Путь от корня репозитория. Только то, что реально относится к фиче.
|
||||||
|
|
||||||
|
- **Handler:** `module/.../src/Handlers/XxxHandler.php` — валидация, вызов сервиса, `JsonResponse`.
|
||||||
|
- **Service:** `module/.../src/Services/XxxService.php` — бизнес-логика.
|
||||||
|
- **Model / данные:** `module/.../src/Models/Xxx.php` или таблицы БД.
|
||||||
|
- **Фронтенд:** `frontend/spa/...` или `frontend/admin/...` (store, view, компоненты).
|
||||||
|
- **Регистрация в DI:** `src/ServiceProviders/...` (если есть нестандартная привязка).
|
||||||
|
|
||||||
|
## Поток данных
|
||||||
|
|
||||||
|
> Кратко: запрос → слои → ответ. Можно списком шагов или стрелками.
|
||||||
|
|
||||||
|
## Связанные фичи
|
||||||
|
|
||||||
|
> Ссылки на соседние папки: `[checkout](../checkout/CLAUDE.md)`.
|
||||||
|
|
||||||
|
## Нюансы и подводные камни
|
||||||
|
|
||||||
|
> Неочевидное: ограничения PHP 7.4, особенности OpenCart-реестра, кэш, edge-cases, известные баги.
|
||||||
59
docs/features/cart/CLAUDE.md
Normal file
59
docs/features/cart/CLAUDE.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Cart — корзина
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Корзина покупателя в SPA (Telegram Mini App): отдаёт текущее содержимое корзины с
|
||||||
|
посчитанными ценами/итогами, принимает набор позиций из SPA и наполняет ими корзину
|
||||||
|
OpenCart перед оформлением заказа. Создание самого заказа — за пределами фичи (см.
|
||||||
|
[order](../order/CLAUDE.md), если описан).
|
||||||
|
|
||||||
|
## Точки входа
|
||||||
|
|
||||||
|
| action | Метод | Где объявлен |
|
||||||
|
|------------|-------------------------|------------------|
|
||||||
|
| `getCart` | `CartHandler::index` | `src/routes.php` |
|
||||||
|
| `checkout` | `CartHandler::checkout` | `src/routes.php` |
|
||||||
|
|
||||||
|
> `storeOrder` (`OrderHandler::store`) относится к оформлению заказа, не к корзине.
|
||||||
|
|
||||||
|
## Ключевые файлы по слоям
|
||||||
|
|
||||||
|
- **Handler:** `module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/CartHandler.php`
|
||||||
|
— `index()` возвращает содержимое корзины, `checkout()` принимает позиции из SPA
|
||||||
|
(`productId`, `quantity`, `options`) и кладёт их в корзину через `Cart\Cart::add()`.
|
||||||
|
- **Service:** `module/oc_telegram_shop/upload/oc_telegram_shop/src/Services/CartService.php`
|
||||||
|
— `getCart()` собирает данные через OpenCart-реестр (`OcRegistryDecorator`): язык
|
||||||
|
`checkout/cart`, проверки стока, веса, цен, форматирование через `Cart\Currency`.
|
||||||
|
- **OpenCart-зависимости:** `Cart\Cart`, `Cart\Currency` — нативные классы OpenCart,
|
||||||
|
внедряются через DI.
|
||||||
|
- **Фронтенд (SPA):**
|
||||||
|
- `frontend/spa/src/stores/CartStore.js` — Pinia-стор корзины.
|
||||||
|
- `frontend/spa/src/ShoppingCart.js` — клиентская логика корзины.
|
||||||
|
- `frontend/spa/src/views/Cart.vue` — экран корзины.
|
||||||
|
- `frontend/spa/src/components/CartButton.vue` — кнопка/индикатор.
|
||||||
|
|
||||||
|
## Поток данных
|
||||||
|
|
||||||
|
**Просмотр:** SPA → `action=getCart` → `CartHandler::index` → `CartService::getCart()`
|
||||||
|
читает корзину из OpenCart-реестра → ответ оборачивается `Utf8Cleaner::clean()` →
|
||||||
|
`JsonResponse { data }`.
|
||||||
|
|
||||||
|
**Передача в оформление:** SPA отправляет массив позиций → `action=checkout` →
|
||||||
|
`CartHandler::checkout` маппит `options` (берёт `product_option_value_id`) и для каждой
|
||||||
|
позиции вызывает `$cart->add(productId, quantity, options)` → возвращает очищенные позиции.
|
||||||
|
|
||||||
|
## Связанные фичи
|
||||||
|
|
||||||
|
- Оформление заказа (`OrderHandler::store` / `OrderCreateService`).
|
||||||
|
- Настройки (`SettingsService`) влияют на отображение цен/стока в `getCart`.
|
||||||
|
|
||||||
|
## Нюансы и подводные камни
|
||||||
|
|
||||||
|
- **PHP 7.4.** Без синтаксиса 8.0+ (см. корневой `CLAUDE.md`).
|
||||||
|
- **Весь вывод чистится `App\Support\Utf8Cleaner::clean()`** перед отдачей — невалидный
|
||||||
|
UTF-8 из товаров OpenCart ломает `json_encode`.
|
||||||
|
- **`getCart` зависит от состояния OpenCart-сессии и реестра** (сток, валюта, логин
|
||||||
|
покупателя, язык `checkout/cart`) — это не чистая функция, результат меняется от конфига
|
||||||
|
магазина (`config_stock_checkout`, `config_customer_price`, `config_cart_weight` и т.д.).
|
||||||
|
- **`checkout` мутирует корзину OpenCart** (`Cart\Cart::add`), а не локальное состояние —
|
||||||
|
он переносит выбор из SPA в нативную корзину OpenCart.
|
||||||
88
docs/features/settings/CLAUDE.md
Normal file
88
docs/features/settings/CLAUDE.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Настройки модуля
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Сквозная подсистема настроек модуля: форма в админ-панели, хранение значений и чтение их
|
||||||
|
на backend. Это **каркас**, в который встраиваются остальные фичи — когда фиче нужен свой
|
||||||
|
флаг/параметр, она добавляет поле в этот пайплайн, а не строит собственный механизм.
|
||||||
|
|
||||||
|
## Точки входа
|
||||||
|
|
||||||
|
| action | Метод | Где объявлен |
|
||||||
|
|--------------------|------------------------------------|---------------------|
|
||||||
|
| `getSettingsForm` | `SettingsHandler::getSettingsForm` | `bastion/routes.php` |
|
||||||
|
| `saveSettingsForm` | `SettingsHandler::saveSettingsForm`| `bastion/routes.php` |
|
||||||
|
| `resetCache` | `SettingsHandler::resetCache` | `bastion/routes.php` |
|
||||||
|
| `getSystemInfo` | `SettingsHandler::getSystemInfo` | `bastion/routes.php` |
|
||||||
|
|
||||||
|
## Ключевые файлы по слоям
|
||||||
|
|
||||||
|
- **Дефолты:** `module/oc_telegram_shop/upload/oc_telegram_shop/configs/app.php` — структура
|
||||||
|
всех настроек с дефолтными значениями (секции `app`, `telegram`, `metrics`, `store`,
|
||||||
|
`orders`, `texts`, `sliders`, `mainpage_blocks`, `pulse`, `cron`).
|
||||||
|
- **Handler (админка):** `module/.../bastion/Handlers/SettingsHandler.php` —
|
||||||
|
`getSettingsForm()` отдаёт срез конфига (`Arr::getWithKeys` по списку секций),
|
||||||
|
`saveSettingsForm()` сохраняет тот же срез.
|
||||||
|
- **Сохранение:** `module/.../bastion/Services/SettingsService.php` — `update()` пишет в
|
||||||
|
OpenCart-таблицу `oc_setting` через `model_setting_setting->editSetting('module_telecart',
|
||||||
|
['module_telecart_settings' => $data])`, синхронизирует статус модуля `module_tgshop` и
|
||||||
|
очищает кэш.
|
||||||
|
- **Чтение (backend):** `Openguru\OpenCartFramework\Config\Settings` — `get('a.b.c')` /
|
||||||
|
`getAll()`. Расширение для витрины — `App\Services\SettingsService` (наследует `Settings`)
|
||||||
|
и DTO в `src/DTO/Settings/` с типизированными геттерами (`is*()`, `get*()`).
|
||||||
|
- **Сборка конфига:** `module/.../bastion/ApplicationFactory.php` (и точка входа
|
||||||
|
`admin/controller/extension/module/tgshop.php`) — дефолты из `configs/app.php` мержатся с
|
||||||
|
сохранёнными `module_telecart_settings` через `Arr::mergeArraysRecursively`, результат
|
||||||
|
отдаётся в `Application`.
|
||||||
|
- **Фронтенд (стор):** `frontend/admin/src/stores/settings.js` — Pinia setup-store: дефолтный
|
||||||
|
state, `fetchSettings()` (`getSettingsForm`), `saveSettings()` (`saveSettingsForm`).
|
||||||
|
- **Фронтенд (поля):** `frontend/admin/src/components/Settings/` — `ItemBool` (toggle),
|
||||||
|
`ItemInput`, `ItemSelect`, `ItemImage`, `ItemToggleButton`. `ItemBool` →
|
||||||
|
`Switcher` → `ToggleSwitch` (PrimeVue). Используются во `views/*View.vue`.
|
||||||
|
|
||||||
|
## Поток данных
|
||||||
|
|
||||||
|
Чтение формы:
|
||||||
|
`getSettingsForm` → `Settings::getAll()` (уже смерженный конфиг) → срез по секциям →
|
||||||
|
JSON → стор `fetchSettings()` → поля во `*View.vue`.
|
||||||
|
|
||||||
|
Сохранение:
|
||||||
|
поле (`v-model`) → стор `saveSettings()` → `saveSettingsForm` → `SettingsService::update()`
|
||||||
|
→ `oc_setting` (`module_telecart_settings`) + сброс кэша.
|
||||||
|
|
||||||
|
Чтение на backend:
|
||||||
|
дефолты `configs/app.php` ⊕ сохранённые значения → `Application` → `Config\Settings` / DTO
|
||||||
|
→ сервисы (через DI-провайдеры, которые инжектят `getConfigValue('...')` в конструкторы).
|
||||||
|
|
||||||
|
## Как добавить новый toggle (чек-лист)
|
||||||
|
|
||||||
|
1. **Дефолт:** добавить ключ в нужную секцию `configs/app.php` (для булева — `true`/`false`).
|
||||||
|
2. **Backend-потребитель:** прочитать значение там, где оно нужно. Идиома модуля — читать в
|
||||||
|
ServiceProvider (`$app->getConfigValue('секция.ключ')`) и **инжектить** в конструктор
|
||||||
|
сервиса (не дёргать конфиг из глубины класса). При наличии DTO — добавить геттер.
|
||||||
|
3. **Стор:** добавить ключ с дефолтом в соответствующую секцию state в
|
||||||
|
`frontend/admin/src/stores/settings.js`.
|
||||||
|
4. **View:** добавить компонент поля (для тумблера — `<ItemBool label="…"
|
||||||
|
v-model="settings.items.<секция>.<ключ>">…текст-подсказка…</ItemBool>`) в нужный
|
||||||
|
`*View.vue`.
|
||||||
|
5. Убедиться, что секция уже перечислена в списках `Arr::getWithKeys` в `getSettingsForm`/
|
||||||
|
`saveSettingsForm` (если добавляешь поле в новую секцию — добавить и саму секцию в оба
|
||||||
|
списка).
|
||||||
|
|
||||||
|
## Связанные фичи
|
||||||
|
|
||||||
|
- [telecart-pulse](../telecart-pulse/CLAUDE.md) — heartbeat использует этот каркас:
|
||||||
|
флаг `pulse.heartbeat_enabled` проходит весь путь от формы до backend-гейта.
|
||||||
|
|
||||||
|
## Нюансы и подводные камни
|
||||||
|
|
||||||
|
- **PHP 7.4** в backend-коде (без `match`, промоушена свойств, union-типов и т.п.).
|
||||||
|
- `getSettingsForm`/`saveSettingsForm` работают строго по **белому списку секций**
|
||||||
|
(`Arr::getWithKeys`). Поле вне перечисленных секций не отдастся и не сохранится.
|
||||||
|
- Секция `cron` содержит read-only поля (`cli_path`, `last_run`), которые добавляются при
|
||||||
|
выдаче и вырезаются перед сохранением — не сохраняй вычисляемые значения.
|
||||||
|
- `saveSettingsForm` сейчас вызывает пустой `validate()` — серверной валидации значений
|
||||||
|
настроек по сути нет; не полагайся на неё.
|
||||||
|
- После сохранения кэш сбрасывается целиком (`SettingsService::update` → `cache->clear()`).
|
||||||
|
- Существующие установки не имеют сохранённого значения нового ключа — оно берётся из
|
||||||
|
дефолта `configs/app.php` (мерж). Выбирай дефолт так, чтобы поведение «как было».
|
||||||
78
docs/features/telecart-pulse/CLAUDE.md
Normal file
78
docs/features/telecart-pulse/CLAUDE.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# TeleCart Pulse
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Связка модуля с внешним SaaS `pulse.telecart.pro`. Включает две концептуально разные части:
|
||||||
|
|
||||||
|
- **События кампаний** (`WEBAPP_OPEN`, `ORDER_CREATED`, …) — аналитика маркетинговых
|
||||||
|
кампаний и Telegram-рассылок. Завязана на действия продавца: событие сохраняется и
|
||||||
|
отправляется только если в `start_param` пришли `campaign_id` и `tracking_id`, и только
|
||||||
|
при заданном `pulse.api_key`.
|
||||||
|
- **Heartbeat** — раз в час отправляет технические сведения об установке (домен, имя бота,
|
||||||
|
версии PHP/модуля/OpenCart). Используется для статистики установок и техподдержки.
|
||||||
|
Управляется **отдельным** переключателем `pulse.heartbeat_enabled` (по умолчанию
|
||||||
|
**выключен**, opt-in), независимым от `pulse.api_key`.
|
||||||
|
|
||||||
|
В коде обе части живут в namespace `Openguru\OpenCartFramework\TeleCartPulse`.
|
||||||
|
|
||||||
|
## Точки входа
|
||||||
|
|
||||||
|
| action | Метод | Где объявлен |
|
||||||
|
|-------------|------------------------------|------------------|
|
||||||
|
| `ingest` | `TelemetryHandler::ingest` | `src/routes.php` |
|
||||||
|
| `heartbeat` | `TelemetryHandler::heartbeat`| `src/routes.php` |
|
||||||
|
|
||||||
|
Триггерятся из SPA (`frontend/spa/src/stores/Pulse.js`, вызов при инициализации WebApp в
|
||||||
|
`frontend/spa/src/main.js`). Heartbeat кэшируется на 1 час (`telecart_pulse_heartbeat`).
|
||||||
|
|
||||||
|
## Ключевые файлы по слоям
|
||||||
|
|
||||||
|
- **Handler:** `module/.../src/Handlers/TelemetryHandler.php` — `ingest()` (события) и
|
||||||
|
`heartbeat()` (телеметрия окружения); оба делегируют в `TeleCartPulseService`.
|
||||||
|
- **Сервис:** `module/.../framework/TeleCartPulse/TeleCartPulseService.php` —
|
||||||
|
`handleIngest()` (валидация campaign/tracking, сохранение события в БД) и
|
||||||
|
`handleHeartbeat()` (сбор окружения, подпись, POST). В начале `handleHeartbeat()` —
|
||||||
|
гейт `if (! $this->heartbeatEnabled) return;`.
|
||||||
|
- **Отправка событий:** `module/.../framework/TeleCartPulse/TeleCartPulseEventsSender.php` —
|
||||||
|
POST накопленных событий на `/events` с `Authorization: Bearer <api_key>`.
|
||||||
|
- **Регистрация в DI:** `module/.../framework/TeleCartPulse/TeleCartPulseServiceProvider.php`
|
||||||
|
— читает `pulse.api_key`, секрет подписи (`pulse.heartbeat_secret` / env
|
||||||
|
`PULSE_HEARTBEAT_SECRET`) и `pulse.heartbeat_enabled`, инжектит в сервисы.
|
||||||
|
- **Модель/данные:** `module/.../framework/TeleCartPulse/TeleCartEvent.php`, таблица
|
||||||
|
`telecart_events` (миграция `database/migrations/20260101000010_create_telecart_events_table.php`).
|
||||||
|
- **Расписание:** `configs/schedule.php` — `TeleCartPulseSendEventsTask` каждые 10 минут;
|
||||||
|
CLI-аналог — `console/Commands/PulseSendEventsCommand.php` (`pulse:send`).
|
||||||
|
- **Конфиг/дефолты:** `configs/app.php`, секция `pulse` (`api_key`, `heartbeat_enabled`,
|
||||||
|
`batch_size`, `max_attempts`).
|
||||||
|
- **Фронтенд (админка):** `frontend/admin/src/views/TeleCartPulseView.vue` — поля настроек,
|
||||||
|
включая переключатель heartbeat (`ItemBool`) с раскрытием состава отправляемых данных;
|
||||||
|
дефолты в `frontend/admin/src/stores/settings.js` (секция `pulse`).
|
||||||
|
|
||||||
|
## Поток данных
|
||||||
|
|
||||||
|
**События:** SPA `ingest` → `handleIngest()` → проверка `api_key` + наличия
|
||||||
|
`campaign_id`/`tracking_id` → запись в `telecart_events` (status `pending`) → каждые 10 мин
|
||||||
|
`TeleCartPulseSendEventsTask` → `TeleCartPulseEventsSender::sendEvent()` → POST `/events`.
|
||||||
|
|
||||||
|
**Heartbeat:** SPA `heartbeat` → `handleHeartbeat()` → гейт `heartbeat_enabled` → кэш-чек
|
||||||
|
(1 ч) → `getMe()` → payload окружения → подпись (если есть signer) → POST `/heartbeat`.
|
||||||
|
|
||||||
|
## Связанные фичи
|
||||||
|
|
||||||
|
- [settings](../settings/CLAUDE.md) — переключатель heartbeat и `api_key` проходят через
|
||||||
|
общий каркас настроек.
|
||||||
|
|
||||||
|
## Нюансы и подводные камни
|
||||||
|
|
||||||
|
- **PHP 7.4** в backend-коде.
|
||||||
|
- **Heartbeat и Pulse — независимы.** `pulse.heartbeat_enabled` НЕ завязан на `api_key`.
|
||||||
|
Heartbeat реально уходит только при `heartbeat_enabled = true` **и** наличии signer
|
||||||
|
(секрет настроен). Выключенный тумблер → ранний `return`, даже кэш не ставится.
|
||||||
|
- **События гейтятся `api_key`:** при пустом `pulse.api_key` `handleIngest()` выходит сразу,
|
||||||
|
ничего не сохраняется и не отправляется.
|
||||||
|
- **Прозрачность heartbeat — обязательна.** Состав отправляемых данных раскрыт покупателю в
|
||||||
|
`TeleCartPulseView.vue`; при изменении полей payload в `handleHeartbeat()` синхронизируй
|
||||||
|
текст раскрытия.
|
||||||
|
- Дефолт `heartbeat_enabled = false` (opt-in): без явного включения покупателем heartbeat не
|
||||||
|
отправляется; существующие установки без сохранённого значения тоже ничего не шлют.
|
||||||
|
- Тест гейта: `tests/Unit/Framework/TeleCartPulse/TeleCartPulseServiceHeartbeatTest.php`.
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
# Дизайн: честный и отключаемый heartbeat
|
||||||
|
|
||||||
|
Дата: 2026-06-30
|
||||||
|
Статус: согласовано (ожидает финального ревью)
|
||||||
|
|
||||||
|
## Проблема
|
||||||
|
|
||||||
|
Модуль скрытно отправляет на сервер разработчика (`pulse.telecart.pro`) сведения об
|
||||||
|
окружении, где он запущен (heartbeat). Покупатель об этом не уведомляется и не может
|
||||||
|
это отключить. Это нечестно по отношению к покупателю и создаёт риски (GDPR / 152-ФЗ,
|
||||||
|
претензии маркетплейсов).
|
||||||
|
|
||||||
|
## Цель
|
||||||
|
|
||||||
|
Сделать heartbeat **честным**: прозрачным и отключаемым, сохранив поведение по умолчанию.
|
||||||
|
|
||||||
|
- **Прозрачность:** в админ-панели покупатель видит, что именно собирается, куда
|
||||||
|
отправляется и зачем.
|
||||||
|
- **Контроль:** отдельный переключатель, которым покупатель управляет heartbeat.
|
||||||
|
- **По умолчанию выключено (opt-in).** Без явного включения покупателем на сервер ничего не
|
||||||
|
уходит — в том числе у существующих установок. (Изначально планировался opt-out с дефолтом
|
||||||
|
«включено»; решение пересмотрено в пользу opt-in из-за юридических рисков сбора
|
||||||
|
идентифицирующих данных — домена и имени бота — без согласия.)
|
||||||
|
|
||||||
|
## Границы (scope)
|
||||||
|
|
||||||
|
- **Только heartbeat.** Остальной TeleCart Pulse (Telegram-рассылки, события кампаний)
|
||||||
|
**не трогаем** — он гейтится наличием `pulse.api_key` и, по позиции владельца, прав не
|
||||||
|
нарушает.
|
||||||
|
- Концептуально heartbeat считаем частью фичи **TeleCart Pulse** (одна папка доков), но
|
||||||
|
у него **отдельный собственный выключатель**, не завязанный на `pulse.api_key` и не
|
||||||
|
завязанный на секрет подписи. Pulse и heartbeat включаются/выключаются независимо.
|
||||||
|
|
||||||
|
## Терминология
|
||||||
|
|
||||||
|
- **heartbeat** — периодическая (раз в час по кэшу) отправка информации об окружении:
|
||||||
|
`domain`, `bot_name`, `php_version`, `module_version`, `opencart_version`,
|
||||||
|
`opencart_version_core`. Триггерится из SPA при открытии WebApp.
|
||||||
|
- **TeleCart Pulse** — фича рассылок и аналитики кампаний; heartbeat живёт внутри её
|
||||||
|
namespace в коде (`framework/TeleCartPulse/`).
|
||||||
|
|
||||||
|
## Текущее устройство (как есть)
|
||||||
|
|
||||||
|
- `TeleCartPulseService::handleHeartbeat()`
|
||||||
|
(`module/.../framework/TeleCartPulse/TeleCartPulseService.php:133`) — ставит кэш-флаг
|
||||||
|
`telecart_pulse_heartbeat` (ttl 3600), дёргает `getMe()`, формирует payload и шлёт POST
|
||||||
|
на `PULSE_API_HOST/heartbeat`.
|
||||||
|
- Отправка фактически происходит только если есть `heartbeatPayloadSigner` (строки 168–170),
|
||||||
|
который создаётся в провайдере при наличии секрета (`pulse.heartbeat_secret` /
|
||||||
|
`PULSE_HEARTBEAT_SECRET`).
|
||||||
|
- `TeleCartPulseServiceProvider`
|
||||||
|
(`module/.../framework/TeleCartPulse/TeleCartPulseServiceProvider.php:22-35`) читает
|
||||||
|
значения из конфига и инжектит их в `TeleCartPulseService` через конструктор.
|
||||||
|
- **Пользовательской (per-install) настройки для heartbeat сейчас нет.**
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
### Принцип
|
||||||
|
|
||||||
|
Использовать существующий паттерн: провайдер читает значение из конфига и **инжектит**
|
||||||
|
его в сервис через конструктор (как уже делается для `pulse.api_key` и signer). Не вводить
|
||||||
|
новых механизмов гейтинга.
|
||||||
|
|
||||||
|
### 1. Backend
|
||||||
|
|
||||||
|
**`module/.../configs/app.php`** — секция `pulse` (строки 86–90): добавить флаг с дефолтом
|
||||||
|
`false` (opt-in).
|
||||||
|
|
||||||
|
```php
|
||||||
|
'pulse' => [
|
||||||
|
'api_key' => '',
|
||||||
|
'heartbeat_enabled' => false, // новый флаг, opt-in
|
||||||
|
'batch_size' => 50,
|
||||||
|
'max_attempts' => 3,
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
**`TeleCartPulseServiceProvider`** — прочитать `pulse.heartbeat_enabled` и передать булевый
|
||||||
|
флаг в конструктор `TeleCartPulseService` (рядом с `apiKey` и `heartbeatSigner`). Привести
|
||||||
|
к `bool` явно.
|
||||||
|
|
||||||
|
**`TeleCartPulseService`** — принять флаг в конструкторе (новое поле `private bool
|
||||||
|
$heartbeatEnabled`). В `handleHeartbeat()` — **ранний `return` в самом начале метода**, до
|
||||||
|
проверки/установки кэша и до `getMe()`, если флаг выключен:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function handleHeartbeat(): void
|
||||||
|
{
|
||||||
|
if (! $this->heartbeatEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ... существующая логика
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Совместимость с PHP 7.4: без named arguments / промоушена свойств / union-типов. Параметр
|
||||||
|
конструктора — позиционный, со значением по умолчанию `true` для обратной совместимости
|
||||||
|
вызовов в тестах.
|
||||||
|
|
||||||
|
### 2. Frontend (админка) + прозрачность
|
||||||
|
|
||||||
|
**`frontend/admin/src/stores/settings.js`** — секция `pulse` (строки 89–92): добавить
|
||||||
|
`heartbeat_enabled` в дефолтное состояние state.
|
||||||
|
|
||||||
|
**`frontend/admin/src/views/TeleCartPulseView.vue`** — добавить `ItemBool` с переключателем,
|
||||||
|
привязанным к `settings.items.pulse.heartbeat_enabled`. В слот-описание — **честное, но
|
||||||
|
нейтральное по тону раскрытие**:
|
||||||
|
|
||||||
|
- перечислить, что отправляется: домен, имя бота, версия PHP, версия модуля, версии
|
||||||
|
OpenCart;
|
||||||
|
- получателя назвать обобщённо — «разработчику» (без адреса сервера и периодичности: они не
|
||||||
|
важны покупателю);
|
||||||
|
- явно указать, что персональные данные покупателей и заказы **не** передаются;
|
||||||
|
- описать цель мягко: для улучшения продукта и техподдержки (без акцента на проверке
|
||||||
|
лицензии);
|
||||||
|
- отметить, что по умолчанию отправка отключена и её можно включить/отключить в любой момент.
|
||||||
|
|
||||||
|
Итоговая формулировка согласована с владельцем (нейтральный тон, вариант с оговоркой про
|
||||||
|
отсутствие персональных данных).
|
||||||
|
|
||||||
|
### 3. Поведение для существующих установок
|
||||||
|
|
||||||
|
Дефолт `false` в конфиге мержится с сохранёнными настройками
|
||||||
|
(`ApplicationFactory`: `Arr::mergeArraysRecursively`). У установок без сохранённого значения
|
||||||
|
heartbeat **выключен** — на сервер ничего не уходит, пока покупатель явно не включит тумблер.
|
||||||
|
Когда покупатель сохраняет настройку, значение пишется в `oc_setting`
|
||||||
|
(`module_telecart_settings`) и читается на backend.
|
||||||
|
|
||||||
|
### 4. Гейтинг — итоговая логика
|
||||||
|
|
||||||
|
heartbeat реально отправляется только если **одновременно**: `heartbeat_enabled = true`
|
||||||
|
(покупатель включил) **и** есть signer (секрет настроен). Переключатель — пользовательский
|
||||||
|
контроль поверх существующего серверного гейта; они независимы.
|
||||||
|
|
||||||
|
## Тестирование
|
||||||
|
|
||||||
|
Unit-тест на `TeleCartPulseService::handleHeartbeat()`:
|
||||||
|
|
||||||
|
- `heartbeat_enabled = false` → метод выходит сразу: HTTP-запрос не выполняется, кэш-флаг
|
||||||
|
`telecart_pulse_heartbeat` **не** ставится, `getMe()` не вызывается.
|
||||||
|
- `heartbeat_enabled = true` + signer присутствует → payload формируется и уходит (как
|
||||||
|
сейчас).
|
||||||
|
|
||||||
|
Запуск: `make test-unit` (или `./vendor/bin/phpunit --filter ...` через `make ssh`).
|
||||||
|
|
||||||
|
## Документация (в этом порядке)
|
||||||
|
|
||||||
|
1. **`docs/features/settings/CLAUDE.md`** — каркас системы настроек: путь поля «форма в
|
||||||
|
админке → `saveSettingsForm` → таблица `oc_setting` (`module_telecart_settings`) →
|
||||||
|
`Config\Settings` / DTO → backend», и **чек-лист «как добавить новый toggle»** по слоям
|
||||||
|
(config-дефолт → провайдер/DTO → store → view-компонент `ItemBool`). По шаблону
|
||||||
|
`docs/features/_TEMPLATE.md`. Добавить строку в реестр в `docs/features/CLAUDE.md`.
|
||||||
|
2. **`docs/features/telecart-pulse/CLAUDE.md`** — фича Pulse целиком: рассылки, события
|
||||||
|
кампаний (гейт `api_key`) и heartbeat с новым выключателем. Ссылка на `[[settings]]`.
|
||||||
|
Добавить строку в реестр.
|
||||||
|
|
||||||
|
## Затрагиваемые файлы (сводка)
|
||||||
|
|
||||||
|
- `module/.../configs/app.php` — флаг `pulse.heartbeat_enabled`.
|
||||||
|
- `module/.../framework/TeleCartPulse/TeleCartPulseServiceProvider.php` — чтение и инжект.
|
||||||
|
- `module/.../framework/TeleCartPulse/TeleCartPulseService.php` — поле + ранний return.
|
||||||
|
- `frontend/admin/src/stores/settings.js` — дефолт в state.
|
||||||
|
- `frontend/admin/src/views/TeleCartPulseView.vue` — `ItemBool` + текст раскрытия.
|
||||||
|
- `module/.../tests/Unit/...` — тест на гейтинг heartbeat.
|
||||||
|
- `docs/features/settings/CLAUDE.md`, `docs/features/telecart-pulse/CLAUDE.md`,
|
||||||
|
`docs/features/CLAUDE.md` (реестр).
|
||||||
|
|
||||||
|
## Явно вне scope (YAGNI)
|
||||||
|
|
||||||
|
- Делаем opt-in (по умолчанию выключено); отдельный consent-баннер на первой установке не
|
||||||
|
нужен — достаточно выключенного по умолчанию переключателя с раскрытием.
|
||||||
|
- Не трогаем события кампаний и рассылки Pulse.
|
||||||
|
- Не выносим heartbeat в отдельный namespace/фичу из `TeleCartPulse`.
|
||||||
503
frontend/admin/package-lock.json
generated
503
frontend/admin/package-lock.json
generated
@@ -51,13 +51,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.29.0",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
||||||
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-validator-identifier": "^7.28.5",
|
"@babel/helper-validator-identifier": "^7.29.7",
|
||||||
"js-tokens": "^4.0.0",
|
"js-tokens": "^4.0.0",
|
||||||
"picocolors": "^1.1.1"
|
"picocolors": "^1.1.1"
|
||||||
},
|
},
|
||||||
@@ -66,9 +66,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/compat-data": {
|
"node_modules/@babel/compat-data": {
|
||||||
"version": "7.29.0",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
|
||||||
"integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
|
"integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -76,21 +76,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/core": {
|
"node_modules/@babel/core": {
|
||||||
"version": "7.29.0",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
|
||||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@babel/code-frame": "^7.29.7",
|
||||||
"@babel/generator": "^7.29.0",
|
"@babel/generator": "^7.29.7",
|
||||||
"@babel/helper-compilation-targets": "^7.28.6",
|
"@babel/helper-compilation-targets": "^7.29.7",
|
||||||
"@babel/helper-module-transforms": "^7.28.6",
|
"@babel/helper-module-transforms": "^7.29.7",
|
||||||
"@babel/helpers": "^7.28.6",
|
"@babel/helpers": "^7.29.7",
|
||||||
"@babel/parser": "^7.29.0",
|
"@babel/parser": "^7.29.7",
|
||||||
"@babel/template": "^7.28.6",
|
"@babel/template": "^7.29.7",
|
||||||
"@babel/traverse": "^7.29.0",
|
"@babel/traverse": "^7.29.7",
|
||||||
"@babel/types": "^7.29.0",
|
"@babel/types": "^7.29.7",
|
||||||
"@jridgewell/remapping": "^2.3.5",
|
"@jridgewell/remapping": "^2.3.5",
|
||||||
"convert-source-map": "^2.0.0",
|
"convert-source-map": "^2.0.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
@@ -117,14 +117,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/generator": {
|
"node_modules/@babel/generator": {
|
||||||
"version": "7.29.1",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
|
||||||
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
"integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.29.0",
|
"@babel/parser": "^7.29.7",
|
||||||
"@babel/types": "^7.29.0",
|
"@babel/types": "^7.29.7",
|
||||||
"@jridgewell/gen-mapping": "^0.3.12",
|
"@jridgewell/gen-mapping": "^0.3.12",
|
||||||
"@jridgewell/trace-mapping": "^0.3.28",
|
"@jridgewell/trace-mapping": "^0.3.28",
|
||||||
"jsesc": "^3.0.2"
|
"jsesc": "^3.0.2"
|
||||||
@@ -147,14 +147,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-compilation-targets": {
|
"node_modules/@babel/helper-compilation-targets": {
|
||||||
"version": "7.28.6",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
|
||||||
"integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
|
"integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/compat-data": "^7.28.6",
|
"@babel/compat-data": "^7.29.7",
|
||||||
"@babel/helper-validator-option": "^7.27.1",
|
"@babel/helper-validator-option": "^7.29.7",
|
||||||
"browserslist": "^4.24.0",
|
"browserslist": "^4.24.0",
|
||||||
"lru-cache": "^5.1.1",
|
"lru-cache": "^5.1.1",
|
||||||
"semver": "^6.3.1"
|
"semver": "^6.3.1"
|
||||||
@@ -206,9 +206,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-globals": {
|
"node_modules/@babel/helper-globals": {
|
||||||
"version": "7.28.0",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
|
||||||
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
"integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -230,29 +230,29 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-module-imports": {
|
"node_modules/@babel/helper-module-imports": {
|
||||||
"version": "7.28.6",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
|
||||||
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
"integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/traverse": "^7.28.6",
|
"@babel/traverse": "^7.29.7",
|
||||||
"@babel/types": "^7.28.6"
|
"@babel/types": "^7.29.7"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-module-transforms": {
|
"node_modules/@babel/helper-module-transforms": {
|
||||||
"version": "7.28.6",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
|
||||||
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
|
"integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-module-imports": "^7.28.6",
|
"@babel/helper-module-imports": "^7.29.7",
|
||||||
"@babel/helper-validator-identifier": "^7.28.5",
|
"@babel/helper-validator-identifier": "^7.29.7",
|
||||||
"@babel/traverse": "^7.28.6"
|
"@babel/traverse": "^7.29.7"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -317,27 +317,27 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-string-parser": {
|
"node_modules/@babel/helper-string-parser": {
|
||||||
"version": "7.27.1",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
||||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-validator-identifier": {
|
"node_modules/@babel/helper-validator-identifier": {
|
||||||
"version": "7.28.5",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
||||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-validator-option": {
|
"node_modules/@babel/helper-validator-option": {
|
||||||
"version": "7.27.1",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
|
||||||
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
|
"integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -345,26 +345,26 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helpers": {
|
"node_modules/@babel/helpers": {
|
||||||
"version": "7.28.6",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
|
||||||
"integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
|
"integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/template": "^7.28.6",
|
"@babel/template": "^7.29.7",
|
||||||
"@babel/types": "^7.28.6"
|
"@babel/types": "^7.29.7"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/parser": {
|
"node_modules/@babel/parser": {
|
||||||
"version": "7.29.0",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
|
||||||
"integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
|
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.29.0"
|
"@babel/types": "^7.29.7"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"parser": "bin/babel-parser.js"
|
"parser": "bin/babel-parser.js"
|
||||||
@@ -489,33 +489,33 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.28.6",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
|
||||||
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
"integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.28.6",
|
"@babel/code-frame": "^7.29.7",
|
||||||
"@babel/parser": "^7.28.6",
|
"@babel/parser": "^7.29.7",
|
||||||
"@babel/types": "^7.28.6"
|
"@babel/types": "^7.29.7"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/traverse": {
|
"node_modules/@babel/traverse": {
|
||||||
"version": "7.29.0",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
|
||||||
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
"integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@babel/code-frame": "^7.29.7",
|
||||||
"@babel/generator": "^7.29.0",
|
"@babel/generator": "^7.29.7",
|
||||||
"@babel/helper-globals": "^7.28.0",
|
"@babel/helper-globals": "^7.29.7",
|
||||||
"@babel/parser": "^7.29.0",
|
"@babel/parser": "^7.29.7",
|
||||||
"@babel/template": "^7.28.6",
|
"@babel/template": "^7.29.7",
|
||||||
"@babel/types": "^7.29.0",
|
"@babel/types": "^7.29.7",
|
||||||
"debug": "^4.3.1"
|
"debug": "^4.3.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -523,13 +523,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/types": {
|
"node_modules/@babel/types": {
|
||||||
"version": "7.29.0",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
|
||||||
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.27.1",
|
"@babel/helper-string-parser": "^7.29.7",
|
||||||
"@babel/helper-validator-identifier": "^7.28.5"
|
"@babel/helper-validator-identifier": "^7.29.7"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -718,9 +718,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
|
||||||
"integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==",
|
"integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -734,9 +734,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
|
||||||
"integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==",
|
"integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -750,9 +750,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==",
|
"integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -766,9 +766,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==",
|
"integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -782,9 +782,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==",
|
"integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -798,9 +798,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==",
|
"integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -814,9 +814,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==",
|
"integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -830,9 +830,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==",
|
"integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -846,9 +846,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
|
||||||
"integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==",
|
"integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -862,9 +862,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==",
|
"integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -878,9 +878,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
|
||||||
"integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==",
|
"integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -894,9 +894,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
|
||||||
"integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==",
|
"integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -910,9 +910,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
|
||||||
"integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==",
|
"integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
@@ -926,9 +926,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
|
||||||
"integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==",
|
"integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -942,9 +942,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
|
||||||
"integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==",
|
"integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -958,9 +958,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
|
||||||
"integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==",
|
"integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -974,9 +974,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==",
|
"integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -990,9 +990,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-arm64": {
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==",
|
"integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1006,9 +1006,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==",
|
"integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1022,9 +1022,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-arm64": {
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==",
|
"integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1038,9 +1038,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==",
|
"integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1054,9 +1054,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openharmony-arm64": {
|
"node_modules/@esbuild/openharmony-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==",
|
"integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1070,9 +1070,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==",
|
"integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1086,9 +1086,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==",
|
"integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1102,9 +1102,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
|
||||||
"integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==",
|
"integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -1118,9 +1118,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==",
|
"integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -3075,6 +3075,18 @@
|
|||||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/agent-base": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.14.0",
|
"version": "6.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
|
||||||
@@ -3132,13 +3144,14 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.15.2",
|
"version": "1.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz",
|
||||||
"integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==",
|
"integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.11",
|
"follow-redirects": "^1.16.0",
|
||||||
"form-data": "^4.0.5",
|
"form-data": "^4.0.5",
|
||||||
|
"https-proxy-agent": "^5.0.1",
|
||||||
"proxy-from-env": "^2.1.0"
|
"proxy-from-env": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3150,9 +3163,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/baseline-browser-mapping": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.10.7",
|
"version": "2.10.40",
|
||||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.7.tgz",
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz",
|
||||||
"integrity": "sha512-1ghYO3HnxGec0TCGBXiDLVns4eCSx4zJpxnHrlqFQajmhfKMQBzUGDdkMK7fUW7PTHTeLf+j87aTuKuuwWzMGw==",
|
"integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -3190,9 +3203,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/browserslist": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.28.1",
|
"version": "4.28.4",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz",
|
||||||
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
|
"integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -3210,11 +3223,11 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.10.38",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001799",
|
||||||
"electron-to-chromium": "^1.5.263",
|
"electron-to-chromium": "^1.5.376",
|
||||||
"node-releases": "^2.0.27",
|
"node-releases": "^2.0.48",
|
||||||
"update-browserslist-db": "^1.2.0"
|
"update-browserslist-db": "^1.2.3"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"browserslist": "cli.js"
|
"browserslist": "cli.js"
|
||||||
@@ -3263,9 +3276,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001778",
|
"version": "1.0.30001800",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001778.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz",
|
||||||
"integrity": "sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==",
|
"integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -3429,7 +3442,6 @@
|
|||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@@ -3526,9 +3538,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.313",
|
"version": "1.5.382",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.382.tgz",
|
||||||
"integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==",
|
"integrity": "sha512-8ETaWbV6SZOrno+G93Ffd9ENsMtetqdnqj4nlfxFW90Sm5GgnuV28Kf62hqQVD6VUgzm7qFQKsTsAPmeUiU3Ug==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
@@ -3613,9 +3625,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
|
||||||
"integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==",
|
"integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -3625,32 +3637,32 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.27.4",
|
"@esbuild/aix-ppc64": "0.28.1",
|
||||||
"@esbuild/android-arm": "0.27.4",
|
"@esbuild/android-arm": "0.28.1",
|
||||||
"@esbuild/android-arm64": "0.27.4",
|
"@esbuild/android-arm64": "0.28.1",
|
||||||
"@esbuild/android-x64": "0.27.4",
|
"@esbuild/android-x64": "0.28.1",
|
||||||
"@esbuild/darwin-arm64": "0.27.4",
|
"@esbuild/darwin-arm64": "0.28.1",
|
||||||
"@esbuild/darwin-x64": "0.27.4",
|
"@esbuild/darwin-x64": "0.28.1",
|
||||||
"@esbuild/freebsd-arm64": "0.27.4",
|
"@esbuild/freebsd-arm64": "0.28.1",
|
||||||
"@esbuild/freebsd-x64": "0.27.4",
|
"@esbuild/freebsd-x64": "0.28.1",
|
||||||
"@esbuild/linux-arm": "0.27.4",
|
"@esbuild/linux-arm": "0.28.1",
|
||||||
"@esbuild/linux-arm64": "0.27.4",
|
"@esbuild/linux-arm64": "0.28.1",
|
||||||
"@esbuild/linux-ia32": "0.27.4",
|
"@esbuild/linux-ia32": "0.28.1",
|
||||||
"@esbuild/linux-loong64": "0.27.4",
|
"@esbuild/linux-loong64": "0.28.1",
|
||||||
"@esbuild/linux-mips64el": "0.27.4",
|
"@esbuild/linux-mips64el": "0.28.1",
|
||||||
"@esbuild/linux-ppc64": "0.27.4",
|
"@esbuild/linux-ppc64": "0.28.1",
|
||||||
"@esbuild/linux-riscv64": "0.27.4",
|
"@esbuild/linux-riscv64": "0.28.1",
|
||||||
"@esbuild/linux-s390x": "0.27.4",
|
"@esbuild/linux-s390x": "0.28.1",
|
||||||
"@esbuild/linux-x64": "0.27.4",
|
"@esbuild/linux-x64": "0.28.1",
|
||||||
"@esbuild/netbsd-arm64": "0.27.4",
|
"@esbuild/netbsd-arm64": "0.28.1",
|
||||||
"@esbuild/netbsd-x64": "0.27.4",
|
"@esbuild/netbsd-x64": "0.28.1",
|
||||||
"@esbuild/openbsd-arm64": "0.27.4",
|
"@esbuild/openbsd-arm64": "0.28.1",
|
||||||
"@esbuild/openbsd-x64": "0.27.4",
|
"@esbuild/openbsd-x64": "0.28.1",
|
||||||
"@esbuild/openharmony-arm64": "0.27.4",
|
"@esbuild/openharmony-arm64": "0.28.1",
|
||||||
"@esbuild/sunos-x64": "0.27.4",
|
"@esbuild/sunos-x64": "0.28.1",
|
||||||
"@esbuild/win32-arm64": "0.27.4",
|
"@esbuild/win32-arm64": "0.28.1",
|
||||||
"@esbuild/win32-ia32": "0.27.4",
|
"@esbuild/win32-ia32": "0.28.1",
|
||||||
"@esbuild/win32-x64": "0.27.4"
|
"@esbuild/win32-x64": "0.28.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
@@ -4042,16 +4054,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
|
||||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
"es-set-tostringtag": "^2.1.0",
|
"es-set-tostringtag": "^2.1.0",
|
||||||
"hasown": "^2.0.2",
|
"hasown": "^2.0.4",
|
||||||
"mime-types": "^2.1.12"
|
"mime-types": "^2.1.35"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -4209,9 +4221,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@@ -4226,6 +4238,19 @@
|
|||||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/https-proxy-agent": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "6",
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
@@ -4379,10 +4404,20 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "4.1.1",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
|
||||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/puzrin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/nodeca"
|
||||||
|
}
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
@@ -4849,7 +4884,6 @@
|
|||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
@@ -4878,11 +4912,14 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.36",
|
"version": "2.0.50",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz",
|
||||||
"integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
|
"integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/npm-normalize-package-bin": {
|
"node_modules/npm-normalize-package-bin": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
@@ -5455,9 +5492,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/shell-quote": {
|
"node_modules/shell-quote": {
|
||||||
"version": "1.8.3",
|
"version": "1.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz",
|
||||||
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
|
"integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -5698,12 +5735,12 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.3.3",
|
"version": "7.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz",
|
||||||
"integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==",
|
"integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.27.0",
|
"esbuild": "^0.27.0 || ^0.28.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
"picomatch": "^4.0.3",
|
"picomatch": "^4.0.3",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export const useSettingsStore = defineStore('settings', {
|
|||||||
|
|
||||||
pulse: {
|
pulse: {
|
||||||
api_key: '',
|
api_key: '',
|
||||||
|
heartbeat_enabled: false,
|
||||||
batch_size: 50,
|
batch_size: 50,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,16 @@
|
|||||||
При малом значении нагрузка ниже, но обработка занимает больше времени.
|
При малом значении нагрузка ниже, но обработка занимает больше времени.
|
||||||
Рекомендуемое значение: 50.
|
Рекомендуемое значение: 50.
|
||||||
</ItemInput>
|
</ItemInput>
|
||||||
|
|
||||||
|
<ItemBool label="Отправка технических данных разработчику"
|
||||||
|
v-model="settings.items.pulse.heartbeat_enabled"
|
||||||
|
>
|
||||||
|
Чтобы улучшать продукт и быстрее помогать в поддержке, модуль отправляет разработчику
|
||||||
|
минимальный набор технических данных: домен магазина, имя бота и версии PHP, модуля и
|
||||||
|
OpenCart. <strong>Персональные данные покупателей и информация о заказах не
|
||||||
|
передаются.</strong> По умолчанию отправка отключена; включить или отключить её можно
|
||||||
|
в любой момент — на работу магазина это не влияет.
|
||||||
|
</ItemBool>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -103,6 +113,7 @@
|
|||||||
import {onMounted, ref} from "vue";
|
import {onMounted, ref} from "vue";
|
||||||
import {useSettingsStore} from "@/stores/settings.js";
|
import {useSettingsStore} from "@/stores/settings.js";
|
||||||
import ItemInput from "@/components/Settings/ItemInput.vue";
|
import ItemInput from "@/components/Settings/ItemInput.vue";
|
||||||
|
import ItemBool from "@/components/Settings/ItemBool.vue";
|
||||||
import {apiGet} from "@/utils/http.js";
|
import {apiGet} from "@/utils/http.js";
|
||||||
import SettingsItem from "@/components/SettingsItem.vue";
|
import SettingsItem from "@/components/SettingsItem.vue";
|
||||||
|
|
||||||
|
|||||||
435
frontend/spa/package-lock.json
generated
435
frontend/spa/package-lock.json
generated
@@ -23,10 +23,11 @@
|
|||||||
"vue-router": "^4.6.3"
|
"vue-router": "^4.6.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/vue": "^8.1.0",
|
"@testing-library/vue": "^8.1.0",
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"@vitest/ui": "^4.0.8",
|
"@vitest/ui": "^4.1.9",
|
||||||
"@vue/test-utils": "^2.4.6",
|
"@vue/test-utils": "^2.4.6",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"daisyui": "^5.3.10",
|
"daisyui": "^5.3.10",
|
||||||
@@ -35,7 +36,7 @@
|
|||||||
"tailwindcss": "^4.1.16",
|
"tailwindcss": "^4.1.16",
|
||||||
"terser": "^5.44.0",
|
"terser": "^5.44.0",
|
||||||
"vite": "^7.3.3",
|
"vite": "^7.3.3",
|
||||||
"vitest": "^4.0.8"
|
"vitest": "^4.1.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@acemir/cssom": {
|
"node_modules/@acemir/cssom": {
|
||||||
@@ -291,9 +292,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
|
||||||
"integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==",
|
"integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -307,9 +308,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
|
||||||
"integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==",
|
"integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -323,9 +324,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==",
|
"integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -339,9 +340,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==",
|
"integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -355,9 +356,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==",
|
"integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -371,9 +372,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==",
|
"integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -387,9 +388,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==",
|
"integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -403,9 +404,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==",
|
"integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -419,9 +420,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
|
||||||
"integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==",
|
"integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -435,9 +436,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==",
|
"integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -451,9 +452,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
|
||||||
"integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==",
|
"integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -467,9 +468,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
|
||||||
"integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==",
|
"integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -483,9 +484,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
|
||||||
"integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==",
|
"integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
@@ -499,9 +500,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
|
||||||
"integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==",
|
"integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -515,9 +516,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
|
||||||
"integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==",
|
"integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -531,9 +532,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
|
||||||
"integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==",
|
"integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -547,9 +548,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==",
|
"integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -563,9 +564,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-arm64": {
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==",
|
"integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -579,9 +580,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==",
|
"integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -595,9 +596,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-arm64": {
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==",
|
"integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -611,9 +612,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==",
|
"integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -627,9 +628,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openharmony-arm64": {
|
"node_modules/@esbuild/openharmony-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==",
|
"integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -643,9 +644,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==",
|
"integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -659,9 +660,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==",
|
"integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -675,9 +676,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
|
||||||
"integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==",
|
"integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -691,9 +692,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==",
|
"integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1588,6 +1589,19 @@
|
|||||||
"node": ">= 20"
|
"node": ">= 20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tailwindcss/typography": {
|
||||||
|
"version": "0.5.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
|
||||||
|
"integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"postcss-selector-parser": "6.0.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/vite": {
|
"node_modules/@tailwindcss/vite": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz",
|
||||||
@@ -1738,31 +1752,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/expect": {
|
"node_modules/@vitest/expect": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz",
|
||||||
"integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==",
|
"integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@standard-schema/spec": "^1.1.0",
|
"@standard-schema/spec": "^1.1.0",
|
||||||
"@types/chai": "^5.2.2",
|
"@types/chai": "^5.2.2",
|
||||||
"@vitest/spy": "4.1.0",
|
"@vitest/spy": "4.1.9",
|
||||||
"@vitest/utils": "4.1.0",
|
"@vitest/utils": "4.1.9",
|
||||||
"chai": "^6.2.2",
|
"chai": "^6.2.2",
|
||||||
"tinyrainbow": "^3.0.3"
|
"tinyrainbow": "^3.1.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/mocker": {
|
"node_modules/@vitest/mocker": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz",
|
||||||
"integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==",
|
"integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/spy": "4.1.0",
|
"@vitest/spy": "4.1.9",
|
||||||
"estree-walker": "^3.0.3",
|
"estree-walker": "^3.0.3",
|
||||||
"magic-string": "^0.30.21"
|
"magic-string": "^0.30.21"
|
||||||
},
|
},
|
||||||
@@ -1771,7 +1785,7 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"msw": "^2.4.9",
|
"msw": "^2.4.9",
|
||||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0"
|
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"msw": {
|
"msw": {
|
||||||
@@ -1783,26 +1797,26 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/pretty-format": {
|
"node_modules/@vitest/pretty-format": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz",
|
||||||
"integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==",
|
"integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tinyrainbow": "^3.0.3"
|
"tinyrainbow": "^3.1.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/runner": {
|
"node_modules/@vitest/runner": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz",
|
||||||
"integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==",
|
"integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/utils": "4.1.0",
|
"@vitest/utils": "4.1.9",
|
||||||
"pathe": "^2.0.3"
|
"pathe": "^2.0.3"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
@@ -1810,14 +1824,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/snapshot": {
|
"node_modules/@vitest/snapshot": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz",
|
||||||
"integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==",
|
"integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/pretty-format": "4.1.0",
|
"@vitest/pretty-format": "4.1.9",
|
||||||
"@vitest/utils": "4.1.0",
|
"@vitest/utils": "4.1.9",
|
||||||
"magic-string": "^0.30.21",
|
"magic-string": "^0.30.21",
|
||||||
"pathe": "^2.0.3"
|
"pathe": "^2.0.3"
|
||||||
},
|
},
|
||||||
@@ -1826,9 +1840,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/spy": {
|
"node_modules/@vitest/spy": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz",
|
||||||
"integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==",
|
"integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
@@ -1836,37 +1850,37 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/ui": {
|
"node_modules/@vitest/ui": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.9.tgz",
|
||||||
"integrity": "sha512-sTSDtVM1GOevRGsCNhp1mBUHKo9Qlc55+HCreFT4fe99AHxl1QQNXSL3uj4Pkjh5yEuWZIx8E2tVC94nnBZECQ==",
|
"integrity": "sha512-U/cRvtqfEPj27FI1n9cyUvi4vXXdcLhjJiI+InYKdk8hP4VrS6RXOjGL7rfFaeBc37iRKANsR6eEzIoC7lmgBQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/utils": "4.1.0",
|
"@vitest/utils": "4.1.9",
|
||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
"flatted": "3.4.0",
|
"flatted": "^3.4.2",
|
||||||
"pathe": "^2.0.3",
|
"pathe": "^2.0.3",
|
||||||
"sirv": "^3.0.2",
|
"sirv": "^3.0.2",
|
||||||
"tinyglobby": "^0.2.15",
|
"tinyglobby": "^0.2.15",
|
||||||
"tinyrainbow": "^3.0.3"
|
"tinyrainbow": "^3.1.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vitest": "4.1.0"
|
"vitest": "4.1.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/utils": {
|
"node_modules/@vitest/utils": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz",
|
||||||
"integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==",
|
"integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/pretty-format": "4.1.0",
|
"@vitest/pretty-format": "4.1.9",
|
||||||
"convert-source-map": "^2.0.0",
|
"convert-source-map": "^2.0.0",
|
||||||
"tinyrainbow": "^3.0.3"
|
"tinyrainbow": "^3.1.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
@@ -2520,6 +2534,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/cssesc": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"cssesc": "bin/cssesc"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cssstyle": {
|
"node_modules/cssstyle": {
|
||||||
"version": "5.3.7",
|
"version": "5.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz",
|
||||||
@@ -2815,9 +2842,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-module-lexer": {
|
"node_modules/es-module-lexer": {
|
||||||
"version": "2.0.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.2.0.tgz",
|
||||||
"integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
|
"integrity": "sha512-3lGxdTXCLfe1MYfTz1y2ksAAUM4NAOP6rPEjxGJVKO7TZ5+tvHCaQWGpC4Y3IXvW3ece0Cz1cIP4FWBxOnGCTQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@@ -2835,9 +2862,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.27.4",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
|
||||||
"integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==",
|
"integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -2847,32 +2874,32 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.27.4",
|
"@esbuild/aix-ppc64": "0.28.1",
|
||||||
"@esbuild/android-arm": "0.27.4",
|
"@esbuild/android-arm": "0.28.1",
|
||||||
"@esbuild/android-arm64": "0.27.4",
|
"@esbuild/android-arm64": "0.28.1",
|
||||||
"@esbuild/android-x64": "0.27.4",
|
"@esbuild/android-x64": "0.28.1",
|
||||||
"@esbuild/darwin-arm64": "0.27.4",
|
"@esbuild/darwin-arm64": "0.28.1",
|
||||||
"@esbuild/darwin-x64": "0.27.4",
|
"@esbuild/darwin-x64": "0.28.1",
|
||||||
"@esbuild/freebsd-arm64": "0.27.4",
|
"@esbuild/freebsd-arm64": "0.28.1",
|
||||||
"@esbuild/freebsd-x64": "0.27.4",
|
"@esbuild/freebsd-x64": "0.28.1",
|
||||||
"@esbuild/linux-arm": "0.27.4",
|
"@esbuild/linux-arm": "0.28.1",
|
||||||
"@esbuild/linux-arm64": "0.27.4",
|
"@esbuild/linux-arm64": "0.28.1",
|
||||||
"@esbuild/linux-ia32": "0.27.4",
|
"@esbuild/linux-ia32": "0.28.1",
|
||||||
"@esbuild/linux-loong64": "0.27.4",
|
"@esbuild/linux-loong64": "0.28.1",
|
||||||
"@esbuild/linux-mips64el": "0.27.4",
|
"@esbuild/linux-mips64el": "0.28.1",
|
||||||
"@esbuild/linux-ppc64": "0.27.4",
|
"@esbuild/linux-ppc64": "0.28.1",
|
||||||
"@esbuild/linux-riscv64": "0.27.4",
|
"@esbuild/linux-riscv64": "0.28.1",
|
||||||
"@esbuild/linux-s390x": "0.27.4",
|
"@esbuild/linux-s390x": "0.28.1",
|
||||||
"@esbuild/linux-x64": "0.27.4",
|
"@esbuild/linux-x64": "0.28.1",
|
||||||
"@esbuild/netbsd-arm64": "0.27.4",
|
"@esbuild/netbsd-arm64": "0.28.1",
|
||||||
"@esbuild/netbsd-x64": "0.27.4",
|
"@esbuild/netbsd-x64": "0.28.1",
|
||||||
"@esbuild/openbsd-arm64": "0.27.4",
|
"@esbuild/openbsd-arm64": "0.28.1",
|
||||||
"@esbuild/openbsd-x64": "0.27.4",
|
"@esbuild/openbsd-x64": "0.28.1",
|
||||||
"@esbuild/openharmony-arm64": "0.27.4",
|
"@esbuild/openharmony-arm64": "0.28.1",
|
||||||
"@esbuild/sunos-x64": "0.27.4",
|
"@esbuild/sunos-x64": "0.28.1",
|
||||||
"@esbuild/win32-arm64": "0.27.4",
|
"@esbuild/win32-arm64": "0.28.1",
|
||||||
"@esbuild/win32-ia32": "0.27.4",
|
"@esbuild/win32-ia32": "0.28.1",
|
||||||
"@esbuild/win32-x64": "0.27.4"
|
"@esbuild/win32-x64": "0.28.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
@@ -2896,9 +2923,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/expect-type": {
|
"node_modules/expect-type": {
|
||||||
"version": "1.3.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
|
||||||
"integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
|
"integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2930,9 +2957,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/flatted": {
|
"node_modules/flatted": {
|
||||||
"version": "3.4.0",
|
"version": "3.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||||
"integrity": "sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw==",
|
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
@@ -4111,15 +4138,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/obug": {
|
"node_modules/obug": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz",
|
||||||
"integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
|
"integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
"https://github.com/sponsors/sxzz",
|
"https://github.com/sponsors/sxzz",
|
||||||
"https://opencollective.com/debug"
|
"https://opencollective.com/debug"
|
||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ofetch": {
|
"node_modules/ofetch": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
@@ -4276,6 +4306,20 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss-selector-parser": {
|
||||||
|
"version": "6.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||||
|
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cssesc": "^3.0.0",
|
||||||
|
"util-deprecate": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss-value-parser": {
|
"node_modules/postcss-value-parser": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
@@ -4689,9 +4733,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/std-env": {
|
"node_modules/std-env": {
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
|
||||||
"integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==",
|
"integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@@ -4923,9 +4967,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/tinyexec": {
|
"node_modules/tinyexec": {
|
||||||
"version": "1.0.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
|
||||||
"integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==",
|
"integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -5051,13 +5095,20 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/util-deprecate": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.3.3",
|
"version": "7.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz",
|
||||||
"integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==",
|
"integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.27.0",
|
"esbuild": "^0.27.0 || ^0.28.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
"picomatch": "^4.0.3",
|
"picomatch": "^4.0.3",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
@@ -5126,19 +5177,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vitest": {
|
"node_modules/vitest": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz",
|
||||||
"integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==",
|
"integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/expect": "4.1.0",
|
"@vitest/expect": "4.1.9",
|
||||||
"@vitest/mocker": "4.1.0",
|
"@vitest/mocker": "4.1.9",
|
||||||
"@vitest/pretty-format": "4.1.0",
|
"@vitest/pretty-format": "4.1.9",
|
||||||
"@vitest/runner": "4.1.0",
|
"@vitest/runner": "4.1.9",
|
||||||
"@vitest/snapshot": "4.1.0",
|
"@vitest/snapshot": "4.1.9",
|
||||||
"@vitest/spy": "4.1.0",
|
"@vitest/spy": "4.1.9",
|
||||||
"@vitest/utils": "4.1.0",
|
"@vitest/utils": "4.1.9",
|
||||||
"es-module-lexer": "^2.0.0",
|
"es-module-lexer": "^2.0.0",
|
||||||
"expect-type": "^1.3.0",
|
"expect-type": "^1.3.0",
|
||||||
"magic-string": "^0.30.21",
|
"magic-string": "^0.30.21",
|
||||||
@@ -5149,8 +5200,8 @@
|
|||||||
"tinybench": "^2.9.0",
|
"tinybench": "^2.9.0",
|
||||||
"tinyexec": "^1.0.2",
|
"tinyexec": "^1.0.2",
|
||||||
"tinyglobby": "^0.2.15",
|
"tinyglobby": "^0.2.15",
|
||||||
"tinyrainbow": "^3.0.3",
|
"tinyrainbow": "^3.1.0",
|
||||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0",
|
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
|
||||||
"why-is-node-running": "^2.3.0"
|
"why-is-node-running": "^2.3.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -5166,13 +5217,15 @@
|
|||||||
"@edge-runtime/vm": "*",
|
"@edge-runtime/vm": "*",
|
||||||
"@opentelemetry/api": "^1.9.0",
|
"@opentelemetry/api": "^1.9.0",
|
||||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||||
"@vitest/browser-playwright": "4.1.0",
|
"@vitest/browser-playwright": "4.1.9",
|
||||||
"@vitest/browser-preview": "4.1.0",
|
"@vitest/browser-preview": "4.1.9",
|
||||||
"@vitest/browser-webdriverio": "4.1.0",
|
"@vitest/browser-webdriverio": "4.1.9",
|
||||||
"@vitest/ui": "4.1.0",
|
"@vitest/coverage-istanbul": "4.1.9",
|
||||||
|
"@vitest/coverage-v8": "4.1.9",
|
||||||
|
"@vitest/ui": "4.1.9",
|
||||||
"happy-dom": "*",
|
"happy-dom": "*",
|
||||||
"jsdom": "*",
|
"jsdom": "*",
|
||||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0"
|
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@edge-runtime/vm": {
|
"@edge-runtime/vm": {
|
||||||
@@ -5193,6 +5246,12 @@
|
|||||||
"@vitest/browser-webdriverio": {
|
"@vitest/browser-webdriverio": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"@vitest/coverage-istanbul": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/coverage-v8": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"@vitest/ui": {
|
"@vitest/ui": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,10 +28,11 @@
|
|||||||
"vue-router": "^4.6.3"
|
"vue-router": "^4.6.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/vue": "^8.1.0",
|
"@testing-library/vue": "^8.1.0",
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"@vitest/ui": "^4.0.8",
|
"@vitest/ui": "^4.1.9",
|
||||||
"@vue/test-utils": "^2.4.6",
|
"@vue/test-utils": "^2.4.6",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"daisyui": "^5.3.10",
|
"daisyui": "^5.3.10",
|
||||||
@@ -40,6 +41,6 @@
|
|||||||
"tailwindcss": "^4.1.16",
|
"tailwindcss": "^4.1.16",
|
||||||
"terser": "^5.44.0",
|
"terser": "^5.44.0",
|
||||||
"vite": "^7.3.3",
|
"vite": "^7.3.3",
|
||||||
"vitest": "^4.0.8"
|
"vitest": "^4.1.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,16 @@
|
|||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
:key="category.id"
|
:key="category.id"
|
||||||
class="flex items-center"
|
class="flex items-center gap-3"
|
||||||
@click.prevent="$emit('onSelect', category)"
|
@click.prevent="$emit('onSelect', category)"
|
||||||
>
|
>
|
||||||
<div class="avatar">
|
<div class="avatar shrink-0">
|
||||||
<div class="w-8 h-8 rounded">
|
<div class="w-10 h-10 rounded">
|
||||||
<img :src="category.image" :alt="category.name" loading="lazy" width="30" height="30" class="bg-base-400"/>
|
<img :src="category.image" :alt="category.name" loading="lazy" width="40" height="40" class="bg-base-400"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="ml-4 text-lg line-clamp-2">{{ category.name }}</h3>
|
<h3 class="text-base font-medium leading-snug line-clamp-2">{{ category.name }}</h3>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="px-4">
|
<section class="px-4">
|
||||||
<header class="flex justify-between items-end mb-4">
|
<header class="flex justify-between items-end mb-4 gap-2">
|
||||||
<div>
|
<div class="min-w-0">
|
||||||
<div v-if="title" class="font-bold uppercase">{{ title }}</div>
|
<div v-if="title" class="font-bold uppercase">{{ title }}</div>
|
||||||
<div v-if="description" class="text-sm text-base-content/50">{{ description }}</div>
|
<div v-if="description" class="text-sm text-base-content/50">{{ description }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="moreLink">
|
<div v-if="moreLink" class="shrink-0">
|
||||||
<RouterLink :to="moreLink" class="btn btn-soft btn-xs" @click="haptic.selectionChanged">
|
<RouterLink :to="moreLink" class="btn btn-soft btn-xs whitespace-nowrap" @click="haptic.selectionChanged">
|
||||||
{{ moreText || 'Смотреть всё' }}
|
{{ moreText || 'Смотреть всё' }}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
|
||||||
|
|||||||
@@ -2,10 +2,23 @@
|
|||||||
<BaseBlock
|
<BaseBlock
|
||||||
:title="block.title"
|
:title="block.title"
|
||||||
:description="block.description"
|
:description="block.description"
|
||||||
:moreLink="{name: 'product.categories.show', params: { category_id: block.data.category_id }}"
|
:moreLink="hasCategory ? {name: 'product.categories.show', params: { category_id: block.data.category_id }} : null"
|
||||||
:moreText="block.data.all_text"
|
:moreText="block.data.all_text"
|
||||||
>
|
>
|
||||||
|
<div v-if="!hasCategory" role="alert" class="alert alert-warning alert-soft">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||||
|
</svg>
|
||||||
|
<span>Для блока не выбрана категория.</span>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!hasProducts" role="alert" class="alert alert-soft">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<span>В этой категории пока нет товаров.</span>
|
||||||
|
</div>
|
||||||
<Swiper
|
<Swiper
|
||||||
|
v-else
|
||||||
class="select-none block-products-carousel"
|
class="select-none block-products-carousel"
|
||||||
:slides-per-view="block.data?.carousel?.slides_per_view || 2.5"
|
:slides-per-view="block.data?.carousel?.slides_per_view || 2.5"
|
||||||
:space-between="block.data?.carousel?.space_between || 20"
|
:space-between="block.data?.carousel?.space_between || 20"
|
||||||
@@ -36,6 +49,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import {computed} from "vue";
|
||||||
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
||||||
import {Swiper, SwiperSlide} from "swiper/vue";
|
import {Swiper, SwiperSlide} from "swiper/vue";
|
||||||
import {useHapticScroll} from "@/composables/useHapticScroll.js";
|
import {useHapticScroll} from "@/composables/useHapticScroll.js";
|
||||||
@@ -52,6 +66,9 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasCategory = computed(() => Boolean(props.block.data?.category_id));
|
||||||
|
const hasProducts = computed(() => (props.block.data?.products?.data?.length ?? 0) > 0);
|
||||||
|
|
||||||
const freeModeSettings = {
|
const freeModeSettings = {
|
||||||
enabled: props.block.data?.carousel?.freemode?.enabled || false,
|
enabled: props.block.data?.carousel?.freemode?.enabled || false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,10 +2,19 @@
|
|||||||
|
|
||||||
@custom-variant dark (&:where(.dark, .dark *));
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
|
@plugin "@tailwindcss/typography";
|
||||||
|
|
||||||
@plugin "daisyui" {
|
@plugin "daisyui" {
|
||||||
themes: all;
|
themes: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.prose :where(table):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
--color-base-100 - DaisyUI background
|
--color-base-100 - DaisyUI background
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,31 +9,22 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<button v-if="parentId && parentCategory" class="py-1 px-4 flex items-center mb-3 cursor-pointer" @click="goBack">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6 min-w-6">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<span class="ml-2 line-clamp-2">Назад к "{{ parentCategory.name }}"</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="parentCategory && parentCategory.children?.length && settings.show_category_products_button"
|
v-if="parentCategory && parentCategory.children?.length && settings.show_category_products_button"
|
||||||
class="py-2 px-4 flex items-center mb-3 cursor-pointer border-b w-full pb-2 border-base-200"
|
class="btn btn-soft btn-block btn-sm mb-3 justify-start"
|
||||||
@click.prevent="showProductsInParentCategory"
|
@click.prevent="showProductsInParentCategory"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5 shrink-0">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6ZM3.75 15.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6ZM13.5 15.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25A2.25 2.25 0 0 1 13.5 18v-2.25Z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6ZM3.75 15.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6ZM13.5 15.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25A2.25 2.25 0 0 1 13.5 18v-2.25Z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
<span>Показать все товары категории</span>
|
||||||
<span class="ml-2">Показать товары из "{{ parentCategory.name }}"</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<TransitionGroup
|
<TransitionGroup
|
||||||
name="stagger"
|
name="stagger"
|
||||||
tag="ul"
|
tag="ul"
|
||||||
appear
|
appear
|
||||||
class="space-y-4"
|
class="space-y-3"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
v-for="(category, i) in categories"
|
v-for="(category, i) in categories"
|
||||||
@@ -99,10 +90,6 @@ function onSelect(category) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function goBack() {
|
|
||||||
router.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showProductsInParentCategory() {
|
function showProductsInParentCategory() {
|
||||||
if (parentId.value != null) {
|
if (parentId.value != null) {
|
||||||
router.push({name: "product.categories.show", params: {category_id: parentId.value}});
|
router.push({name: "product.categories.show", params: {category_id: parentId.value}});
|
||||||
|
|||||||
@@ -87,7 +87,11 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Описание
|
Описание
|
||||||
</h3>
|
</h3>
|
||||||
<div class="prose prose-sm max-w-none text-base-content/80" v-html="product.description"></div>
|
<div
|
||||||
|
class="prose prose-sm max-w-none text-base-content/80"
|
||||||
|
v-html="product.description"
|
||||||
|
@click="handleDescriptionClick"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Attributes -->
|
<!-- Attributes -->
|
||||||
@@ -306,6 +310,29 @@ async function onCartBtnClick() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleDescriptionClick(event) {
|
||||||
|
const link = event.target.closest('a');
|
||||||
|
if (!link) return;
|
||||||
|
|
||||||
|
const href = link.getAttribute('href');
|
||||||
|
if (!href || href.startsWith('#') || href.startsWith('javascript:')) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const telegramWebApp = window.Telegram?.WebApp;
|
||||||
|
if (!telegramWebApp) {
|
||||||
|
window.open(href, '_blank', 'noopener,noreferrer');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^https?:\/\/(t|telegram)\.me\//i.test(href) || href.startsWith('tg://')) {
|
||||||
|
telegramWebApp.openTelegramLink(href);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
telegramWebApp.openLink(href, {try_instant_view: false});
|
||||||
|
}
|
||||||
|
|
||||||
function openProductInMarketplace() {
|
function openProductInMarketplace() {
|
||||||
if (!product.value.share) {
|
if (!product.value.share) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -517,25 +517,26 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/guzzle",
|
"name": "guzzlehttp/guzzle",
|
||||||
"version": "7.10.0",
|
"version": "7.13.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/guzzle/guzzle.git",
|
"url": "https://github.com/guzzle/guzzle.git",
|
||||||
"reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4"
|
"reference": "55901a76dfd2006a0cc012b9e3c5b487f796478d"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
|
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/55901a76dfd2006a0cc012b9e3c5b487f796478d",
|
||||||
"reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
|
"reference": "55901a76dfd2006a0cc012b9e3c5b487f796478d",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"guzzlehttp/promises": "^2.3",
|
"guzzlehttp/promises": "^2.5",
|
||||||
"guzzlehttp/psr7": "^2.8",
|
"guzzlehttp/psr7": "^2.12.3",
|
||||||
"php": "^7.2.5 || ^8.0",
|
"php": "^7.2.5 || ^8.0",
|
||||||
"psr/http-client": "^1.0",
|
"psr/http-client": "^1.0",
|
||||||
"symfony/deprecation-contracts": "^2.2 || ^3.0"
|
"symfony/deprecation-contracts": "^2.5 || ^3.0",
|
||||||
|
"symfony/polyfill-php80": "^1.25"
|
||||||
},
|
},
|
||||||
"provide": {
|
"provide": {
|
||||||
"psr/http-client-implementation": "1.0"
|
"psr/http-client-implementation": "1.0"
|
||||||
@@ -544,8 +545,9 @@
|
|||||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
"guzzle/client-integration-tests": "3.0.2",
|
"guzzle/client-integration-tests": "3.0.2",
|
||||||
|
"guzzlehttp/test-server": "^0.6",
|
||||||
"php-http/message-factory": "^1.1",
|
"php-http/message-factory": "^1.1",
|
||||||
"phpunit/phpunit": "^8.5.39 || ^9.6.20",
|
"phpunit/phpunit": "^8.5.52 || ^9.6.34",
|
||||||
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
@@ -623,7 +625,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/guzzle/guzzle/issues",
|
"issues": "https://github.com/guzzle/guzzle/issues",
|
||||||
"source": "https://github.com/guzzle/guzzle/tree/7.10.0"
|
"source": "https://github.com/guzzle/guzzle/tree/7.13.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -639,28 +641,29 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-08-23T22:36:01+00:00"
|
"time": "2026-06-29T20:14:18+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/promises",
|
"name": "guzzlehttp/promises",
|
||||||
"version": "2.3.0",
|
"version": "2.5.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/guzzle/promises.git",
|
"url": "https://github.com/guzzle/promises.git",
|
||||||
"reference": "481557b130ef3790cf82b713667b43030dc9c957"
|
"reference": "4360e982f87f5f258bf872d094647791db2f4c8e"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957",
|
"url": "https://api.github.com/repos/guzzle/promises/zipball/4360e982f87f5f258bf872d094647791db2f4c8e",
|
||||||
"reference": "481557b130ef3790cf82b713667b43030dc9c957",
|
"reference": "4360e982f87f5f258bf872d094647791db2f4c8e",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.2.5 || ^8.0"
|
"php": "^7.2.5 || ^8.0",
|
||||||
|
"symfony/deprecation-contracts": "^2.5 || ^3.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||||
"phpunit/phpunit": "^8.5.44 || ^9.6.25"
|
"phpunit/phpunit": "^8.5.52 || ^9.6.34"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
@@ -706,7 +709,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/guzzle/promises/issues",
|
"issues": "https://github.com/guzzle/promises/issues",
|
||||||
"source": "https://github.com/guzzle/promises/tree/2.3.0"
|
"source": "https://github.com/guzzle/promises/tree/2.5.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -722,27 +725,29 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-08-22T14:34:08+00:00"
|
"time": "2026-06-02T12:23:43+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/psr7",
|
"name": "guzzlehttp/psr7",
|
||||||
"version": "2.9.0",
|
"version": "2.12.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/guzzle/psr7.git",
|
"url": "https://github.com/guzzle/psr7.git",
|
||||||
"reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884"
|
"reference": "7ec62dc3f44aa218487dbed81a9bf9bc647be55d"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884",
|
"url": "https://api.github.com/repos/guzzle/psr7/zipball/7ec62dc3f44aa218487dbed81a9bf9bc647be55d",
|
||||||
"reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884",
|
"reference": "7ec62dc3f44aa218487dbed81a9bf9bc647be55d",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.2.5 || ^8.0",
|
"php": "^7.2.5 || ^8.0",
|
||||||
"psr/http-factory": "^1.0",
|
"psr/http-factory": "^1.0",
|
||||||
"psr/http-message": "^1.1 || ^2.0",
|
"psr/http-message": "^1.1 || ^2.0",
|
||||||
"ralouphie/getallheaders": "^3.0"
|
"ralouphie/getallheaders": "^3.0",
|
||||||
|
"symfony/deprecation-contracts": "^2.5 || ^3.0",
|
||||||
|
"symfony/polyfill-php80": "^1.25"
|
||||||
},
|
},
|
||||||
"provide": {
|
"provide": {
|
||||||
"psr/http-factory-implementation": "1.0",
|
"psr/http-factory-implementation": "1.0",
|
||||||
@@ -750,9 +755,9 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||||
"http-interop/http-factory-tests": "0.9.0",
|
"http-interop/http-factory-tests": "1.1.0",
|
||||||
"jshttp/mime-db": "1.54.0.1",
|
"jshttp/mime-db": "1.54.0.1",
|
||||||
"phpunit/phpunit": "^8.5.44 || ^9.6.25"
|
"phpunit/phpunit": "^8.5.52 || ^9.6.34"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
|
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
|
||||||
@@ -823,7 +828,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/guzzle/psr7/issues",
|
"issues": "https://github.com/guzzle/psr7/issues",
|
||||||
"source": "https://github.com/guzzle/psr7/tree/2.9.0"
|
"source": "https://github.com/guzzle/psr7/tree/2.12.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -839,7 +844,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2026-03-10T16:41:02+00:00"
|
"time": "2026-06-23T15:21:08+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "intervention/image",
|
"name": "intervention/image",
|
||||||
@@ -1865,16 +1870,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/cache",
|
"name": "symfony/cache",
|
||||||
"version": "v5.4.46",
|
"version": "v5.4.53",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/cache.git",
|
"url": "https://github.com/symfony/cache.git",
|
||||||
"reference": "0fe08ee32cec2748fbfea10c52d3ee02049e0f6b"
|
"reference": "bf581474737420d5c932ae80b868e253f465ee5b"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/cache/zipball/0fe08ee32cec2748fbfea10c52d3ee02049e0f6b",
|
"url": "https://api.github.com/repos/symfony/cache/zipball/bf581474737420d5c932ae80b868e253f465ee5b",
|
||||||
"reference": "0fe08ee32cec2748fbfea10c52d3ee02049e0f6b",
|
"reference": "bf581474737420d5c932ae80b868e253f465ee5b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1942,7 +1947,7 @@
|
|||||||
"psr6"
|
"psr6"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/cache/tree/v5.4.46"
|
"source": "https://github.com/symfony/cache/tree/v5.4.53"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -1953,12 +1958,16 @@
|
|||||||
"url": "https://github.com/fabpot",
|
"url": "https://github.com/fabpot",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-11-04T11:43:55+00:00"
|
"time": "2026-05-24T08:41:21+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/cache-contracts",
|
"name": "symfony/cache-contracts",
|
||||||
@@ -2622,7 +2631,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php73",
|
"name": "symfony/polyfill-php73",
|
||||||
"version": "v1.33.0",
|
"version": "v1.37.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-php73.git",
|
"url": "https://github.com/symfony/polyfill-php73.git",
|
||||||
@@ -2678,7 +2687,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0"
|
"source": "https://github.com/symfony/polyfill-php73/tree/v1.37.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -2702,16 +2711,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php80",
|
"name": "symfony/polyfill-php80",
|
||||||
"version": "v1.33.0",
|
"version": "v1.37.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
|
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -2762,7 +2771,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
|
"source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -2782,7 +2791,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-01-02T08:10:11+00:00"
|
"time": "2026-04-10T16:19:22+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php81",
|
"name": "symfony/polyfill-php81",
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ HTML,
|
|||||||
|
|
||||||
'pulse' => [
|
'pulse' => [
|
||||||
'api_key' => '',
|
'api_key' => '',
|
||||||
|
'heartbeat_enabled' => false,
|
||||||
'batch_size' => 50,
|
'batch_size' => 50,
|
||||||
'max_attempts' => 3,
|
'max_attempts' => 3,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class TeleCartPulseService
|
|||||||
private TeleCartEvent $eventModel;
|
private TeleCartEvent $eventModel;
|
||||||
private ?string $apiKey;
|
private ?string $apiKey;
|
||||||
private ?PayloadSigner $heartbeatPayloadSigner;
|
private ?PayloadSigner $heartbeatPayloadSigner;
|
||||||
|
private bool $heartbeatEnabled;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
TelegramInitDataDecoder $initDataDecoder,
|
TelegramInitDataDecoder $initDataDecoder,
|
||||||
@@ -30,7 +31,8 @@ class TeleCartPulseService
|
|||||||
LoggerInterface $logger,
|
LoggerInterface $logger,
|
||||||
TeleCartEvent $eventModel,
|
TeleCartEvent $eventModel,
|
||||||
?string $apiKey = null,
|
?string $apiKey = null,
|
||||||
?PayloadSigner $heartbeatPayloadSigner = null
|
?PayloadSigner $heartbeatPayloadSigner = null,
|
||||||
|
bool $heartbeatEnabled = false
|
||||||
) {
|
) {
|
||||||
$this->initDataDecoder = $initDataDecoder;
|
$this->initDataDecoder = $initDataDecoder;
|
||||||
$this->telegramService = $telegramService;
|
$this->telegramService = $telegramService;
|
||||||
@@ -39,6 +41,7 @@ class TeleCartPulseService
|
|||||||
$this->eventModel = $eventModel;
|
$this->eventModel = $eventModel;
|
||||||
$this->apiKey = $apiKey;
|
$this->apiKey = $apiKey;
|
||||||
$this->heartbeatPayloadSigner = $heartbeatPayloadSigner;
|
$this->heartbeatPayloadSigner = $heartbeatPayloadSigner;
|
||||||
|
$this->heartbeatEnabled = $heartbeatEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,6 +135,10 @@ class TeleCartPulseService
|
|||||||
*/
|
*/
|
||||||
public function handleHeartbeat(): void
|
public function handleHeartbeat(): void
|
||||||
{
|
{
|
||||||
|
if (! $this->heartbeatEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->cache->get('telecart_pulse_heartbeat')) {
|
if ($this->cache->get('telecart_pulse_heartbeat')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -193,6 +200,10 @@ class TeleCartPulseService
|
|||||||
*/
|
*/
|
||||||
private function pushHeartbeat(array $json): void
|
private function pushHeartbeat(array $json): void
|
||||||
{
|
{
|
||||||
|
$this->logger->debug('Pushing heartbeat', [
|
||||||
|
'data' => $json,
|
||||||
|
]);
|
||||||
|
|
||||||
$baseUri = rtrim(env('PULSE_API_HOST', 'http://localhost'), '/') . '/';
|
$baseUri = rtrim(env('PULSE_API_HOST', 'http://localhost'), '/') . '/';
|
||||||
|
|
||||||
$client = new Client([
|
$client = new Client([
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class TeleCartPulseServiceProvider extends ServiceProvider
|
|||||||
$this->container->singleton(TeleCartPulseService::class, function (Container $app) {
|
$this->container->singleton(TeleCartPulseService::class, function (Container $app) {
|
||||||
$heartbeatSecret = $app->getConfigValue('pulse.heartbeat_secret') ?? env('PULSE_HEARTBEAT_SECRET');
|
$heartbeatSecret = $app->getConfigValue('pulse.heartbeat_secret') ?? env('PULSE_HEARTBEAT_SECRET');
|
||||||
$heartbeatSigner = $heartbeatSecret ? new PayloadSigner($heartbeatSecret) : null;
|
$heartbeatSigner = $heartbeatSecret ? new PayloadSigner($heartbeatSecret) : null;
|
||||||
|
$heartbeatEnabled = (bool) $app->getConfigValue('pulse.heartbeat_enabled');
|
||||||
|
|
||||||
return new TeleCartPulseService(
|
return new TeleCartPulseService(
|
||||||
$app->get(TelegramInitDataDecoder::class),
|
$app->get(TelegramInitDataDecoder::class),
|
||||||
@@ -31,6 +32,7 @@ class TeleCartPulseServiceProvider extends ServiceProvider
|
|||||||
$app->get(TeleCartEvent::class),
|
$app->get(TeleCartEvent::class),
|
||||||
$app->getConfigValue('pulse.api_key'),
|
$app->getConfigValue('pulse.api_key'),
|
||||||
$heartbeatSigner,
|
$heartbeatSigner,
|
||||||
|
$heartbeatEnabled,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit\Framework\TeleCartPulse;
|
||||||
|
|
||||||
|
use Mockery as m;
|
||||||
|
use Openguru\OpenCartFramework\Cache\CacheInterface;
|
||||||
|
use Openguru\OpenCartFramework\TeleCartPulse\PayloadSigner;
|
||||||
|
use Openguru\OpenCartFramework\TeleCartPulse\TeleCartEvent;
|
||||||
|
use Openguru\OpenCartFramework\TeleCartPulse\TeleCartPulseService;
|
||||||
|
use Openguru\OpenCartFramework\Telegram\TelegramInitDataDecoder;
|
||||||
|
use Openguru\OpenCartFramework\Telegram\TelegramService;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
class TeleCartPulseServiceHeartbeatTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
m::close();
|
||||||
|
parent::tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDisabledHeartbeatSendsNothingAndTouchesNoCache(): void
|
||||||
|
{
|
||||||
|
$cache = m::mock(CacheInterface::class);
|
||||||
|
$cache->shouldNotReceive('get');
|
||||||
|
$cache->shouldNotReceive('set');
|
||||||
|
|
||||||
|
$telegramService = m::mock(TelegramService::class);
|
||||||
|
$telegramService->shouldNotReceive('getMe');
|
||||||
|
|
||||||
|
$service = new TeleCartPulseService(
|
||||||
|
m::mock(TelegramInitDataDecoder::class),
|
||||||
|
$telegramService,
|
||||||
|
$cache,
|
||||||
|
m::mock(LoggerInterface::class),
|
||||||
|
m::mock(TeleCartEvent::class),
|
||||||
|
'some-api-key',
|
||||||
|
m::mock(PayloadSigner::class),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
$service->handleHeartbeat();
|
||||||
|
|
||||||
|
// shouldNotReceive() expectations are verified on Mockery::close() in tearDown.
|
||||||
|
$this->addToAssertionCount(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEnabledHeartbeatPassesGateAndConsultsCache(): void
|
||||||
|
{
|
||||||
|
// Cache hit → метод выходит сразу после проверки кэша, не доходя до getMe()/HTTP.
|
||||||
|
$cache = m::mock(CacheInterface::class);
|
||||||
|
$cache->shouldReceive('get')->once()->with('telecart_pulse_heartbeat')->andReturn(time());
|
||||||
|
$cache->shouldNotReceive('set');
|
||||||
|
|
||||||
|
$telegramService = m::mock(TelegramService::class);
|
||||||
|
$telegramService->shouldNotReceive('getMe');
|
||||||
|
|
||||||
|
$service = new TeleCartPulseService(
|
||||||
|
m::mock(TelegramInitDataDecoder::class),
|
||||||
|
$telegramService,
|
||||||
|
$cache,
|
||||||
|
m::mock(LoggerInterface::class),
|
||||||
|
m::mock(TeleCartEvent::class),
|
||||||
|
'some-api-key',
|
||||||
|
m::mock(PayloadSigner::class),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
$service->handleHeartbeat();
|
||||||
|
|
||||||
|
$this->addToAssertionCount(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user