fix(rc5): harden frontend runtime and diagnostics

This commit is contained in:
2026-09-08 05:22:06 +05:00
parent 079094591b
commit b7e478f014
26 changed files with 707 additions and 169 deletions
+14 -9
View File
@@ -63,15 +63,16 @@ Windows и macOS можно использовать для редактиров
и сверяет каждый архив с контрольной суммой из `versions.env`.
6. Выполняет `verify_versions_contract`: рассинхрон версий роняет сборку до создания tarball.
7. Прогоняет тесты и типы оркестратора (`bun test`, `tsc --noEmit`).
8. Разрешает upstream-версию Hysteria, берёт ожидаемый SHA-256 из upstream `hashes.txt` и сверяет с ним скачанный артефакт.
9. Проходит compatibility gate: реальный бинарник Hysteria должен принять канонический конфиг HY2XS.
10. Копирует package skeleton.
11. Собирает install-only orchestrator в standalone binary.
12. Собирает frontend и backend HY2XS admin в Linux amd64 binary, проставляя версию админки через ldflags.
13. Прогоняет `go vet` и `go test` для HY2XS admin (после сборки frontend, потому что `go:embed all:dist` требует готовых ассетов).
14. Записывает metadata и checksums.
15. Создаёт `dist/hy2xs-install-<version>.tar.gz`.
16. Проверяет архив и прогоняет acceptance-проверки.
8. Рано прогоняет dependency-free контракты панели.
9. Разрешает upstream-версию Hysteria, берёт ожидаемый SHA-256 из upstream `hashes.txt` и сверяет с ним скачанный артефакт.
10. Проходит compatibility gate: реальный бинарник Hysteria должен принять канонический конфиг HY2XS.
11. Копирует package skeleton и собирает install-only orchestrator.
12. Устанавливает frozen frontend lock-граф, runtime-компилирует все сообщения RU/EN реальным `vue-i18n`, проверяет типы и собирает production bundle.
13. Собирает backend HY2XS admin в Linux amd64 binary, проставляя версию через ldflags.
14. Прогоняет `go vet` и `go test` для HY2XS admin (после сборки frontend, потому что `go:embed all:dist` требует готовых ассетов).
15. Проверяет зависимости через `govulncheck ./...` и `pnpm audit` по всему lock-графу.
16. Записывает metadata и checksums.
17. Создаёт архив и прогоняет его acceptance-проверки.
## Контракт версий
@@ -99,6 +100,10 @@ Go-коде больше нет: она уже успела разъехатьс
только политика `HYSTERIA_CHANNEL`, результат резолва — в
[`hysteria-lock.env`](hysteria-lock.env)).
Security overrides frontend находятся в `apps/frontend/pnpm-workspace.yaml`:
это канонический файл настроек pnpm. Поле `pnpm` в `package.json` для этой цели
не используется, потому что новые версии package manager его игнорируют.
## Версия Hysteria: разрешение и compatibility gate
Builder не хранит версию Hysteria вручную. По умолчанию он определяет последнюю стабильную версию сам и замораживает её в пакете.
+3 -3
View File
@@ -43,9 +43,9 @@ main() {
log_step "Checking orchestrator contracts"
run_orchestrator_tests
# Контракты панели проверяются рано: они не требуют ни собранного bundle, ни
# установленных зависимостей, и падать на них после резолва Hysteria и сборки
# артефактов означало бы платить минутами за ошибку, видимую сразу.
# Dependency-free контракты панели проверяются рано. Runtime-компиляция
# словарей запускается внутри bundle_ui сразу после frozen install, потому
# что ей нужен реальный vue-i18n из frontend lock-графа.
log_step "Checking HY2XS admin frontend contracts"
run_frontend_tests
+21 -2
View File
@@ -1345,14 +1345,20 @@ $piped_matcher"
|| fail "acceptance: прогон тестов оркестратора не фиксируется результатом"
grep -q 'ADMIN_TESTS_PASSED' tools/build/lib/package.sh \
|| fail "acceptance: прогон тестов админки не фиксируется результатом"
grep -q 'FRONTEND_CONTRACT_TESTS_PASSED' tools/build/lib/package.sh \
|| fail "acceptance: ранние контракты панели не фиксируются результатом"
grep -q 'FRONTEND_RUNTIME_TESTS_PASSED' tools/build/lib/package.sh \
|| fail "acceptance: runtime-компиляция локализации не фиксируется результатом"
grep -q 'FRONTEND_TESTS_PASSED' tools/build/lib/package.sh \
|| fail "acceptance: прогон контрактных тестов панели не фиксируется результатом"
|| fail "acceptance: общий результат тестов панели не фиксируется"
"$BUN_BIN" -e '
const source = require("node:fs").readFileSync("tools/build/lib/package.sh", "utf8");
for (const [fn, flag] of [
["run_orchestrator_tests()", "ORCHESTRATOR_TESTS_PASSED=\"true\""],
["run_admin_tests()", "ADMIN_TESTS_PASSED=\"true\""],
["run_frontend_tests()", "FRONTEND_TESTS_PASSED=\"true\""]
["run_frontend_tests()", "FRONTEND_CONTRACT_TESTS_PASSED=\"true\""],
["bundle_ui()", "FRONTEND_RUNTIME_TESTS_PASSED=\"true\""],
["bundle_ui()", "FRONTEND_TESTS_PASSED=\"true\""]
]) {
const start = source.indexOf(fn);
if (start < 0) throw new Error("не найдена функция " + fn);
@@ -1366,6 +1372,19 @@ $piped_matcher"
if (set < 0) throw new Error(fn + ": результат прогона не фиксируется");
if (set < run) throw new Error(fn + ": результат объявляется раньше, чем получен");
}
const bundleStart = source.indexOf("bundle_ui()");
const bundleRest = source.slice(bundleStart);
const bundleEnd = bundleRest.search(/\n\}[\r\n]/);
const bundle = bundleEnd < 0 ? bundleRest : bundleRest.slice(0, bundleEnd);
const install = bundle.indexOf("install --frozen-lockfile");
const runtime = bundle.indexOf("test test/i18n-runtime.test.ts");
const typecheck = bundle.indexOf("run typecheck");
const build = bundle.indexOf("run build:prod");
if (install < 0 || runtime < 0) throw new Error("runtime i18n gate не найден в bundle_ui");
if (!(install < runtime && runtime < typecheck && typecheck < build)) {
throw new Error("порядок frontend gate обязан быть install -> i18n runtime -> typecheck -> build");
}
' || fail "acceptance: утверждение о пройденных тестах обязано следовать за прогоном"
# Детектор гонок — часть прогона админки, а не пожелание.
+19 -4
View File
@@ -64,7 +64,7 @@ run_orchestrator_tests() {
}
run_frontend_tests() {
# Контракты панели, которые не проверяются ни типами, ни сборкой bundle:
# Ранние контракты панели, которые не требуют установленных зависимостей:
# цвет иконок в спрайте, совпадение словарей локализации, соответствие кодов
# ошибок серверным константам, единственность адреса атрибуции.
#
@@ -80,8 +80,8 @@ run_frontend_tests() {
"$BUN_BIN" test tools/test/frontend-sprite.test.ts tools/test/frontend-contract.test.ts \
|| fail "HY2XS admin frontend contract tests failed"
FRONTEND_TESTS_PASSED="true"
export FRONTEND_TESTS_PASSED
FRONTEND_CONTRACT_TESTS_PASSED="true"
export FRONTEND_CONTRACT_TESTS_PASSED
}
run_admin_tests() {
@@ -146,6 +146,9 @@ build_orchestrator() {
bundle_ui() {
local ui_src="${UI_SRC:-apps}"
[ "${FRONTEND_CONTRACT_TESTS_PASSED:-false}" = "true" ] \
|| fail "bundle_ui: ранние контракты панели не проверялись"
rm -rf "$ADMIN_BUILD_DIR"
mkdir -p "$ADMIN_BUILD_DIR"
@@ -164,6 +167,12 @@ bundle_ui() {
"$PNPM_BIN" install --frozen-lockfile
# Словари являются JavaScript-объектами и компилируются vue-i18n только
# при вызове t(). Поэтому ни vue-tsc, ни Vite не видят синтаксическую
# ошибку сообщения. Gate использует тот же установленный runtime, обходит
# все строки обоих языков и выполняется до typecheck и production bundle.
"$BUN_BIN" test test/i18n-runtime.test.ts
# Проверка типов идёт ПЕРЕД сборкой bundle, и это обязательный шаг релиза.
#
# Раньше её здесь не было вовсе: `build:prod` означал
@@ -176,7 +185,7 @@ bundle_ui() {
# кода, который не проходит проверку типов, нет.
"$PNPM_BIN" run typecheck
"$PNPM_BIN" run build:prod
)
) || fail "HY2XS admin frontend runtime tests or bundle failed"
# Версия админки приходит из versions.env через ldflags: один и тот же
# исполняемый файл обязан знать версию пакета, из которого он собран.
@@ -196,6 +205,12 @@ bundle_ui() {
mkdir -p "$STAGE_DIR/ui/hy2xs-admin/docs/sql"
install -m 0644 "$ui_src/docs/sql/schema.sql" "$STAGE_DIR/ui/hy2xs-admin/docs/sql/schema.sql"
fi
# Оба утверждения выставляются только после dependency-free контрактов,
# runtime-компиляции словарей и успешной сборки production bundle.
FRONTEND_RUNTIME_TESTS_PASSED="true"
FRONTEND_TESTS_PASSED="true"
export FRONTEND_RUNTIME_TESTS_PASSED FRONTEND_TESTS_PASSED
}
write_metadata() {