docs: сделать проверку типов frontend release gate и убрать известное ограничение

bundle_ui запускает `pnpm run typecheck` перед сборкой bundle. И наличие шага,
и его порядок закреплены приёмкой — вместе с требованием vue-tsc версии 3 и
выше и с запретом снова совмещать сборку и проверку в build:prod.

Из docs/02 убран раздел «Известное ограничение: проверка типов frontend почти
ничего не проверяет» и заменён описанием действующего контракта. Прогноз в нём
был близок, но неточен: ошибок оказалось 142, а не ~155, и класс DefaultRow/
PeerVo на Element Plus 2.3 не существовал вовсе — он появился вместе с
обновлением Element Plus.

docs/04 получил описание модели отображения (третий слой рядом с типизированной
моделью и сырым YAML) и раздел о том, что страница Hysteria теперь read-only на
всех уровнях, а не только визуально.

docs/11: команды проверки frontend и dev doctor в раздел запуска, семь новых
пунктов приёмки.
This commit is contained in:
2026-08-30 07:44:51 +05:00
parent 32ff47731c
commit 219bb364bc
6 changed files with 209 additions and 18 deletions
+28 -18
View File
@@ -111,28 +111,38 @@ Bun обновляется отдельно от остальных: оркес
полного прохода `bun test → tsc → compile → приёмка на Debian`, а не строки в
общем патче.
### Известное ограничение: проверка типов frontend почти ничего не проверяет
### Проверка типов frontend — обязательный шаг релиза
`pnpm run build:prod` выполняет `vite build && vue-tsc --noEmit`, но `vue-tsc`
здесь версии `0.35.0` (2022 год) и шаблоны Vue практически не типизирует.
Проверка проходит зелёной, не давая гарантии, которую обещает.
```text
pnpm run typecheck → vue-tsc --noEmit → ОБЯЗАН пройти
pnpm run build:prod → vite build
pnpm run verify → typecheck, затем build
```
Замер сделан: на паре `typescript@5.9` + `vue-tsc@2.2` тот же исходный код даёт
**около 155 ошибок типов** в четырёх файлах — почти все одного вида
(`possibly 'undefined'` при обращении к необязательным полям модели конфига
Hysteria в шаблоне) плюс несовместимость `DefaultRow` с `PeerVo` в слотах
таблицы пиров.
`bundle_ui()` запускает `typecheck` **до** сборки bundle: собирать production
bundle из кода, который не проходит проверку типов, незачем. Порядок и сам факт
наличия шага проверяются приёмкой.
Это не дефект безопасности и не блокер релиза: ошибки существуют в коде уже
сейчас и ни на что в рантайме не влияют. Но обновление typechecker'а тянет за
собой обновление `vue` (3.2 → 3.5, иначе `vue-tsc` 2.x не разбирает
`JSX.IntrinsicElements`), а за ним — `element-plus`, `pinia` и `vue-router`.
То есть это отдельная работа с собственной проверкой на живой панели, а не
строка в security-патче.
До v1 этой гарантии не было. `build:prod` означал `vite build && vue-tsc
--noEmit`, но `vue-tsc` был версии `0.35.0` (2022 год) и шаблоны Vue
практически не типизировал: проверка проходила зелёной, не давая гарантии,
которую обещает. Хуже того — на Vue 3.5 она ломается сама, потому что не знает
`vue/jsx-runtime`, то есть пережить обновление Vue всё равно не могла.
Ограничение на безопасность не влияет: уязвимые пакеты frontend обновляются
независимо от версии typechecker'а, движением lockfile внутри уже объявленных
диапазонов (см. `pnpm.overrides` в `apps/frontend/package.json`).
Современный `vue-tsc 3.3` на том же коде дал **142 ошибки**: 141 × `TS18048`
(«possibly undefined» при обращении к необязательным секциям конфига Hysteria в
шаблоне) и одна `TS2322`, всё в двух файлах представления Hysteria. Ожидавшегося
класса «`DefaultRow` несовместим с `PeerVo`» на Element Plus 2.3 не было вовсе —
он появился позже, вместе с обновлением Element Plus до 2.14, где слоты таблицы
типизированы строже.
Закрыто это не подавлением, а границей: `api/config/hysteriaViewModel.ts`
превращает ответ сервера в модель, где присутствие каждой секции — свойство
типа. Подробности — в [docs/04](04-admin-panel.md).
Контракт теперь читается так:
> проверка типов SFC-шаблонов проходит, и это доказывает сборка, а не намерение.
### Проверка зависимостей на уязвимости
+64
View File
@@ -354,6 +354,70 @@ AES-GCM. Значение без этого префикса — не «форм
- будущие версии Hysteria не ломают экспорт только потому, что backend и frontend ещё не научились показывать новый параметр;
- это прямое следствие модели «latest stable на сборке»: схема upstream может опережать модель HY2XS.
### Третий слой: модель отображения
У типизированной модели есть подслой, о котором стоит сказать отдельно, потому
что он определяет, как устроены шаблоны страницы Hysteria.
`Hysteria2ServerConfig` описывает то, что **приходит по сети**, и почти все его
секции необязательны — ровно так же, как в upstream YAML. Форма же обращается к
ним напрямую: `dataForm.tls.cert`, `dataForm.acme.dns.config`,
`dataForm.resolver.https.sni`.
Пока проверка типов SFC-шаблонов не работала, это выглядело безобидно.
Современный `vue-tsc` даёт на этом 141 ошибку `TS18048` — и он прав: обращение
через возможно отсутствующий объект падает в рантайме. Спасало то, что форма
строится merge'ем поверх полного объекта значений по умолчанию, то есть
инвариант «секция есть всегда» существовал, но держался на порядке присваиваний
внутри компонента и нигде не был выражен типом.
Закрыто одним преобразованием на границе, а не 141 оператором `?.` и не
`as any`:
```text
ответ API (Hysteria2ServerConfig, секции необязательны)
normalizeHysteriaViewModel()
Hysteria2ServerConfigView — все секции обязательны
шаблон
```
`Hysteria2ServerConfigView` выводится из `Hysteria2ServerConfig` типом, а не
пишется вторым списком полей. Поэтому новая секция в схеме ломает компиляцию на
объекте значений по умолчанию — то есть поле upstream нельзя молча не
отобразить.
Побочное следствие: `v-if` в шаблоне перестали проверять присутствие секции и
проверяют только то, что действительно определяет выбор ветки. Например для
обфускации это `dataForm.obfs.type === 'gecko'` вместо
`dataForm.obfs.type === 'gecko' && dataForm.obfs.gecko` — вторая половина
дублировала первую и существовала только из-за необязательности типа.
Этот слой не участвует в экспорте: выгрузка идёт от исходного YAML и сохраняет
неизвестные поля, поэтому их потеря в модели отображения безвредна.
### Страница Hysteria — только чтение, и теперь это верно на всех уровнях
Страница отрисована с `:disabled="true"` и прямо сообщает, что конфигом владеет
`hy2xs-orchestrator reconfigure`. Маршрутов записи серверного конфига в API нет
— они удалены вместе с мёртвым updater/config-write слоем.
Тем не менее на ней жили три полноценных редактора: outbounds (кнопка «+»,
диалог создания, удаление), список значений (перетаскивание тегов, добавление,
удаление) и словарь «ключ — значение». Ни один не мог ничего сохранить: значения
передаются в них как `:outbounds=`, `:tags=`, `:map-object=` — без `v-model`,
то есть у их событий `update:*` нет ни одного слушателя. Оператор мог добавить
outbound, увидеть его в списке и уйти в уверенности, что изменил конфигурацию
сервера; изменения не переживали даже переключения вкладки.
Все три приведены к отображению. У одного из них цена была ещё и измеримой:
редактор списка значений работал на `vuedraggable`, которая поставляется
UMD-сборкой, поэтому её `require("vue")` разрешался в полную сборку Vue вместе с
рантайм-компилятором шаблонов — около полумегабайта в bundle ради
перетаскивания тегов в недоступной для редактирования форме.
### Санитайз экспорта
Экспортируемый файл покидает сервер, поэтому секреты из него вырезаются:
+13
View File
@@ -13,6 +13,12 @@ cd orchestrator && bun install --frozen-lockfile && bun run check && bun test
# Тесты и статический анализ HY2XS admin
cd apps && go vet ./... && go test ./...
# Проверка типов и сборка frontend
cd apps/frontend && pnpm install --frozen-lockfile && pnpm run verify
# Сверка среды разработки с versions.env (ничего не меняет)
./tools/dev/doctor.sh
# Полный E2E с реальным клиентом Hysteria (Debian 13 amd64; нужен Go)
HYSTERIA_BIN=/usr/local/bin/hysteria ./tools/test/e2e-hysteria.sh
@@ -866,3 +872,10 @@ hy2xs-orchestrator doctor
41. `apps/go.mod` объявляет `toolchain`, совпадающий с `GO_VERSION` из `versions.env`
42. `tools/dev/doctor.sh` / `doctor.ps1` показывают расхождение среды разработки с `versions.env`
43. маршруты-алиасы `/:id/client-url` и `/:id/qr` удалены и не входят в публичный API v1
44. `pnpm run typecheck` (`vue-tsc --noEmit`) проходит без ошибок и является обязательным шагом сборки
45. проверка типов идёт до сборки bundle, а не после
46. `vue-tsc` версии 3 и выше: 0.x проверку шаблонов не выполняет
47. `pnpm audit` по всему графу зависимостей frontend не находит уязвимостей
48. локальные SVG-иконки собираются спрайтом из репозитория, без `vite-plugin-svg-icons`
49. каждая иконка задаёт систему координат: `viewBox` либо пара `width`/`height`
50. страница конфига Hysteria не содержит элементов управления, которые ничего не сохраняют