arm-ka-docs/scripts/test_sync_web.sh
Andrey Epifancev 0574ba402f feat(scripts): снимок для контура — во web/ только зависимости сборки (#112)
prepare-contour-snapshot.sh урезает web/package.json arm-core по разрешённому списку
(devDependencies: @rsbuild/core, @rsbuild/plugin-react, tailwindcss, daisyui; optional: raisa-*;
scripts: build, start, preview), сокращает web-check до npm install + npm run build, пишет убранное
в SNAPSHOT.md и сам собирает урезанный web/ во временной копии: не собралось — снимка нет.
Гайд: раздел 3.2 и правило «web/package.json из контура обратно не переносить».

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 14:47:35 +04:00

136 lines
10 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Тест sync-web.sh на синтетических репозиториях arm-web и arm-core (bare origin + клон), без tea, npm и сети.
# Сценарии: синхронно; arm-core отстаёт (--check и --dry-run); arm-core правили напрямую (отказ);
# пайплайн переноса в контур (prepare-contour-snapshot.sh спрашивает / останавливается / идёт «как есть»).
# Запуск: make test-sync-web (или напрямую из корня docs).
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
SYNC="$HERE/sync-web.sh"
SNAPSHOT="$HERE/prepare-contour-snapshot.sh"
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
export GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t
fail=0
check() { if [ "$1" = 0 ]; then echo "ok: $2"; else echo "FAIL: $2" >&2; fail=1; fi; }
put() { mkdir -p "$(dirname "$1")"; printf '%s\n' "${2:-x}" > "$1"; }
commit_push() { git -C "$1" add -A; git -C "$1" commit -qm "$2"; git -C "$1" push -q origin "HEAD:$3" 2>/dev/null; git -C "$1" fetch -q origin; }
# --- синтетический arm-web: ветка test ---------------------------------------------------------------------------
WEB="$WORK/arm-web"
git init -q --bare "$WORK/arm-web.git"
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","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
POINT=$(git -C "$WEB" rev-parse HEAD)
# --- синтетический arm-core: web/ = снимок arm-web v1 без служебных файлов, плюс платформенные файлы ----------------
CORE="$WORK/arm-core"
git init -q --bare "$WORK/arm-core.git"
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","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
commit_push "$CORE" "core v1" main
COMMON=(--arm-web "$WEB" --arm-core "$CORE" --wt-root "$WORK/wt")
# --- 1. синхронно ---------------------------------------------------------------------------------------------------
rc=0; "$SYNC" --check "${COMMON[@]}" >/dev/null || rc=$?
check "$([ "$rc" = 0 ] && echo 0 || echo 1)" "синхронно: --check возвращает 0 (rc=$rc)"
rc=0; out=$("$SYNC" --dry-run "${COMMON[@]}") || rc=$?
check "$([ "$rc" = 0 ] && [ ! -d "$WORK/wt" ] && echo 0 || echo 1)" "синхронно: --dry-run ничего не делает (rc=$rc)"
# --- arm-web уходит вперёд: правка src, новый файл, README; служебные файлы тоже меняются ---------------------------
put "$WEB/src/a.ts" "export const a = 2;"
put "$WEB/src/b.ts" "export const b = 1;"
put "$WEB/README.md" "arm-web v2"
put "$WEB/CLAUDE.md" "/Users/someone/arm-web v2"
put "$WEB/scripts/demo-prepare.sh" "v2"
commit_push "$WEB" "web v2" test
HEAD7=$(git -C "$WEB" rev-parse --short=7 HEAD)
# --- 2. отстаёт: --check ----------------------------------------------------------------------------------------------
rc=0; out=$("$SYNC" --check "${COMMON[@]}" 2>&1) || rc=$?
check "$([ "$rc" = 10 ] && echo 0 || echo 1)" "отстаёт: --check возвращает 10 (rc=$rc)"
check "$(grep -q "web v2" <<<"$out" && echo 0 || echo 1)" "отстаёт: --check перечисляет непереносённые коммиты"
check "$(grep -q "${POINT:0:7}" <<<"$out" && echo 0 || echo 1)" "отстаёт: --check называет точку синхронизации"
# --- 3. отстаёт: --dry-run переносит набор в worktree -------------------------------------------------------------------
rc=0; "$SYNC" --dry-run "${COMMON[@]}" >/dev/null || rc=$?
WT="$WORK/wt/web-sync-$HEAD7/arm-core"
check "$([ "$rc" = 0 ] && [ -d "$WT" ] && echo 0 || echo 1)" "dry-run: worktree $WT создан (rc=$rc)"
check "$([ "$(git -C "$WT" rev-parse --abbrev-ref HEAD)" = "tmp/web-sync-$HEAD7" ] && echo 0 || echo 1)" "dry-run: временная ветка tmp/web-sync-$HEAD7"
check "$([ -f "$WT/.bounded" ] && echo 0 || echo 1)" "dry-run: .bounded создан"
check "$([ "$(cat "$WT/web/src/a.ts")" = "export const a = 2;" ] && echo 0 || echo 1)" "dry-run: изменённый src/a.ts перенесён"
check "$([ -f "$WT/web/src/b.ts" ] && echo 0 || echo 1)" "dry-run: новый src/b.ts перенесён"
check "$([ "$(cat "$WT/web/README.md")" = "arm-web v2" ] && echo 0 || echo 1)" "dry-run: README перенесён"
check "$([ ! -e "$WT/web/CLAUDE.md" ] && [ ! -e "$WT/web/scripts" ] && echo 0 || echo 1)" "dry-run: служебные файлы arm-web не перенесены"
git -C "$WT" add -A web >/dev/null
TREE_WT=$(git -C "$WT" write-tree)
check "$([ "$(git -C "$WT" rev-parse "$TREE_WT:web/src")" = "$(git -C "$WEB" rev-parse test:src)" ] && echo 0 || echo 1)" "dry-run: дерево web/src совпадает с arm-web test:src"
git -C "$WT" reset -q
rc=0; "$SYNC" --dry-run "${COMMON[@]}" >/dev/null 2>&1 || rc=$?
check "$([ "$rc" != 0 ] && echo 0 || echo 1)" "dry-run повторно: отказ, worktree уже существует (rc=$rc)"
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)"
check "$(grep -qi "остановлен" <<<"$out" && echo 0 || echo 1)" "пайплайн: сообщение «перенос остановлен до завершения синхронизации»"
check "$([ -d "$WT" ] && [ -f "$WT/web/src/b.ts" ] && echo 0 || echo 1)" "пайплайн: синхронизация запущена, worktree создан"
check "$([ ! -d "$WORK/out1/arm-core" ] && echo 0 || echo 1)" "пайплайн: снимок не создан"
git -C "$CORE" worktree remove --force "$WT"; git -C "$CORE" branch -q -D "tmp/web-sync-$HEAD7"
rc=0; out=$(printf 'y\n' | "$SNAPSHOT" "$CORE" "$WORK/out2" origin/main 2>&1) || rc=$?
check "$([ "$rc" = 0 ] && [ -f "$WORK/out2/arm-core/web/src/a.ts" ] && [ ! -e "$WORK/out2/arm-core/web/src/b.ts" ] && echo 0 || echo 1)" \
"пайплайн, ответ «y»: снимок как есть (rc=$rc)"
rc=0; out=$(printf 'n\n' | "$SNAPSHOT" "$CORE" "$WORK/out3" origin/main 2>&1) || rc=$?
check "$([ "$rc" = 3 ] && [ -d "$WT" ] && echo 0 || echo 1)" "пайплайн, ответ «n»: синхронизация запущена, перенос остановлен (rc=$rc)"
git -C "$CORE" worktree remove --force "$WT"; git -C "$CORE" branch -q -D "tmp/web-sync-$HEAD7"
rc=0; out=$("$SNAPSHOT" --web-as-is "$CORE" "$WORK/out4" origin/main </dev/null 2>&1) || rc=$?
check "$([ "$rc" = 0 ] && [ -f "$WORK/out4/arm-core/SNAPSHOT.md" ] && echo 0 || echo 1)" "пайплайн, --web-as-is: снимок без вопроса (rc=$rc)"
check "$(grep -q "отстаёт" "$WORK/out4/arm-core/SNAPSHOT.md" && echo 0 || echo 1)" "пайплайн, --web-as-is: отставание записано в SNAPSHOT.md"
unset SYNC_WEB_ARGS
# arm-web рядом нет (в тесте он есть как сосед $CORE, поэтому путь задаётся явно несуществующим)
export ARM_WEB="$WORK/no-such-arm-web"
rc=0; out=$("$SNAPSHOT" "$CORE" "$WORK/out5" origin/main </dev/null 2>&1) || rc=$?
check "$([ "$rc" = 0 ] && grep -q "arm-web не найден" <<<"$out" && echo 0 || echo 1)" "пайплайн без arm-web рядом: предупреждение, снимок сделан (rc=$rc)"
unset ARM_WEB
# --- 5. arm-core правили напрямую: точки синхронизации нет ----------------------------------------------------------
put "$CORE/web/src/a.ts" "export const a = 'direct';"
commit_push "$CORE" "core: прямая правка web" main
rc=0; out=$("$SYNC" --check "${COMMON[@]}" 2>&1) || rc=$?
check "$([ "$rc" = 20 ] && echo 0 || echo 1)" "прямая правка arm-core: --check возвращает 20 (rc=$rc)"
check "$(grep -q "прямая правка web" <<<"$out" && echo 0 || echo 1)" "прямая правка arm-core: названы коммиты arm-core по web/"
export ARM_WEB="$WEB"
rc=0; out=$("$SNAPSHOT" "$CORE" "$WORK/out6" origin/main </dev/null 2>&1) || rc=$?
check "$([ "$rc" != 0 ] && [ ! -d "$WORK/out6/arm-core" ] && echo 0 || echo 1)" "пайплайн при расхождении: снимок не делается (rc=$rc)"
unset ARM_WEB
# --- 6. вызовы tea: флаги после подкоманды ---------------------------------------------------------------------------
# tea 0.16 не принимает --login/--repo перед подкомандой («flag provided but not defined: -login», arm-ka-docs#110);
# сам tea тест не вызывает (нет сети), поэтому проверяется текст скриптов
bad=$(grep -nE '(^|[^[:alnum:]_-])tea[[:space:]]+-' "$SYNC" "$SNAPSHOT" || true)
check "$([ -z "$bad" ] && echo 0 || echo 1)" "tea: флаги идут после подкоманды${bad:+ — нарушения: $bad}"
[ "$fail" = 0 ] || exit 1
echo "ok: sync-web.sh — точка синхронизации, перенос набора, отказ при расхождении, пайплайн переноса"