diff --git a/dev/contour-transfer-guide.md b/dev/contour-transfer-guide.md index 169acdd..b1d633d 100644 --- a/dev/contour-transfer-guide.md +++ b/dev/contour-transfer-guide.md @@ -78,8 +78,12 @@ docs/scripts/prepare-contour-snapshot.sh scan-gateway /tmp/contour (переменная `PYTHON`, по умолчанию `python3.11`); удаляет цель `docker-build` и её упоминания в `check-full`, `.PHONY`, `help`. 4. Заменяет ссылки на внешний Gitea на `RSHB/#`. -5. Пишет `SNAPSHOT.md` (источник, хеш, дата, что вырезано). -6. Проверяет: нет `.claude`/`.codegraph`/`CLAUDE.md`/`openspec/config.yaml`/планов ни на какой глубине, нет `Dockerfile`, нет локальных путей (`/Users/…`, `/home/…`), нет `uv` и +5. Только arm-core: урезает `web/package.json` до зависимостей сборки (раздел 3.2) и правит цель `web-check`. +6. Пишет `SNAPSHOT.md` (источник, хеш, дата, что вырезано и какие пакеты/скрипты фронта убраны). +7. Только arm-core: собирает урезанный `web/` (`npm install` + `npm run build` во временной копии); не собралось — снимок + не выдаётся (код 1, «пакет, нужный сборке, не в разрешённом списке»). Для тестов без сети проверку отключает + `SNAPSHOT_SKIP_WEB_BUILD=1`. +8. Проверяет: нет `.claude`/`.codegraph`/`CLAUDE.md`/`openspec/config.yaml`/планов ни на какой глубине, нет `Dockerfile`, нет локальных путей (`/Users/…`, `/home/…`), нет `uv` и `git.aepif.ru`, на месте платформенные файлы (`raisa_config.yml`, `setup/`, `run_server.py`, `requirements.txt`, `.env.test`, `contracts/`). Любая проблема — код возврата 1 и список. @@ -114,6 +118,35 @@ arm-web или откатить, иначе автоперенос их затё Проверено 06.10.2026: снимки arm-core@1d7f2c8, tabular-parser@3b32462, scan-gateway@dffc18a проходят `make check` без `docs` и `fixtures` (1084 / 1087 / 726 passed, пропуски только по причинам из раздела 5). +### 3.2. Урезанный `web/package.json` в снимке arm-core + +В контуре фронт только собирается (`npm install` через Nexus + `rsbuild build`). Зеркало npm банка не содержит свежих +версий инструментов разработки (уже ломались на vitest — в зеркале ≤3.1.4; затем eslint, typescript-eslint, prettier из +arm-web #29), поэтому снимок оставляет только нужное сборке (решение владельца 16.10.2026, AURORA-1635, issue docs #112). +Разработка и проверки фронта (`vitest`, `eslint`, `prettier`, `biome`, `tsc`) идут снаружи, где npm доступен. + +Разрешённый список — массивы `WEB_DEV_ALLOW`, `WEB_OPTIONAL_KEEP_PREFIX`, `WEB_SCRIPTS_KEEP` в начале +`prepare-contour-snapshot.sh`: + +| Раздел `package.json` | Остаётся | Убирается | +|---|---|---| +| `dependencies` | всё | — | +| `devDependencies` | `@rsbuild/core`, `@rsbuild/plugin-react`, `tailwindcss`, `daisyui` (`rsbuild.config.ts`, `postcss.config.cjs`, `tailwind.config.js`) | всё остальное: eslint и плагины, prettier, biome, typescript, `@types/*`, vitest, globals | +| `optionalDependencies` | `raisa-*` | инструменты разработки (`@biomejs/cli-linux-x64-musl`) | +| `scripts` | `build`, `start`, `preview` | `test`, `lint`, `format` и любые новые | + +`make web-check` в снимке — только `npm install` и `npm run build` (без `biome` и `tsc`); `check-full` ссылается на него как +раньше. Конфиги инструментов (`biome.json`, `eslint.config.js`, `tsconfig.json`) в `web/` остаются, сборка их не читает. +Что убрано в конкретном снимке — в `SNAPSHOT.md`. + +Защита от обратной ошибки: скрипт сам собирает урезанный `web/` во временной копии (снимок остаётся без +`node_modules`/`dist`/`package-lock.json`). Если сборка не прошла, значит ей понадобился пакет вне списка: добавьте его в +`WEB_DEV_ALLOW` и повторите снимок. + +**Правило обратного потока:** `web/package.json` из контура наружу не переносить — он урезан (нет eslint, prettier, vitest, +typescript и скриптов `test`/`lint`/`format`); источник истины — `arm-web`, а `arm-core/web` синхронизируется из него +(раздел 3.1). Правки зависимостей в контуре возвращаются в arm-web вручную, только по существу, а не файлом целиком. + ## 4. Как положить снимок в контур Репозитории контура — проекты GitLab, по одному на сервис, имена как снаружи. Первый перенос и каждый следующий делаются @@ -156,7 +189,7 @@ make check # ruff, mypy, pytest без интеграционных т compose из `docs` либо Docker. Интеграционные тесты против PostgreSQL/NATS/MinIO прогоняются снаружи перед каждым снимком; пайплайну платформы отдаётся `make check`. -Фронт arm-core: `make web-check` (`npm install` через `.npmrc` Nexus, biome, tsc, `npm run build`). В контуре +Фронт arm-core: `make web-check` (в снимке — `npm install` через `.npmrc` Nexus и `npm run build`; biome и tsc убраны вместе с пакетами, раздел 3.2). В контуре `raisa-fastapi-protected-web` и `raisa-ui` ставятся штатно, снаружи они в `optionalDependencies` и пропускаются. `web/package-lock.json` снаружи не коммитится (OQ-63) — в контуре lock-файл появится при первом `npm install`, решение о его коммите за контуром. diff --git a/scripts/prepare-contour-snapshot.sh b/scripts/prepare-contour-snapshot.sh index efd9d1a..edc37fc 100755 --- a/scripts/prepare-contour-snapshot.sh +++ b/scripts/prepare-contour-snapshot.sh @@ -17,6 +17,15 @@ # issue. Без терминала вопрос не задаётся: нужен флаг --web-as-is, иначе код 3. Расхождение (прямые правки web/ в arm-core) — # всегда стоп. arm-web ищется рядом с репозиторием (../arm-web) или в $ARM_WEB; если его нет — предупреждение, снимок делается. # $SYNC_WEB_ARGS — дополнительные аргументы sync-web.sh (для теста: --dry-run --wt-root …). +# $SNAPSHOT_SKIP_WEB_BUILD=1 — не проверять сборку web/ (для тестов без сети); по умолчанию проверка включена. +# +# Встроенный фронт arm-core в контуре только собирается (npm install через Nexus + rsbuild build), а зеркало npm банка не +# содержит свежих версий инструментов разработки. Поэтому в снимке web/package.json урезается по РАЗРЕШЁННОМУ списку +# (решение владельца 16.10.2026); всё остальное — eslint, prettier, biome, typescript, vitest, @types/* — разработка +# и проверки идут снаружи. Список — единственное место, которое правят, если сборке понадобится ещё пакет. +WEB_DEV_ALLOW=(@rsbuild/core @rsbuild/plugin-react tailwindcss daisyui) # devDependencies, нужные сборке +WEB_OPTIONAL_KEEP_PREFIX="raisa-" # optionalDependencies: платформенные пакеты +WEB_SCRIPTS_KEEP=(build start preview) # scripts, работающие без инструментов разработки set -euo pipefail usage() { echo "usage: $0 [--web-as-is] [ref]" >&2; exit 2; } @@ -139,6 +148,17 @@ while i < len(lines): if i < len(lines) and lines[i] == "" and out and out[-1] == "": i += 1 continue + # web-check в контуре: только npm install и npm run build (biome/tsc убраны вместе с пакетами) + if line.startswith("web-check:"): + out.append(line) + i += 1 + while i < len(lines) and lines[i].startswith("\t"): + if "npx " not in lines[i]: + out.append(lines[i]) + i += 1 + continue + if line.startswith("# Фронт:"): + line = line.replace("biome, типы, ", "") if line.startswith("check-full:"): line = re.sub(r"\s+docker-build\b", "", line) if line.startswith("\t$(MAKE) docker-build"): @@ -157,6 +177,44 @@ if not venv_patched: path.write_text("\n".join(out) + "\n", encoding="utf-8") PY +# --- 2a. arm-core: web/package.json только с зависимостями сборки --------------------------------------------------------- +WEB_REMOVED="" +if [ -f "$DEST/web/package.json" ]; then + WEB_REMOVED=$(python3 -I - "$DEST/web/package.json" "$WEB_OPTIONAL_KEEP_PREFIX" "${WEB_DEV_ALLOW[*]}" "${WEB_SCRIPTS_KEEP[*]}" <<'PY' +import json +import sys +from pathlib import Path + +path = Path(sys.argv[1]) +optional_prefix = sys.argv[2] +dev_allow = sys.argv[3].split() +scripts_keep = sys.argv[4].split() +pkg = json.loads(path.read_text(encoding="utf-8")) + +missing = [d for d in dev_allow if d not in pkg.get("devDependencies", {})] +if missing: + sys.exit(f"web/package.json: в devDependencies нет {', '.join(missing)} из разрешённого списка — скрипт устарел относительно репозитория") + +dev = pkg.get("devDependencies", {}) +opt = pkg.get("optionalDependencies", {}) +scripts = pkg.get("scripts", {}) +removed_dev = [n for n in dev if n not in dev_allow] +removed_opt = [n for n in opt if not n.startswith(optional_prefix)] +removed_scripts = [n for n in scripts if n not in scripts_keep] + +pkg["devDependencies"] = {n: v for n, v in dev.items() if n in dev_allow} +if "optionalDependencies" in pkg: + pkg["optionalDependencies"] = {n: v for n, v in opt.items() if n.startswith(optional_prefix)} +pkg["scripts"] = {n: v for n, v in scripts.items() if n in scripts_keep} +path.write_text(json.dumps(pkg, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + +print("devDependencies: " + (", ".join(removed_dev) or "—")) +print("optionalDependencies: " + (", ".join(removed_opt) or "—")) +print("scripts: " + (", ".join(removed_scripts) or "—")) +PY +) +fi + # --- 3. Ссылки на внешний Gitea → имя репозитория и номер --------------------------------------------------------- # В openspec-архивах и README встречаются ссылки вида https://git.aepif.ru/RSHB//issues/5 и pulls/7. while IFS= read -r file; do @@ -189,6 +247,17 @@ cat > "$DEST/SNAPSHOT.md" <> "$DEST/SNAPSHOT.md" +fi if [ -n "$WEB_LAG" ]; then printf '\nВстроенный фронт \`web/\` на момент снимка %s.\n' "$WEB_LAG" >> "$DEST/SNAPSHOT.md" fi @@ -210,6 +279,23 @@ fi for p in raisa_config.yml setup/setup.sh setup/pip.conf api/run_server.py api/requirements.txt api/.env.test api/contracts; do if [ ! -e "$DEST/$p" ]; then echo "ошибка: платформенный файл $p отсутствует" >&2; fail=1; fi done + +# Сборка урезанного web/ собирается в ВРЕМЕННОЙ копии: снимок остаётся без node_modules/dist/package-lock.json, а упавшая +# проверка не оставляет полуготовых файлов. Нужен npm и доступ к реестру (в контуре — Nexus, снаружи — публичный npm; +# raisa-* опциональны и снаружи пропускаются). Падение сборки почти всегда значит: пакет, нужный сборке, не в WEB_DEV_ALLOW. +if [ -f "$DEST/web/package.json" ] && [ "${SNAPSHOT_SKIP_WEB_BUILD:-0}" != 1 ]; then + echo "проверка сборки урезанного web/ (npm install + npm run build)…" >&2 + BUILD_TMP=$(mktemp -d) + cp -R "$DEST/web" "$BUILD_TMP/web" + if ! { (cd "$BUILD_TMP/web" && npm install --no-audit --no-fund && npm run build) >"$BUILD_TMP/build.log" 2>&1; }; then + tail -n 30 "$BUILD_TMP/build.log" >&2 + rm -rf "$BUILD_TMP" "$DEST" + echo "ошибка: сборка урезанного web/ не прошла — пакет, нужный сборке, не в разрешённом списке (WEB_DEV_ALLOW в $(basename "$0"))." >&2 + echo "снимок не выдан." >&2 + exit 1 + fi + rm -rf "$BUILD_TMP" +fi [ "$fail" = 0 ] || exit 1 echo "снимок готов: $DEST" diff --git a/scripts/test_prepare_contour_snapshot.sh b/scripts/test_prepare_contour_snapshot.sh index e27b150..1385ed2 100755 --- a/scripts/test_prepare_contour_snapshot.sh +++ b/scripts/test_prepare_contour_snapshot.sh @@ -14,7 +14,7 @@ R="$WORK/src/arm-core" put() { mkdir -p "$(dirname "$R/$1")"; printf '%s\n' "${2:-x}" > "$R/$1"; } # платформенные файлы, без которых проверка снимка падает -printf 'venv:\n\tuv venv api/.venv\n' > "$R/Makefile" +printf 'venv:\n\tuv venv api/.venv\n\n# Фронт: biome, типы, сборка\nweb-check:\n\tcd web && npm install\n\tcd web && npx biome check src\n\tcd web && npx tsc --noEmit\n\tcd web && npm run build\n\ncheck-full: check web-check\n' > "$R/Makefile" for p in raisa_config.yml setup/setup.sh setup/pip.conf api/run_server.py api/requirements.txt api/.env.test \ api/contracts/schema.json; do put "$p"; done # корень: артефакты агента и локальная сборка @@ -30,6 +30,27 @@ done put web/openspec/specs/osv-ui/spec.md put web/openspec/changes/archive/2026-10-08-demo/design.md put web/src/App.tsx +cat > "$R/web/package.json" <<'JSON' +{ + "name": "frontend", + "private": true, + "scripts": { + "build": "rsbuild build", + "test": "vitest run", + "format": "eslint --fix src && prettier --write src", + "lint": "eslint src && prettier --check src", + "start": "rsbuild dev --open", + "preview": "rsbuild preview" + }, + "dependencies": {"antd": "^6.5.0", "react": "^18.3.1"}, + "devDependencies": { + "@biomejs/biome": "1.9.4", "@rsbuild/core": "1.6.7", "@rsbuild/plugin-react": "^1.1.0", "@types/react": "^18.3.1", + "daisyui": "^4.12.10", "eslint": "9.17.0", "prettier": "3.3.3", "tailwindcss": "^3.4.13", "typescript": "^5.7.3", + "vitest": "3.1.4" + }, + "optionalDependencies": {"@biomejs/cli-linux-x64-musl": "^1.9.4", "raisa-ui": "^1.0.0"} +} +JSON put web/docs/notes.md # прочее содержимое вложенного docs/ остаётся git -C "$R" add -A @@ -37,7 +58,8 @@ git -C "$R" -c user.name=t -c user.email=t@t commit -qm init git -C "$R" push -q origin HEAD:main 2>/dev/null git -C "$R" fetch -q origin -"$SCRIPT" "$R" "$WORK/out" origin/main >/dev/null +# проверка сборки web/ в тесте отключена (нет сети); её включённый режим проверяется ниже заглушкой npm +SNAPSHOT_SKIP_WEB_BUILD=1 "$SCRIPT" "$R" "$WORK/out" origin/main >/dev/null D="$WORK/out/arm-core" fail=0 @@ -51,3 +73,82 @@ for p in openspec/specs/osv/spec.md web/openspec/specs/osv-ui/spec.md web/opensp done [ "$fail" = 0 ] || exit 1 echo "ok: prepare-contour-snapshot.sh вырезает артефакты агента на любой глубине" + +# --- web/package.json урезан до зависимостей сборки ------------------------------------------------------------------- +python3 -I - "$D/web/package.json" <<'PY' || fail=1 +import json, sys +pk = json.load(open(sys.argv[1], encoding="utf-8")) +errs = [] +if sorted(pk["devDependencies"]) != ["@rsbuild/core", "@rsbuild/plugin-react", "daisyui", "tailwindcss"]: + errs.append(f"devDependencies: {sorted(pk['devDependencies'])}") +if pk["devDependencies"]["@rsbuild/core"] != "1.6.7": + errs.append("версии devDependencies должны сохраниться") +if pk["dependencies"] != {"antd": "^6.5.0", "react": "^18.3.1"}: + errs.append("dependencies изменились") +if pk["optionalDependencies"] != {"raisa-ui": "^1.0.0"}: + errs.append(f"optionalDependencies: {pk['optionalDependencies']}") +if sorted(pk["scripts"]) != ["build", "preview", "start"]: + errs.append(f"scripts: {sorted(pk['scripts'])}") +if errs: + print("FAIL:", *errs, sep="\n ", file=sys.stderr) + sys.exit(1) +PY +# Makefile: web-check без biome/tsc, check-full на месте +if grep -Eq "biome|tsc" "$D/Makefile"; then echo "FAIL: в Makefile снимка остались biome/tsc" >&2; fail=1; fi +grep -q "cd web && npm install" "$D/Makefile" && grep -q "cd web && npm run build" "$D/Makefile" \ + || { echo "FAIL: web-check потерял npm install/build" >&2; fail=1; } +grep -q "^check-full:.*web-check" "$D/Makefile" || { echo "FAIL: check-full не ссылается на web-check" >&2; fail=1; } +# SNAPSHOT.md перечисляет убранное +for w in "@biomejs/biome" "eslint" "prettier" "typescript" "vitest" "@types/react" "@biomejs/cli-linux-x64-musl" \ + "test" "lint" "format"; do + grep -q "$w" "$D/SNAPSHOT.md" || { echo "FAIL: SNAPSHOT.md не упоминает убранное: $w" >&2; fail=1; } +done +[ "$fail" = 0 ] || exit 1 +echo "ok: web/package.json, Makefile и SNAPSHOT.md урезаны под сборку" + +# --- проверка сборки web/ (включена по умолчанию): заглушка npm в PATH ----------------------------------------------- +STUB="$WORK/stub"; mkdir -p "$STUB" +cat > "$STUB/npm" <<'SH' +#!/usr/bin/env bash +# заглушка: install создаёт node_modules, build — dist; NPM_STUB_FAIL=build роняет сборку +echo "$*" >> "$NPM_STUB_LOG" +case "$1" in + install) mkdir -p node_modules; echo "$PWD" >> "$NPM_STUB_LOG.cwd" ;; + run) if [ "${NPM_STUB_FAIL:-}" = build ]; then echo "Module not found: eslint" >&2; exit 1; fi; mkdir -p dist ;; +esac +SH +chmod +x "$STUB/npm" +export NPM_STUB_LOG="$WORK/npm.log" + +# сборка проходит: снимок выдан, node_modules/dist в него не попали, исходный каталог вывода чист +PATH="$STUB:$PATH" "$SCRIPT" "$R" "$WORK/out-ok" origin/main >/dev/null +O="$WORK/out-ok/arm-core" +grep -q "^install" "$NPM_STUB_LOG" && grep -q "^run build" "$NPM_STUB_LOG" \ + || { echo "FAIL: проверка сборки не вызвала npm install и npm run build" >&2; fail=1; } +for p in web/node_modules web/dist web/package-lock.json; do + [ ! -e "$O/$p" ] || { echo "FAIL: в снимке остался $p" >&2; fail=1; } +done +[ -e "$O/web/package.json" ] || { echo "FAIL: после проверки сборки пропал web/package.json" >&2; fail=1; } + +# сборка падает: код 1, понятное сообщение, снимка нет +rc=0 +PATH="$STUB:$PATH" NPM_STUB_FAIL=build "$SCRIPT" "$R" "$WORK/out-bad" origin/main >"$WORK/bad.out" 2>&1 || rc=$? +[ "$rc" = 1 ] || { echo "FAIL: при падении сборки ожидался код 1, получен $rc" >&2; fail=1; } +grep -q "не в разрешённом списке" "$WORK/bad.out" || { echo "FAIL: нет сообщения про разрешённый список" >&2; fail=1; } +[ ! -e "$WORK/out-bad/arm-core" ] || { echo "FAIL: при падении сборки снимок выдан" >&2; fail=1; } + +# tabular-parser и scan-gateway фронта не имеют: npm не вызывается +: > "$NPM_STUB_LOG" +git init -q --bare "$WORK/tp-origin.git" +git clone -q "$WORK/tp-origin.git" "$WORK/src/tabular-parser" 2>/dev/null +T="$WORK/src/tabular-parser" +printf 'venv:\n\tuv venv api/.venv\n' > "$T/Makefile" +for p in raisa_config.yml setup/setup.sh setup/pip.conf api/run_server.py api/requirements.txt api/.env.test api/contracts/s.json; do + mkdir -p "$(dirname "$T/$p")"; echo x > "$T/$p" +done +git -C "$T" add -A; git -C "$T" -c user.name=t -c user.email=t@t commit -qm init +git -C "$T" push -q origin HEAD:main 2>/dev/null; git -C "$T" fetch -q origin +PATH="$STUB:$PATH" "$SCRIPT" "$T" "$WORK/out-tp" origin/main >/dev/null +[ ! -s "$NPM_STUB_LOG" ] || { echo "FAIL: для tabular-parser вызван npm" >&2; fail=1; } +[ "$fail" = 0 ] || exit 1 +echo "ok: проверка сборки web/ — выдаёт снимок без node_modules/dist, при падении снимка нет, у сервисов без фронта не вызывается" diff --git a/scripts/test_sync_web.sh b/scripts/test_sync_web.sh index 1f7e330..aae0d8c 100755 --- a/scripts/test_sync_web.sh +++ b/scripts/test_sync_web.sh @@ -25,7 +25,7 @@ git clone -q "$WORK/arm-web.git" "$WEB" 2>/dev/null git -C "$WEB" checkout -q -b test put "$WEB/src/a.ts" "export const a = 1;" put "$WEB/README.md" "arm-web v1" -put "$WEB/package.json" '{"name":"web"}' +put "$WEB/package.json" '{"name":"web","devDependencies":{"@rsbuild/core":"1","@rsbuild/plugin-react":"1","tailwindcss":"3","daisyui":"4"}}' for p in CLAUDE.md .claude/skills/s/SKILL.md openspec/config.yaml openspec/specs/ui/spec.md docs/superpowers/plans/p.md \ scripts/demo-prepare.sh Makefile ARCHITECTURE.md .gitignore; do put "$WEB/$p" "/Users/someone/arm-web"; done commit_push "$WEB" "web v1" test @@ -38,7 +38,7 @@ git clone -q "$WORK/arm-core.git" "$CORE" 2>/dev/null git -C "$CORE" checkout -q -b main put "$CORE/web/src/a.ts" "export const a = 1;" put "$CORE/web/README.md" "arm-web v1" -put "$CORE/web/package.json" '{"name":"web"}' +put "$CORE/web/package.json" '{"name":"web","devDependencies":{"@rsbuild/core":"1","@rsbuild/plugin-react":"1","tailwindcss":"3","daisyui":"4"}}' printf 'venv:\n\tuv venv api/.venv\n' > "$CORE/Makefile" for p in raisa_config.yml setup/setup.sh setup/pip.conf api/run_server.py api/requirements.txt api/.env.test \ api/contracts/schema.json; do put "$CORE/$p"; done @@ -86,6 +86,8 @@ check "$([ "$rc" != 0 ] && echo 0 || echo 1)" "dry-run повторно: отк git -C "$CORE" worktree remove --force "$WT"; git -C "$CORE" branch -q -D "tmp/web-sync-$HEAD7" # --- 4. пайплайн переноса: prepare-contour-snapshot.sh видит отставание --------------------------------------------- +# сети в тесте нет: проверку сборки web/ в снимке отключаем (она покрыта test_prepare_contour_snapshot.sh) +export SNAPSHOT_SKIP_WEB_BUILD=1 export ARM_WEB="$WEB" SYNC_WEB_ARGS="--dry-run --wt-root $WORK/wt" rc=0; out=$("$SNAPSHOT" "$CORE" "$WORK/out1" origin/main &1) || rc=$? check "$([ "$rc" = 3 ] && echo 0 || echo 1)" "пайплайн без терминала: перенос остановлен, rc=3 (rc=$rc)"