diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 44a2a0eac..76bf324bb 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,30 +1,14 @@ #!/bin/sh -# -# Pre-commit hook: read version from branch name (e.g. v0.5.0) -# and sync to all package.json files -# +set -eu -BRANCH=$(git rev-parse --abbrev-ref HEAD) +script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd) +repo_root=$(CDPATH= cd "$script_dir/.." && pwd) +cd "$repo_root" -# Extract version from branch name like v0.5.0, v1.2.3, release/v2.0.0 -VERSION=$(echo "$BRANCH" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || true) - -if [ -n "$VERSION" ]; then - CHANGED=0 - for f in package.json apps/*/package.json packages/*/package.json; do - if [ -f "$f" ]; then - CURRENT=$(jq -r '.version' "$f") - if [ "$CURRENT" != "$VERSION" ]; then - jq --arg v "$VERSION" '.version=$v' "$f" > "$f.tmp" && mv "$f.tmp" "$f" - git add "$f" - CHANGED=1 - fi - fi - done - - if [ "$CHANGED" = "1" ]; then - echo "[version-sync] branch $BRANCH → $VERSION" - fi +echo "[pre-commit] checking version consistency" +if ! tools/check-version-sync.sh; then + echo "[pre-commit] version consistency check failed. Run: scripts/sync-version.sh" + exit 1 fi # --- Rust format + lint gates (same checks as CI) --- diff --git a/.github/workflows/version-sync.yml b/.github/workflows/version-sync.yml new file mode 100644 index 000000000..7e58111ba --- /dev/null +++ b/.github/workflows/version-sync.yml @@ -0,0 +1,102 @@ +name: Version consistency + +on: + pull_request: + paths: + - "Cargo.toml" + - "Cargo.lock" + - "crates/**/Cargo.toml" + - "crates/op-cli/assets/skill-bundle.json" + - "crates/op-cli/src/app_control_cli.rs" + - "crates/op-editor-core/src/host_support.rs" + - "crates/op-editor-core/src/state.rs" + - "packages/package.json" + - "packages/bun.lock" + - "packages/op-web-sdk/package.json" + - "packages/op-web-sdk/src/index.ts" + - "packages/op-web-sdk-react/package.json" + - "packages/op-web-sdk-react/src/index.ts" + - "packages/op-web-sdk-vue/package.json" + - "packages/op-web-sdk-vue/src/index.ts" + - "packages/scripts/sync-version.mjs" + - "packages/scripts/sync-version.test.mjs" + - "scripts/workspace-version.sh" + - "scripts/workspace-version.test.sh" + - "scripts/sync-version.sh" + - "scripts/sync-version.test.sh" + - "scripts/bundle-macos.sh" + - "scripts/package-windows.nsi" + - "scripts/install-op.sh" + - "tools/check-version-sync.sh" + - "tools/check-version-sync.test.sh" + - "tools/bundle-macos.sh" + - ".githooks/pre-commit" + - ".github/workflows/rust-release.yml" + - ".github/workflows/version-sync.yml" + push: + paths: + - "Cargo.toml" + - "Cargo.lock" + - "crates/**/Cargo.toml" + - "crates/op-cli/assets/skill-bundle.json" + - "crates/op-cli/src/app_control_cli.rs" + - "crates/op-editor-core/src/host_support.rs" + - "crates/op-editor-core/src/state.rs" + - "packages/package.json" + - "packages/bun.lock" + - "packages/op-web-sdk/package.json" + - "packages/op-web-sdk/src/index.ts" + - "packages/op-web-sdk-react/package.json" + - "packages/op-web-sdk-react/src/index.ts" + - "packages/op-web-sdk-vue/package.json" + - "packages/op-web-sdk-vue/src/index.ts" + - "packages/scripts/sync-version.mjs" + - "packages/scripts/sync-version.test.mjs" + - "scripts/workspace-version.sh" + - "scripts/workspace-version.test.sh" + - "scripts/sync-version.sh" + - "scripts/sync-version.test.sh" + - "scripts/bundle-macos.sh" + - "scripts/package-windows.nsi" + - "scripts/install-op.sh" + - "tools/check-version-sync.sh" + - "tools/check-version-sync.test.sh" + - "tools/bundle-macos.sh" + - ".githooks/pre-commit" + - ".github/workflows/rust-release.yml" + - ".github/workflows/version-sync.yml" + +permissions: + contents: read + +jobs: + version-sync: + name: Verify synchronized versions + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install Bun + uses: oven-sh/setup-bun@v2 + + - name: Install SDK dependencies + working-directory: packages + run: bun install --frozen-lockfile + + - name: Test canonical version reader + run: scripts/workspace-version.test.sh + + - name: Test version synchronization entrypoint + run: scripts/sync-version.test.sh + + - name: Test repository version guard + run: tools/check-version-sync.test.sh + + - name: Check repository version consistency + run: tools/check-version-sync.sh diff --git a/CLAUDE.md b/CLAUDE.md index 607f0cd51..02cdcfeab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,7 +23,7 @@ Tooling is **Cargo** (Rust — the product). The root has **no `package.json`**; - **CLI:** `cargo build -p op-cli` → binary `op` - **MCP server:** built into the desktop/web host (`--mcp `); crate `op-mcp` - **Iconify catalog (Rust assets):** from `packages/`: `bun run generate-iconify-catalog` -- **Sync SDK versions:** from `packages/`: `bun run sync-version` (reads the canonical version from root `Cargo.toml`); verify with `bun run sync-version:check` +- **Sync all managed versions:** `scripts/sync-version.sh` reads the canonical version from root `Cargo.toml`; verify without writing via `tools/check-version-sync.sh` ## Architecture diff --git a/README.de.md b/README.de.md index 80686b4b9..39cf9af37 100644 --- a/README.de.md +++ b/README.de.md @@ -378,8 +378,8 @@ cargo run -p op-cli -- # CLI (binary: op) # Web SDK / JS tooling (run from packages/) cd packages && bun run lint # Lint the web SDK (oxlint); also: bun run format cd packages && bun run generate-iconify-catalog # Regenerate the Rust icon catalog assets -cd packages && bun run sync-version # Sync SDK versions from root Cargo.toml -cd packages && bun run sync-version:check # Verify SDK versions match root Cargo.toml +scripts/sync-version.sh # Sync all managed versions from root Cargo.toml +tools/check-version-sync.sh # Verify all managed versions match root Cargo.toml ``` ## Mitwirken diff --git a/README.es.md b/README.es.md index eed0e3022..278ccab0e 100644 --- a/README.es.md +++ b/README.es.md @@ -341,7 +341,7 @@ openpencil/ │ ├── jian/ Framework de UI GPU-Skia — widgets/render/eventos │ ├── casement/ Fork de winit │ └── agent/ Runtime de agente Rust multi-producto (agent-rs) -└── .githooks/ Sincronización de versión pre-commit desde nombre de rama +└── .githooks/ Comprobación de coherencia de versiones en pre-commit ``` ## Atajos de Teclado @@ -378,8 +378,8 @@ cargo run -p op-cli -- # CLI (binary: op) # Web SDK / JS tooling (run from packages/) cd packages && bun run lint # Lint the web SDK (oxlint); also: bun run format cd packages && bun run generate-iconify-catalog # Regenerate the Rust icon catalog assets -cd packages && bun run sync-version # Sync SDK versions from root Cargo.toml -cd packages && bun run sync-version:check # Verify SDK versions match root Cargo.toml +scripts/sync-version.sh # Sync all managed versions from root Cargo.toml +tools/check-version-sync.sh # Verify all managed versions match root Cargo.toml ``` ## Contribuir diff --git a/README.fr.md b/README.fr.md index 93ca6c525..183d45aad 100644 --- a/README.fr.md +++ b/README.fr.md @@ -341,7 +341,7 @@ openpencil/ │ ├── jian/ Framework UI GPU-Skia — widgets/rendu/événements │ ├── casement/ Fork de winit │ └── agent/ Runtime d'agent Rust transversal (agent-rs) -└── .githooks/ Synchronisation de version pre-commit depuis le nom de branche +└── .githooks/ Vérification de cohérence des versions en pre-commit ``` ## Raccourcis clavier @@ -378,8 +378,8 @@ cargo run -p op-cli -- # CLI (binary: op) # Web SDK / JS tooling (run from packages/) cd packages && bun run lint # Lint the web SDK (oxlint); also: bun run format cd packages && bun run generate-iconify-catalog # Regenerate the Rust icon catalog assets -cd packages && bun run sync-version # Sync SDK versions from root Cargo.toml -cd packages && bun run sync-version:check # Verify SDK versions match root Cargo.toml +scripts/sync-version.sh # Sync all managed versions from root Cargo.toml +tools/check-version-sync.sh # Verify all managed versions match root Cargo.toml ``` ## Contribuer diff --git a/README.hi.md b/README.hi.md index e783b263a..87742e89f 100644 --- a/README.hi.md +++ b/README.hi.md @@ -378,8 +378,8 @@ cargo run -p op-cli -- # CLI (binary: op) # Web SDK / JS tooling (run from packages/) cd packages && bun run lint # Lint the web SDK (oxlint); also: bun run format cd packages && bun run generate-iconify-catalog # Regenerate the Rust icon catalog assets -cd packages && bun run sync-version # Sync SDK versions from root Cargo.toml -cd packages && bun run sync-version:check # Verify SDK versions match root Cargo.toml +scripts/sync-version.sh # Sync all managed versions from root Cargo.toml +tools/check-version-sync.sh # Verify all managed versions match root Cargo.toml ``` ## योगदान diff --git a/README.id.md b/README.id.md index 8226950a0..9be67941d 100644 --- a/README.id.md +++ b/README.id.md @@ -378,8 +378,8 @@ cargo run -p op-cli -- # CLI (binary: op) # Web SDK / JS tooling (run from packages/) cd packages && bun run lint # Lint the web SDK (oxlint); also: bun run format cd packages && bun run generate-iconify-catalog # Regenerate the Rust icon catalog assets -cd packages && bun run sync-version # Sync SDK versions from root Cargo.toml -cd packages && bun run sync-version:check # Verify SDK versions match root Cargo.toml +scripts/sync-version.sh # Sync all managed versions from root Cargo.toml +tools/check-version-sync.sh # Verify all managed versions match root Cargo.toml ``` ## Berkontribusi diff --git a/README.ja.md b/README.ja.md index 48ef0019b..241cd1827 100644 --- a/README.ja.md +++ b/README.ja.md @@ -378,8 +378,8 @@ cargo run -p op-cli -- # CLI (binary: op) # Web SDK / JS tooling (run from packages/) cd packages && bun run lint # Lint the web SDK (oxlint); also: bun run format cd packages && bun run generate-iconify-catalog # Regenerate the Rust icon catalog assets -cd packages && bun run sync-version # Sync SDK versions from root Cargo.toml -cd packages && bun run sync-version:check # Verify SDK versions match root Cargo.toml +scripts/sync-version.sh # Sync all managed versions from root Cargo.toml +tools/check-version-sync.sh # Verify all managed versions match root Cargo.toml ``` ## コントリビュート diff --git a/README.ko.md b/README.ko.md index a8c152c40..175886b17 100644 --- a/README.ko.md +++ b/README.ko.md @@ -341,7 +341,7 @@ openpencil/ │ ├── jian/ GPU-Skia UI 프레임워크 — 위젯/렌더/이벤트 │ ├── casement/ winit 포크 │ └── agent/ 제품 공용 Rust 에이전트 런타임 (agent-rs) -└── .githooks/ 브랜치 이름에서 버전 동기화를 위한 pre-commit +└── .githooks/ pre-commit 버전 일관성 검사 ``` ## 키보드 단축키 @@ -378,8 +378,8 @@ cargo run -p op-cli -- # CLI (binary: op) # Web SDK / JS tooling (run from packages/) cd packages && bun run lint # Lint the web SDK (oxlint); also: bun run format cd packages && bun run generate-iconify-catalog # Regenerate the Rust icon catalog assets -cd packages && bun run sync-version # Sync SDK versions from root Cargo.toml -cd packages && bun run sync-version:check # Verify SDK versions match root Cargo.toml +scripts/sync-version.sh # Sync all managed versions from root Cargo.toml +tools/check-version-sync.sh # Verify all managed versions match root Cargo.toml ``` ## 기여하기 diff --git a/README.md b/README.md index 4df774b4d..570aeb0e3 100644 --- a/README.md +++ b/README.md @@ -479,7 +479,7 @@ openpencil/ │ ├── jian/ GPU-Skia UI framework — widgets/render/events │ ├── casement/ winit fork │ └── agent/ Cross-product Rust agent runtime (agent-rs) -└── .githooks/ Pre-commit version sync from branch name +└── .githooks/ Pre-commit version consistency check ``` ## Keyboard Shortcuts @@ -516,8 +516,8 @@ cargo run -p op-cli -- # CLI (binary: op) # Web SDK / JS tooling (run from packages/) cd packages && bun run lint # Lint the web SDK (oxlint); also: bun run format cd packages && bun run generate-iconify-catalog # Regenerate the Rust icon catalog assets -cd packages && bun run sync-version # Sync SDK versions from root Cargo.toml -cd packages && bun run sync-version:check # Verify SDK versions match root Cargo.toml +scripts/sync-version.sh # Sync all managed versions from root Cargo.toml +tools/check-version-sync.sh # Verify all managed versions match root Cargo.toml ``` ### Rust workspace details diff --git a/README.pt.md b/README.pt.md index 33cbe6370..2be6d3364 100644 --- a/README.pt.md +++ b/README.pt.md @@ -341,7 +341,7 @@ openpencil/ │ ├── jian/ Framework de UI GPU-Skia — widgets/renderização/eventos │ ├── casement/ Fork do winit │ └── agent/ Runtime de agente Rust compartilhado entre produtos (agent-rs) -└── .githooks/ Sincronização de versão no pre-commit a partir do nome da branch +└── .githooks/ Verificação de consistência de versão no pre-commit ``` ## Atalhos de Teclado @@ -378,8 +378,8 @@ cargo run -p op-cli -- # CLI (binary: op) # Web SDK / JS tooling (run from packages/) cd packages && bun run lint # Lint the web SDK (oxlint); also: bun run format cd packages && bun run generate-iconify-catalog # Regenerate the Rust icon catalog assets -cd packages && bun run sync-version # Sync SDK versions from root Cargo.toml -cd packages && bun run sync-version:check # Verify SDK versions match root Cargo.toml +scripts/sync-version.sh # Sync all managed versions from root Cargo.toml +tools/check-version-sync.sh # Verify all managed versions match root Cargo.toml ``` ## Contribuindo diff --git a/README.ru.md b/README.ru.md index 5665991f3..d492a4934 100644 --- a/README.ru.md +++ b/README.ru.md @@ -378,8 +378,8 @@ cargo run -p op-cli -- # CLI (binary: op) # Web SDK / JS tooling (run from packages/) cd packages && bun run lint # Lint the web SDK (oxlint); also: bun run format cd packages && bun run generate-iconify-catalog # Regenerate the Rust icon catalog assets -cd packages && bun run sync-version # Sync SDK versions from root Cargo.toml -cd packages && bun run sync-version:check # Verify SDK versions match root Cargo.toml +scripts/sync-version.sh # Sync all managed versions from root Cargo.toml +tools/check-version-sync.sh # Verify all managed versions match root Cargo.toml ``` ## Участие в разработке diff --git a/README.th.md b/README.th.md index df50371f3..829fbf4f5 100644 --- a/README.th.md +++ b/README.th.md @@ -341,7 +341,7 @@ openpencil/ │ ├── jian/ เฟรมเวิร์ก UI แบบ GPU-Skia — widget/render/event │ ├── casement/ winit fork │ └── agent/ Rust agent runtime ข้ามผลิตภัณฑ์ (agent-rs) -└── .githooks/ Pre-commit version sync จาก branch name +└── .githooks/ การตรวจสอบความสอดคล้องของเวอร์ชันก่อน commit ``` ## คีย์ลัด @@ -378,8 +378,8 @@ cargo run -p op-cli -- # CLI (binary: op) # Web SDK / JS tooling (run from packages/) cd packages && bun run lint # Lint the web SDK (oxlint); also: bun run format cd packages && bun run generate-iconify-catalog # Regenerate the Rust icon catalog assets -cd packages && bun run sync-version # Sync SDK versions from root Cargo.toml -cd packages && bun run sync-version:check # Verify SDK versions match root Cargo.toml +scripts/sync-version.sh # Sync all managed versions from root Cargo.toml +tools/check-version-sync.sh # Verify all managed versions match root Cargo.toml ``` ## การมีส่วนร่วม diff --git a/README.tr.md b/README.tr.md index 499481959..d96f1ea9e 100644 --- a/README.tr.md +++ b/README.tr.md @@ -378,8 +378,8 @@ cargo run -p op-cli -- # CLI (binary: op) # Web SDK / JS tooling (run from packages/) cd packages && bun run lint # Lint the web SDK (oxlint); also: bun run format cd packages && bun run generate-iconify-catalog # Regenerate the Rust icon catalog assets -cd packages && bun run sync-version # Sync SDK versions from root Cargo.toml -cd packages && bun run sync-version:check # Verify SDK versions match root Cargo.toml +scripts/sync-version.sh # Sync all managed versions from root Cargo.toml +tools/check-version-sync.sh # Verify all managed versions match root Cargo.toml ``` ## Katkıda Bulunma diff --git a/README.vi.md b/README.vi.md index 2478d81cd..088c7ee88 100644 --- a/README.vi.md +++ b/README.vi.md @@ -378,8 +378,8 @@ cargo run -p op-cli -- # CLI (binary: op) # Web SDK / JS tooling (run from packages/) cd packages && bun run lint # Lint the web SDK (oxlint); also: bun run format cd packages && bun run generate-iconify-catalog # Regenerate the Rust icon catalog assets -cd packages && bun run sync-version # Sync SDK versions from root Cargo.toml -cd packages && bun run sync-version:check # Verify SDK versions match root Cargo.toml +scripts/sync-version.sh # Sync all managed versions from root Cargo.toml +tools/check-version-sync.sh # Verify all managed versions match root Cargo.toml ``` ## Đóng góp diff --git a/README.zh-TW.md b/README.zh-TW.md index afbd51d01..cffabf8e8 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -378,8 +378,8 @@ cargo run -p op-cli -- # CLI (binary: op) # Web SDK / JS tooling (run from packages/) cd packages && bun run lint # Lint the web SDK (oxlint); also: bun run format cd packages && bun run generate-iconify-catalog # Regenerate the Rust icon catalog assets -cd packages && bun run sync-version # Sync SDK versions from root Cargo.toml -cd packages && bun run sync-version:check # Verify SDK versions match root Cargo.toml +scripts/sync-version.sh # Sync all managed versions from root Cargo.toml +tools/check-version-sync.sh # Verify all managed versions match root Cargo.toml ``` ## 參與貢獻 diff --git a/README.zh.md b/README.zh.md index 7e28acc1f..60e4c6fff 100644 --- a/README.zh.md +++ b/README.zh.md @@ -378,8 +378,8 @@ cargo run -p op-cli -- # CLI (binary: op) # Web SDK / JS tooling (run from packages/) cd packages && bun run lint # Lint the web SDK (oxlint); also: bun run format cd packages && bun run generate-iconify-catalog # Regenerate the Rust icon catalog assets -cd packages && bun run sync-version # Sync SDK versions from root Cargo.toml -cd packages && bun run sync-version:check # Verify SDK versions match root Cargo.toml +scripts/sync-version.sh # Sync all managed versions from root Cargo.toml +tools/check-version-sync.sh # Verify all managed versions match root Cargo.toml ``` ## 参与贡献 diff --git a/scripts/sync-version.sh b/scripts/sync-version.sh new file mode 100755 index 000000000..bdefa95eb --- /dev/null +++ b/scripts/sync-version.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +set -eu + +if [ "$#" -ne 0 ]; then + printf 'usage: scripts/sync-version.sh\n' >&2 + printf 'sync-version: edit [workspace.package].version in Cargo.toml, then run this command without arguments\n' >&2 + exit 2 +fi + +script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd) +repo_root=$(CDPATH= cd "$script_dir/.." && pwd) + +if ! canonical_version=$("$repo_root/scripts/workspace-version.sh"); then + printf 'sync-version: failed to read the canonical Cargo workspace version\n' >&2 + exit 1 +fi + +printf 'sync-version: canonical Cargo workspace version is %s\n' "$canonical_version" + +if ! (cd "$repo_root" && cargo metadata --no-deps --format-version 1 >/dev/null); then + printf 'sync-version: Cargo lock refresh failed\n' >&2 + exit 1 +fi + +if ! (cd "$repo_root/packages" && bun run sync-version); then + printf 'sync-version: package version synchronization failed\n' >&2 + exit 1 +fi + +if ! (cd "$repo_root" && "$repo_root/tools/check-version-sync.sh"); then + printf 'sync-version: version consistency check failed\n' >&2 + exit 1 +fi + +printf 'sync-version: all managed versions are synchronized to %s\n' "$canonical_version" diff --git a/scripts/sync-version.test.sh b/scripts/sync-version.test.sh new file mode 100755 index 000000000..35c5755dc --- /dev/null +++ b/scripts/sync-version.test.sh @@ -0,0 +1,209 @@ +#!/usr/bin/env bash + +set -euo pipefail + +script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd) +repo_root=$(CDPATH= cd "$script_dir/.." && pwd) +sync_source="$script_dir/sync-version.sh" +reader_source="$script_dir/workspace-version.sh" +tmp_base=${TMPDIR:-/tmp} +tmp_dir=$(mktemp -d "${tmp_base%/}/sync-version.XXXXXX") +trap 'rm -rf "$tmp_dir"' 0 HUP INT TERM + +test_count=0 +case_status=0 +case_output= + +fail() { + printf 'not ok %s - %s\n' "$test_count" "$1" >&2 + exit 1 +} + +pass() { + test_count=$((test_count + 1)) + printf 'ok %s - %s\n' "$test_count" "$1" +} + +assert_status() { + expected=$1 + label=$2 + if [ "$case_status" -ne "$expected" ]; then + printf '%s\n' "$case_output" >&2 + fail "$label: expected status $expected, got $case_status" + fi +} + +assert_contains() { + needle=$1 + label=$2 + case "$case_output" in + *"$needle"*) ;; + *) + printf '%s\n' "$case_output" >&2 + fail "$label: missing output: $needle" + ;; + esac +} + +assert_file_contains() { + file=$1 + needle=$2 + label=$3 + if ! grep -Fq -- "$needle" "$file"; then + fail "$label: $file does not contain: $needle" + fi +} + +assert_file_not_matches() { + file=$1 + pattern=$2 + label=$3 + if grep -Eq -- "$pattern" "$file"; then + fail "$label: $file unexpectedly matches: $pattern" + fi +} + +cargo() { + printf 'cargo:%s:%s\n' "$PWD" "$*" >> "$SYNC_TEST_LOG" + if [ -e "$PWD/.fail-cargo" ]; then + printf 'fake cargo failure\n' >&2 + return 31 + fi + version=$(sed -n 's/^version = "\([^"]*\)"/\1/p' "$PWD/Cargo.toml") + printf 'lock-version=%s\n' "$version" > "$PWD/Cargo.lock" + printf '%s\n' '{"packages":[]}' +} + +bun() { + printf 'bun:%s:%s\n' "$PWD" "$*" >> "$SYNC_TEST_LOG" + repo_root=$(CDPATH= cd "$(dirname "$PWD")" && pwd) + if [ -e "$repo_root/.fail-bun" ]; then + printf 'fake bun failure\n' >&2 + return 32 + fi + version=$(sed -n 's/^version = "\([^"]*\)"/\1/p' "$repo_root/Cargo.toml") + printf '{"version":"%s"}\n' "$version" > "$PWD/package.json" +} + +export -f cargo bun + +new_repo() { + name=$1 + repo="$tmp_dir/$name" + mkdir -p "$repo/scripts" "$repo/tools" "$repo/packages" "$repo/outside" + ln -s "$sync_source" "$repo/scripts/sync-version.sh" + ln -s "$reader_source" "$repo/scripts/workspace-version.sh" + printf '%s\n' \ + '[workspace]' \ + 'members = []' \ + '' \ + '[workspace.package]' \ + 'version = "2.3.4"' > "$repo/Cargo.toml" + printf '%s\n' 'lock-version=1.0.0' > "$repo/Cargo.lock" + printf '%s\n' '{"version":"1.0.0"}' > "$repo/packages/package.json" + + cat > "$repo/tools/check-version-sync.sh" <<'SCRIPT' +#!/bin/sh +set -eu +repo_root=$(CDPATH= cd "$(dirname "$0")/.." && pwd) +printf 'guard:%s\n' "$PWD" >> "$SYNC_TEST_LOG" +if [ -e "$repo_root/.fail-guard" ]; then + printf 'fake guard failure\n' >&2 + exit 33 +fi +SCRIPT + chmod +x "$repo/tools/check-version-sync.sh" + printf '%s\n' "$repo" +} + +run_sync() { + repo=$1 + shift + case_status=0 + case_output=$(cd "$repo/outside" && env SYNC_TEST_LOG="$repo/calls.log" \ + bash "$repo/scripts/sync-version.sh" "$@" 2>&1) || case_status=$? +} + +case_status=0 +case_output=$("$sync_source" 2.3.4 2>&1) || case_status=$? +assert_status 2 'argument rejection' +assert_contains 'usage: scripts/sync-version.sh' 'argument rejection' +pass 'sync entrypoint rejects positional versions with actionable usage' + +repo=$(new_repo external_cwd) +run_sync "$repo" +assert_status 0 'external cwd' +assert_file_contains "$repo/calls.log" "cargo:$repo:metadata --no-deps --format-version 1" \ + 'external cwd' +assert_file_contains "$repo/calls.log" "bun:$repo/packages:run sync-version" 'external cwd' +assert_file_contains "$repo/calls.log" "guard:$repo" 'external cwd' +assert_file_contains "$repo/Cargo.lock" 'lock-version=2.3.4' 'external cwd' +assert_file_contains "$repo/packages/package.json" '"version":"2.3.4"' 'external cwd' +pass 'sync entrypoint locates the repository and runs every stage from an external cwd' + +first_hash=$(shasum "$repo/Cargo.lock" "$repo/packages/package.json") +run_sync "$repo" +assert_status 0 'idempotent rerun' +second_hash=$(shasum "$repo/Cargo.lock" "$repo/packages/package.json") +if [ "$first_hash" != "$second_hash" ]; then + fail 'idempotent rerun changed managed file hashes' +fi +pass 'running synchronization twice leaves managed file hashes unchanged' + +repo=$(new_repo cargo_failure) +touch "$repo/.fail-cargo" +run_sync "$repo" +assert_status 1 'Cargo stage failure' +assert_contains 'sync-version: Cargo lock refresh failed' 'Cargo stage failure' +pass 'Cargo refresh failures identify the failing synchronization stage' + +repo=$(new_repo bun_failure) +touch "$repo/.fail-bun" +run_sync "$repo" +assert_status 1 'package stage failure' +assert_contains 'sync-version: package version synchronization failed' 'package stage failure' +pass 'package synchronization failures identify the failing stage' + +repo=$(new_repo guard_failure) +touch "$repo/.fail-guard" +run_sync "$repo" +assert_status 1 'guard stage failure' +assert_contains 'sync-version: version consistency check failed' 'guard stage failure' +pass 'guard failures identify the final verification stage' + +hook="$repo_root/.githooks/pre-commit" +assert_file_contains "$hook" 'tools/check-version-sync.sh' 'pre-commit guard' +assert_file_not_matches "$hook" 'rev-parse --abbrev-ref|grep -oE.*[0-9]|git add|jq --arg|\.tmp.*mv' \ + 'pre-commit detect-only policy' +guard_line=$(grep -n 'tools/check-version-sync.sh' "$hook" | head -n 1 | cut -d: -f1) +fmt_line=$(grep -n 'cargo fmt' "$hook" | head -n 1 | cut -d: -f1) +if [ -z "$guard_line" ] || [ -z "$fmt_line" ] || [ "$guard_line" -ge "$fmt_line" ]; then + fail 'pre-commit guard must run before Rust format and lint checks' +fi +pass 'pre-commit is detect-only and runs the version guard before Rust checks' + +workflow="$repo_root/.github/workflows/version-sync.yml" +assert_file_contains "$workflow" 'pull_request:' 'version sync workflow trigger' +assert_file_contains "$workflow" 'push:' 'version sync workflow trigger' +assert_file_contains "$workflow" 'submodules: recursive' 'version sync checkout' +assert_file_contains "$workflow" 'dtolnay/rust-toolchain@stable' 'version sync Rust setup' +assert_file_contains "$workflow" 'oven-sh/setup-bun@v2' 'version sync Bun setup' +assert_file_contains "$workflow" 'bun install --frozen-lockfile' 'version sync dependency install' +assert_file_contains "$workflow" 'packages/bun.lock' 'version sync managed paths' +assert_file_contains "$workflow" 'packages/scripts/sync-version.mjs' 'version sync implementation paths' +assert_file_not_matches "$workflow" 'packages/\*\*' 'version sync focused path filters' +assert_file_contains "$workflow" 'scripts/workspace-version.test.sh' 'version reader tests' +assert_file_contains "$workflow" 'scripts/sync-version.test.sh' 'version sync tests' +assert_file_contains "$workflow" 'tools/check-version-sync.test.sh' 'version guard tests' +assert_file_contains "$workflow" 'tools/check-version-sync.sh' 'version guard' +pass 'dedicated CI installs prerequisites and runs the focused version checks' + +old_guard_name='check-version-'"fixtures" +if rg -n "$old_guard_name" "$repo_root" --hidden \ + --glob '!.git/**' --glob '!vendor/**' > "$tmp_dir/old-paths"; then + sed 's/^/ /' "$tmp_dir/old-paths" >&2 + fail 'old version guard paths remain in the repository' +fi +pass 'repository no longer references the retired fixture-only guard name' + +printf '1..%s\n' "$test_count" diff --git a/tools/check-version-fixtures.sh b/tools/check-version-sync.sh similarity index 73% rename from tools/check-version-fixtures.sh rename to tools/check-version-sync.sh index f2e8665d0..3b41ef6d7 100755 --- a/tools/check-version-fixtures.sh +++ b/tools/check-version-sync.sh @@ -5,6 +5,14 @@ set -euo pipefail script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd) repo_root=$(CDPATH= cd "$script_dir/.." && pwd) fixture_version=1.0.0 + +for required_command in cargo jq bun rg; do + if ! command -v "$required_command" >/dev/null 2>&1; then + printf 'version-sync: required command not found: %s\n' "$required_command" >&2 + exit 1 + fi +done + current_version=$(bash "$repo_root/scripts/workspace-version.sh") cd "$repo_root" @@ -170,8 +178,110 @@ reject_matches() { fi } +validate_workspace_package_versions() { + if ! metadata=$(cargo metadata --no-deps --format-version 1 --locked); then + report_missing Cargo.lock \ + 'cargo metadata --locked failed; run scripts/sync-version.sh to refresh the lockfile' + return + fi + + mismatch_status=0 + mismatches=$(printf '%s\n' "$metadata" | jq -r \ + --arg crates_prefix "$repo_root/crates/" \ + --arg expected "$current_version" ' + .packages[]? + | select(.name | startswith("op-")) + | select(.manifest_path | startswith($crates_prefix)) + | select(.version != $expected) + | [.manifest_path, .name, .version] + | @tsv + ' 2>&1) || mismatch_status=$? + if [[ "$mismatch_status" -ne 0 ]]; then + printf '%s\n' "$mismatches" >&2 + report_missing Cargo.toml 'failed to inspect cargo metadata with jq' + return + fi + + if [[ -n "$mismatches" ]]; then + while IFS=$'\t' read -r manifest package package_version; do + relative_manifest=${manifest#"$repo_root"/} + printf '%s:1: error: workspace package %s has version %s; expected %s\n' \ + "$relative_manifest" "$package" "$package_version" "$current_version" >&2 + errors=1 + done <<< "$mismatches" + fi +} + +validate_package_versions() { + package_status=0 + package_output=$(cd packages && bun run sync-version:check 2>&1) || package_status=$? + if [[ "$package_status" -ne 0 ]]; then + printf '%s\n' "$package_output" >&2 + report_missing packages \ + 'bun run sync-version:check failed; run scripts/sync-version.sh to repair package drift' + fi +} + +validate_release_tag() { + tag_name= + if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then + tag_name=${GITHUB_REF#refs/tags/} + elif [[ -z "${GITHUB_REF:-}" && "${GITHUB_REF_NAME:-}" == v* ]]; then + tag_name=$GITHUB_REF_NAME + fi + + if [[ -n "$tag_name" && "${tag_name#v}" != "$current_version" ]]; then + report_missing environment \ + "release tag ${tag_name} does not match Cargo workspace version ${current_version}" + fi +} + +validate_cli_bundle_version_template() { + bundle=crates/op-cli/assets/skill-bundle.json + sentinel=__OPENPENCIL_VERSION__ + sentinel_count=$(rg --fixed-strings --count-matches -- "$sentinel" "$bundle" || true) + if [[ "${sentinel_count:-0}" != 6 ]]; then + report_missing "$bundle" \ + "expected exactly 6 version sentinels ${sentinel} (found ${sentinel_count:-0})" + fi + reject_matches fixed "$bundle" "$current_version" \ + 'embedded CLI bundle must not contain the canonical version literal' +} + +validate_rust_product_version_producers() { + require_statement crates/op-editor-core/src/state.rs \ + 'version:[[:space:]]*env!\("CARGO_PKG_VERSION"\)[.]to_owned\(\),' \ + 'empty documents must derive their version from CARGO_PKG_VERSION' + + host_support=crates/op-editor-core/src/host_support.rs + host_version_count=$(rg --fixed-strings --count-matches -- \ + 'src.replace("__OPENPENCIL_VERSION__", env!("CARGO_PKG_VERSION"))' \ + "$host_support" || true) + if [[ "$host_version_count" != 3 ]]; then + report_missing "$host_support" \ + "expected 3 document templates to derive from CARGO_PKG_VERSION (found ${host_version_count:-0})" + fi + + cli_source=crates/op-cli/src/app_control_cli.rs + require_regex "$cli_source" 'env!\("CARGO_PKG_VERSION"\)' \ + 'CLI starter documents must derive their version from CARGO_PKG_VERSION' + reject_matches regex "$cli_source" \ + '"version"[^[:cntrl:]]*"[0-9]+[.][0-9]+[.][0-9]+' \ + 'CLI starter documents must not hard-code a product version' + + reject_matches regex crates/op-host-desktop/Cargo.toml \ + '^[[:space:]]*op-host-native[[:space:]]*=.*path[[:space:]]*=.*version[[:space:]]*=' \ + 'local op-host-native dependency must not duplicate the product version' +} + +validate_workspace_package_versions +validate_package_versions +validate_release_tag +validate_cli_bundle_version_template +validate_rust_product_version_producers + if [[ "$current_version" == "$fixture_version" ]]; then - printf 'version-fixtures: current product version %s equals stable fixture version %s; skipping literal fixture drift scan because stable fixtures and product-version literals are indistinguishable\n' \ + printf 'version-sync: current product version %s equals stable fixture version %s; skipping literal fixture drift scan because stable fixtures and product-version literals are indistinguishable\n' \ "$current_version" "$fixture_version" fixture_scan_skipped=1 else @@ -187,15 +297,15 @@ else crates) || rg_status=$? if [[ "$rg_status" -gt 1 ]]; then - printf 'version-fixtures: failed to scan Rust sources with rg (status %s)\n' "$rg_status" >&2 + printf 'version-sync: failed to scan Rust sources with rg (status %s)\n' "$rg_status" >&2 exit "$rg_status" fi if [[ -n "$matches" ]]; then - printf 'version-fixtures: ordinary Rust fixtures copy current product version %s:\n' \ + printf 'version-sync: ordinary Rust fixtures copy current product version %s:\n' \ "$current_version" >&2 printf '%s\n' "$matches" >&2 - printf 'version-fixtures: use stable %s test data unless a test explicitly covers compatibility, migration, or updates\n' \ + printf 'version-sync: use stable %s test data unless a test explicitly covers compatibility, migration, or updates\n' \ "$fixture_version" >&2 errors=1 fi @@ -319,8 +429,8 @@ if [[ "$errors" -ne 0 ]]; then fi if [[ "$fixture_scan_skipped" -eq 0 ]]; then - printf 'version-fixtures: no ordinary Rust fixtures copy current product version %s\n' \ + printf 'version-sync: no ordinary Rust fixtures copy current product version %s\n' \ "$current_version" fi -printf 'version-fixtures: packaging and release versions derive from Cargo workspace version %s\n' \ +printf 'version-sync: all managed versions derive from Cargo workspace version %s\n' \ "$current_version" diff --git a/tools/check-version-fixtures.test.sh b/tools/check-version-sync.test.sh similarity index 70% rename from tools/check-version-fixtures.test.sh rename to tools/check-version-sync.test.sh index 432ddca1f..110c4b9ae 100755 --- a/tools/check-version-fixtures.test.sh +++ b/tools/check-version-sync.test.sh @@ -3,9 +3,9 @@ set -euo pipefail script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd) -guard_source="$script_dir/check-version-fixtures.sh" +guard_source="$script_dir/check-version-sync.sh" reader_source="$script_dir/../scripts/workspace-version.sh" -temp_root=$(mktemp -d "${TMPDIR:-/tmp}/check-version-fixtures.XXXXXX") +temp_root=$(mktemp -d "${TMPDIR:-/tmp}/check-version-sync.XXXXXX") trap 'rm -rf "$temp_root"' EXIT HUP INT TERM tests_run=0 @@ -61,6 +61,38 @@ assert_no_success_output() { assert_not_contains 'skipping literal fixture drift scan' "$label" } +cargo() { + if [[ "$*" != 'metadata --no-deps --format-version 1 --locked' ]]; then + printf 'unexpected cargo arguments: %s\n' "$*" >&2 + return 41 + fi + repo_root=$PWD + canonical=$("$repo_root/scripts/workspace-version.sh") + package_version=$canonical + if [[ -f "$repo_root/.fake-cargo-version" ]]; then + package_version=$(sed -n '1p' "$repo_root/.fake-cargo-version") + fi + if [[ -e "$repo_root/.fake-cargo-warning" ]]; then + printf 'warning: fake Cargo metadata warning\n' >&2 + fi + printf '{"packages":[{"name":"op-example","version":"%s","manifest_path":"%s/crates/example/Cargo.toml"}]}\n' \ + "$package_version" "$repo_root" +} + +bun() { + repo_root=$(CDPATH= cd "$PWD/.." && pwd) + if [[ "$PWD" != "$repo_root/packages" || "$*" != 'run sync-version:check' ]]; then + printf 'unexpected bun invocation: cwd=%s args=%s\n' "$PWD" "$*" >&2 + return 42 + fi + if [[ -e "$repo_root/.fake-bun-fail" ]]; then + printf 'package versions are stale\n' >&2 + return 43 + fi +} + +export -f cargo bun + write_workflow_fixture() { repo=$1 dependency_mode=$2 @@ -158,14 +190,19 @@ new_repo() { mkdir -p \ "$repo/.github/workflows" \ + "$repo/packages" \ "$repo/tools" \ "$repo/scripts" \ + "$repo/crates/op-cli/assets" \ + "$repo/crates/op-cli/src" \ + "$repo/crates/op-editor-core/src" \ + "$repo/crates/op-host-desktop" \ "$repo/crates/example/src" \ "$repo/crates/op-host-desktop/src" git -C "$repo" init -q - cp "$guard_source" "$repo/tools/check-version-fixtures.sh" + cp "$guard_source" "$repo/tools/check-version-sync.sh" cp "$reader_source" "$repo/scripts/workspace-version.sh" - chmod +x "$repo/tools/check-version-fixtures.sh" "$repo/scripts/workspace-version.sh" + chmod +x "$repo/tools/check-version-sync.sh" "$repo/scripts/workspace-version.sh" printf '%s\n' \ '[workspace]' \ 'members = []' \ @@ -173,6 +210,26 @@ new_repo() { '[workspace.package]' \ "version = \"$version\"" \ 'edition = "2024"' > "$repo/Cargo.toml" + printf '%s\n' 'version = 4' > "$repo/Cargo.lock" + printf '%s\n' '{"name":"fixture-packages"}' > "$repo/packages/package.json" + + cat > "$repo/crates/op-cli/assets/skill-bundle.json" <<'JSON' +{"one":"__OPENPENCIL_VERSION__","two":"__OPENPENCIL_VERSION__","three":"__OPENPENCIL_VERSION__","four":"__OPENPENCIL_VERSION__","five":"__OPENPENCIL_VERSION__","six":"__OPENPENCIL_VERSION__"} +JSON + cat > "$repo/crates/op-editor-core/src/state.rs" <<'RUST' +version: env!("CARGO_PKG_VERSION").to_owned(), +RUST + cat > "$repo/crates/op-editor-core/src/host_support.rs" <<'RUST' +let src = src.replace("__OPENPENCIL_VERSION__", env!("CARGO_PKG_VERSION")); +let src = src.replace("__OPENPENCIL_VERSION__", env!("CARGO_PKG_VERSION")); +let src = src.replace("__OPENPENCIL_VERSION__", env!("CARGO_PKG_VERSION")); +RUST + cat > "$repo/crates/op-cli/src/app_control_cli.rs" <<'RUST' +const MINIMAL_DOCUMENT: &str = concat!(env!("CARGO_PKG_VERSION")); +RUST + cat > "$repo/crates/op-host-desktop/Cargo.toml" <<'TOML' +op-host-native = { path = "../op-host-native", features = ["gl-host"] } +TOML cat > "$repo/scripts/bundle-macos.sh" <<'SCRIPT' #!/usr/bin/env bash @@ -229,8 +286,18 @@ SCRIPT run_guard() { repo=$1 + shift case_status=0 - case_output=$(cd "$repo" && bash tools/check-version-fixtures.sh 2>&1) || case_status=$? + case_output=$(cd "$repo" && env "$@" \ + bash tools/check-version-sync.sh 2>&1) || case_status=$? +} + +repo_snapshot() { + repo=$1 + find "$repo" -type f ! -path "$repo/.git/*" -exec shasum {} + | + LC_ALL=C sort | + shasum | + awk '{print $1}' } repo=$(new_repo v_prefixed_stale_version 0.8.1) @@ -470,14 +537,14 @@ printf '%s\n' '# identifier build0.8.2candidate is not a version token' \ >> "$repo/scripts/install-op.sh" run_guard "$repo" assert_status 0 'embedded version-like substring' -assert_contains 'packaging and release versions derive from Cargo workspace version 0.8.1' \ +assert_contains 'all managed versions derive from Cargo workspace version 0.8.1' \ 'embedded version-like substring' pass 'version-like substrings inside larger identifiers are allowed' repo=$(new_repo nsis_defensive_fallback 0.8.1) run_guard "$repo" assert_status 0 'NSIS defensive fallback' -assert_contains 'packaging and release versions derive from Cargo workspace version 0.8.1' \ +assert_contains 'all managed versions derive from Cargo workspace version 0.8.1' \ 'NSIS defensive fallback' pass 'NSIS 0.0.0 defensive fallback remains allowed' @@ -518,4 +585,104 @@ assert_not_contains 'no ordinary Rust fixtures copy current product version' \ 'fixture-version collision packaging check' pass 'fixture-version collision still runs packaging checks' +repo=$(new_repo cargo_metadata_version_mismatch 0.8.1) +printf '%s\n' '0.8.0' > "$repo/.fake-cargo-version" +run_guard "$repo" +assert_status 1 'Cargo metadata version mismatch' +assert_contains 'crates/example/Cargo.toml:1: error: workspace package op-example has version 0.8.0; expected 0.8.1' \ + 'Cargo metadata version mismatch' +assert_no_success_output 'Cargo metadata version mismatch' +pass 'workspace op-* packages under crates must match the canonical Cargo version' + +repo=$(new_repo cargo_metadata_warning 0.8.1) +touch "$repo/.fake-cargo-warning" +run_guard "$repo" +assert_status 0 'Cargo metadata warning' +assert_contains 'warning: fake Cargo metadata warning' 'Cargo metadata warning' +pass 'Cargo warnings do not corrupt the metadata JSON passed to jq' + +repo=$(new_repo package_version_drift 0.8.1) +touch "$repo/.fake-bun-fail" +run_guard "$repo" +assert_status 1 'package version drift' +assert_contains 'packages:1: error: bun run sync-version:check failed' 'package version drift' +assert_no_success_output 'package version drift' +pass 'web SDK package drift is reported by the read-only guard' + +repo=$(new_repo matching_release_tag 0.8.1) +run_guard "$repo" GITHUB_REF=refs/tags/v0.8.1 GITHUB_REF_NAME=v0.8.1 +assert_status 0 'matching release tag' +pass 'matching v* release tags pass the repository guard' + +repo=$(new_repo mismatched_release_tag 0.8.1) +run_guard "$repo" GITHUB_REF=refs/tags/v0.8.2 GITHUB_REF_NAME=v0.8.2 +assert_status 1 'mismatched release tag' +assert_contains 'environment:1: error: release tag v0.8.2 does not match Cargo workspace version 0.8.1' \ + 'mismatched release tag' +assert_no_success_output 'mismatched release tag' +pass 'mismatched v* release tags are rejected outside the release workflow too' + +repo=$(new_repo cli_bundle_wrong_sentinel_count 0.8.1) +sed -i.bak 's/,"six":"__OPENPENCIL_VERSION__"//' \ + "$repo/crates/op-cli/assets/skill-bundle.json" +rm "$repo/crates/op-cli/assets/skill-bundle.json.bak" +run_guard "$repo" +assert_status 1 'CLI bundle sentinel count' +assert_contains 'crates/op-cli/assets/skill-bundle.json:1: error: expected exactly 6 version sentinels' \ + 'CLI bundle sentinel count' +assert_no_success_output 'CLI bundle sentinel count' +pass 'embedded CLI bundle retains exactly six version sentinels' + +repo=$(new_repo cli_bundle_without_sentinels 0.8.1) +sed -i.bak 's/__OPENPENCIL_VERSION__/__MISSING_VERSION__/g' \ + "$repo/crates/op-cli/assets/skill-bundle.json" +rm "$repo/crates/op-cli/assets/skill-bundle.json.bak" +run_guard "$repo" +assert_status 1 'CLI bundle missing sentinels' +assert_contains 'crates/op-cli/assets/skill-bundle.json:1: error: expected exactly 6 version sentinels' \ + 'CLI bundle missing sentinels' +assert_contains '(found 0)' 'CLI bundle missing sentinels' +assert_no_success_output 'CLI bundle missing sentinels' +pass 'embedded CLI bundle reports zero missing version sentinels actionably' + +repo=$(new_repo cli_bundle_numeric_product_version 0.8.1) +printf '%s\n' '{"stale":"0.8.1"}' >> "$repo/crates/op-cli/assets/skill-bundle.json" +run_guard "$repo" +assert_status 1 'CLI bundle numeric product version' +assert_contains 'crates/op-cli/assets/skill-bundle.json:' 'CLI bundle numeric product version' +assert_contains 'error: embedded CLI bundle must not contain the canonical version literal' \ + 'CLI bundle numeric product version' +assert_no_success_output 'CLI bundle numeric product version' +pass 'embedded CLI bundle cannot duplicate the numeric product version' + +repo=$(new_repo hardcoded_rust_product_version 0.8.1) +printf '%s\n' 'version: "0.8.1".to_owned(),' > "$repo/crates/op-editor-core/src/state.rs" +run_guard "$repo" +assert_status 1 'hardcoded Rust product version' +assert_contains 'crates/op-editor-core/src/state.rs:1: error: empty documents must derive their version from CARGO_PKG_VERSION' \ + 'hardcoded Rust product version' +assert_no_success_output 'hardcoded Rust product version' +pass 'Rust product-version producers remain derived from Cargo metadata' + +repo=$(new_repo versioned_local_product_dependency 0.8.1) +printf '%s\n' \ + 'op-host-native = { path = "../op-host-native", version = "0.8.1", features = ["gl-host"] }' \ + > "$repo/crates/op-host-desktop/Cargo.toml" +run_guard "$repo" +assert_status 1 'versioned local product dependency' +assert_contains 'crates/op-host-desktop/Cargo.toml:1: error: local op-host-native dependency must not duplicate the product version' \ + 'versioned local product dependency' +assert_no_success_output 'versioned local product dependency' +pass 'local product dependencies do not repeat the workspace version' + +repo=$(new_repo guard_is_read_only 0.8.1) +before_snapshot=$(repo_snapshot "$repo") +run_guard "$repo" +assert_status 0 'read-only guard' +after_snapshot=$(repo_snapshot "$repo") +if [[ "$before_snapshot" != "$after_snapshot" ]]; then + fail 'read-only guard changed repository file contents' +fi +pass 'repository-wide version guard does not write files' + printf '1..%s\n' "$tests_run"