Merge pull request 'feat(scripts): снимок для контура — во web/ только зависимости сборки (#112)' (#113) from feat/112-contour-web-deps into main

This commit is contained in:
aep 2026-10-16 14:00:59 +03:00
commit 0fc940d072
4 changed files with 229 additions and 7 deletions

View File

@ -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/<repo>#<n>`.
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`, решение о его
коммите за контуром.

View File

@ -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] <repo-dir> <out-dir> [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/<repo>/issues/5 и pulls/7.
while IFS= read -r file; do
@ -189,6 +247,17 @@ cat > "$DEST/SNAPSHOT.md" <<EOF
Разработка ведётся снаружи; правки в контуре возвращаются в источник вручную (см. dev/contour-transfer-guide.md в docs).
EOF
if [ -n "$WEB_REMOVED" ]; then
{
printf '\nВстроенный фронт `web/`: `package.json` урезан до зависимостей сборки (в контуре фронт только собирается: '
printf '`npm install` через Nexus + `rsbuild build`; зеркало npm банка не содержит свежих инструментов разработки).\n'
printf 'Оставлены `dependencies` целиком, devDependencies `%s`, optionalDependencies `%s*`, scripts `%s`.\n' \
"${WEB_DEV_ALLOW[*]}" "$WEB_OPTIONAL_KEEP_PREFIX" "${WEB_SCRIPTS_KEEP[*]}"
printf 'Убрано:\n\n'
printf '%s\n' "$WEB_REMOVED" | sed 's/^/- /'
printf '\nЦель `make web-check` — `npm install` и `npm run build`. `web/package.json` из контура обратно не переносить: он урезан.\n'
} >> "$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"

View File

@ -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, при падении снимка нет, у сервисов без фронта не вызывается"

View File

@ -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 </dev/null 2>&1) || rc=$?
check "$([ "$rc" = 3 ] && echo 0 || echo 1)" "пайплайн без терминала: перенос остановлен, rc=3 (rc=$rc)"