#!/usr/bin/env bash # Local verification: runs the same fast checks as CI before pushing. # # scripts/check.sh fast (~1 min): fmt, clippy, i18n, scripts, workflows, semver # scripts/check.sh --full also runs workspace tests and cargo audit # # CI takes 5 to 30 minutes per run; almost everything that breaks CI is caught # here in seconds. set -uo pipefail cd "$(dirname "$0")/.." full=0 [ "${1:-}" = "--full" ] || [ "${1:-}" = "--completo" ] && full=1 failures=() step() { # step "name" command... local name="$1"; shift printf '\n\033[1m== %s\033[0m\n' "$name" if "$@"; then printf '\033[32mok\033[0m %s\n' "$name"; else printf '\033[31mFAILED\033[0m %s\n' "$name"; failures+=("$name"); fi } duckdb_dir="${XDG_CACHE_HOME:-$HOME/.cache}/acaua/duckdb" scripts/fetch-libduckdb.sh "$duckdb_dir" >/dev/null || { echo "could not install libduckdb" >&2; exit 1; } export DUCKDB_LIB_DIR="$duckdb_dir" DUCKDB_INCLUDE_DIR="$duckdb_dir" LD_LIBRARY_PATH="$duckdb_dir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" step "formatting (cargo fmt)" cargo fmt --all -- --check # Same flags as CI: warnings are treated as errors across all targets, including tests step "clippy (workspace, all targets)" env RUSTFLAGS="-D warnings" cargo clippy --workspace --all-targets check_forbidden_terms() { python3 - <<'PY' from pathlib import Path terms = [ 'Fast' + 'API', 'Click' + 'House', 'latest' + '.json', 'updater' + '.pubkey', 'createUpdater' + 'Artifacts', 'plugin' + '-updater', 'Tau' + 'ri', 'App' + 'Image', ] skip_dirs = {'.git', 'target', '.wolf', '.claude'} allowed = { Path('docs/architecture.md'), Path('docs/pt-BR/arquitetura.md'), Path('CHANGELOG.md'), Path('CHANGELOG.pt-BR.md'), } hits = [] for path in Path('.').rglob('*'): if not path.is_file() or any(part in skip_dirs for part in path.parts): continue if path in allowed: continue try: text = path.read_text(errors='ignore') except OSError: continue for term in terms: if term in text: hits.append(f'{path}: {term}') if hits: print('\n'.join(hits)) raise SystemExit(1) PY } step "UI translation coverage" node scripts/check-ui-i18n.mjs step "bash script syntax" bash -c 'for f in scripts/*.sh .githooks/*; do bash -n "$f" || exit 1; done' step "workflows and templates (valid YAML)" python3 -c " import glob, yaml for f in glob.glob('.gitea/**/*.yml', recursive=True): yaml.safe_load(open(f)) " step "brand assets" test -s logo.png step "consistent Semantic Versioning 2.0.0" python3 -c " import re, sys from pathlib import Path semver_re = re.compile( r'^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)' r'(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?' r'(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$' ) def cargo_version(path): m = re.search(r'^version\s*=\s*\"([^\"]+)\"', Path(path).read_text(), re.M) if not m: sys.exit(f'version not found in {path}') return m.group(1) version = cargo_version('Cargo.toml') if not semver_re.match(version): sys.exit(f'invalid SemVer 2.0.0 version: {version}') print(version) " step "DuckDB pin matches the duckdb crate" python3 -c " import re, sys from pathlib import Path lock = Path('Cargo.lock').read_text() m = re.search(r'^name = \"duckdb\"\nversion = \"1\.(\d+)\.\d+\"', lock, re.M) if not m: sys.exit('duckdb crate not found in Cargo.lock') enc = int(m.group(1)) crate = f'{enc // 10000}.{enc // 100 % 100}.{enc % 100}' pin = re.search(r'^DUCKDB_VERSION=(\S+)$', Path('scripts/fetch-libduckdb.sh').read_text(), re.M).group(1) if crate != pin: sys.exit(f'DuckDB mismatch: duckdb crate targets {crate}, scripts/fetch-libduckdb.sh pins {pin}') print(f'DuckDB {pin}') " step "forbidden stack terms" check_forbidden_terms if command -v docker >/dev/null && docker compose version >/dev/null 2>&1; then step "docker compose config" docker compose config --quiet else printf '\n(docker compose config: skipped, docker compose not available)\n' fi if [ "$full" = 1 ]; then step "tests (cargo test --workspace)" cargo test --workspace step "dependency audit (cargo audit)" cargo audit fi printf '\n' if [ ${#failures[@]} -eq 0 ]; then printf '\033[32mall checks passed\033[0m: ready to push.\n' else printf '\033[31mfailed:\033[0m %s\n' "${failures[*]}" exit 1 fi