#!/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 &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 &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 &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 &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 — точка синхронизации, перенос набора, отказ при расхождении, пайплайн переноса"