arm-ka-docs/scripts/test_sync_web.sh
Andrey Epifancev e8dbcff83f feat(scripts): sync-web.sh — синхронизация arm-core/web с arm-web и проверка отставания в пайплайне переноса (#94)
Скрипт делает одной командой то, что делалось вручную в arm-core #94 и #100:
точка синхронизации по хэшу дерева src (без хранения состояния), worktree по
абсолютному пути, issue и PR в arm-core через tea, перенос набора (всё кроме
служебного arm-web), контроль diff -rq, проверки vitest/tsc/biome/build до
коммита. Режимы --check (0/10/20), --dry-run, --ref, --core-ref.

prepare-contour-snapshot.sh для arm-core перед снимком вызывает sync-web.sh
--check: при отставании спрашивает «перенести как есть?», «n» запускает
синхронизацию и останавливает перенос (код 3) до merge PR и закрытия issue;
без терминала — флаг --web-as-is; расхождение (прямые правки web/) — стоп.
Тест test_sync_web.sh на синтетических репозиториях, цели make test-sync-web
и sync-web, гайд §3 шаг 0, §3.1, §8.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-10 10:45:35 +04:00

128 lines
9.4 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"}'
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"}'
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 видит отставание ---------------------------------------------
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
[ "$fail" = 0 ] || exit 1
echo "ok: sync-web.sh — точка синхронизации, перенос набора, отказ при расхождении, пайплайн переноса"