build(frontend): убрать неподдерживаемый плагин иконок и EOL-линтеры, audit до нуля

vite-plugin-svg-icons не обновлялся с 2022 года и был единственным источником
половины оставшихся предупреждений: svgo 2.8, postcss 5.2.18, braces 2.3.2 и
image-size 0.5.5 — у последней advisory прямо сообщает `Patched versions:
<0.0.0`, то есть исправленной версии не существует.

Задача, которую он решал, заняла один модуль: import.meta.glob собирает
семнадцать локальных SVG в скрытый спрайт со <symbol>. Компонентный подход
(unplugin-icons) здесь не подходит — имя иконки часто вычисляется в рантайме
(onlyOneChild.meta.icon, isFullscreen ? ... : ...), а поиск по id нужен именно
для этого. API <svg-icon icon-class="..."> не изменился.

Проверка на реальных ассетах поймала то, что иначе уехало бы в релиз: три
иконки из семнадцати не объявляют viewBox, задавая только width/height. Без
viewBox <use> рисует иконку в натуральную величину и обрезает её. Плагин
синтезировал viewBox сам; теперь это делает sprite.ts, а инвариант закреплён
приёмкой — иконка без обоих способов задать координаты роняет сборку.

Отдельно: typecheck поймал две ошибки уже в самом sprite.ts (SVGSVGElement
против HTMLElement из getElementById). Ровно то, ради чего он возвращался.

eslint 8 объявлен EOL и оставался вторым источником предупреждений. Переход на
eslint 10 потребовал flat config: eslintrc в 9 работает только через переменную
окружения, а в 10 удалён, поэтому обновление версии без смены формата было бы
отсрочкой на релиз. Набор правил сохранён прежним; .eslintignore свёрнут в
ignores, как требует новый формат.

Проверено, что flat config действительно линтит, а не молча пропускает: пробный
файл с неиспользуемой переменной и необъявленным именем даёт обе ошибки и в
.ts, и в .vue.

Оставшиеся предупреждения приходили из графов самих eslint 10 и stylelint 17,
то есть уже последних версий, — закрыты pnpm.overrides. Override для ajv
ограничен `table>ajv`: глобальный ломал eslint, который использует ajv 6.

pnpm audit по всему графу: 33 предупреждения -> 0.
typecheck/eslint/stylelint/build: 0. go build и go test с встроенным dist: OK.
This commit is contained in:
2026-08-30 07:41:31 +05:00
parent ddd1020a3a
commit 32ff47731c
9 changed files with 567 additions and 1687 deletions
+36
View File
@@ -187,6 +187,42 @@ run_fix20_acceptance_subset() {
log_step "Acceptance: bootstrap peer can pass auth smoke"
grep -q 'quota := int64(-1)' apps/dao/sqlite.go || fail "acceptance: bootstrap peer quota must be unlimited (-1), otherwise install auth smoke fails"
log_step "Acceptance: local SVG icons are rendered from an in-repo sprite"
# vite-plugin-svg-icons убран: он не обновлялся с 2022 года и тянул svgo 2.8,
# postcss 5.2.18 и image-size 0.5.5, у последней advisory прямо сообщает
# `Patched versions: <0.0.0` — исправленной версии не существует.
! grep -q '"vite-plugin-svg-icons"' apps/frontend/package.json \
|| fail "acceptance: неподдерживаемый vite-plugin-svg-icons вернулся в зависимости"
! grep -rq 'virtual:svg-icons-register' apps/frontend/src \
|| fail "acceptance: виртуальный модуль удалённого плагина иконок вернулся"
grep -q 'registerSvgIcons' apps/frontend/src/main.ts \
|| fail "acceptance: спрайт локальных иконок не регистрируется при старте приложения"
# Каждая иконка обязана давать symbol с viewBox.
#
# Без viewBox `<use>` не знает систему координат и рисует иконку в натуральную
# величину, обрезая её по размеру родительского svg. Три иконки из
# семнадцати его не объявляют — для них viewBox синтезируется из width/height,
# как это делал заменённый плагин. Проверка сторожит именно ассеты: иконка,
# добавленная без обоих способов задать координаты, иначе сломала бы
# отрисовку молча.
"$BUN_BIN" -e '
const fs = require("node:fs");
const dir = "apps/frontend/src/assets/icons";
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".svg"));
if (files.length === 0) throw new Error("каталог локальных иконок пуст");
const broken = [];
for (const file of files) {
const raw = fs.readFileSync(`${dir}/${file}`, "utf8");
const openTag = raw.replace(/<\?xml[\s\S]*?\?>/gi, "").replace(/<!DOCTYPE[\s\S]*?>/gi, "").match(/<svg\b[^>]*>/i);
if (!openTag) { broken.push(`${file}: нет корневого <svg>`); continue; }
const hasViewBox = /viewBox="[^"]+"/i.test(openTag[0]);
const hasSize = /width="[\d.]+[a-z%]*"/i.test(openTag[0]) && /height="[\d.]+[a-z%]*"/i.test(openTag[0]);
if (!hasViewBox && !hasSize) broken.push(`${file}: нет ни viewBox, ни пары width/height`);
}
if (broken.length) throw new Error("иконки без системы координат:\n" + broken.join("\n"));
' || fail "acceptance: локальная иконка не даёт корректный <symbol> для спрайта"
log_step "Acceptance: frontend i18n does not touch Pinia at module import"
! grep -q 'useAppStore' apps/frontend/src/lang/index.ts || fail "acceptance: lang/index.ts must not import/use Pinia store"