Skip to content

Commit e2caf77

Browse files
authored
feat(shared): offline-tolerant sync.sh (#43)
sync.sh now never blocks commits on network failure: - SYNC_OFFLINE=1 env var → skip entirely with warning, exit 0 - curl fetch failures → warn to stderr, skip that file, exit 0 - missing curl/jq → warn + exit 0 (local dev without deps still commits) - unknown language → warn + exit 0 (no more hard die) - committed files: write to .tmp first, atomically mv on success, preserving cached copy if fetch fails mid-run - --max-time 10 on every curl so a hung network doesn't stall the hook Committed files stay committed (release-please-config.json must live at repo root for release-please-action to read it in CI); cached files stay in .shared/ as before. Offline tolerance is purely about hook robustness — CI still re-fetches on every run so canonical drift is picked up. Consumers' pre-commit entry bootstrap will be updated in a follow-up to tolerate the initial curl (before .shared/sync.sh is cached).
1 parent 5d9c862 commit e2caf77

1 file changed

Lines changed: 44 additions & 19 deletions

File tree

shared/sync.sh

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,34 @@
1010
# Two destination modes per file (declared in MANIFEST.json):
1111
# - cache → written to .shared/<file> (gitignored; re-fetched in CI)
1212
# - committed → written to <file> at repo root (must be committed; drift-checked)
13+
#
14+
# Offline tolerance:
15+
# - SYNC_OFFLINE=1 → skip entirely, exit 0 (explicit opt-out)
16+
# - network failures → warn to stderr, skip that file, exit 0 (never block commit)
17+
# - missing jq/curl → warn + exit 0 (local dev without deps still commits)
1318

14-
set -euo pipefail
19+
set -uo pipefail
1520

1621
REPO="jr200-labs/github-action-templates"
1722
BRANCH="master"
1823
SHARED_DIR=".shared"
1924
BASE_URL="https://raw.githubusercontent.com/${REPO}/${BRANCH}/shared"
2025

21-
die() { echo "error: $*" >&2; exit 1; }
26+
warn() { echo "sync: $*" >&2; }
2227

23-
require() {
24-
command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"
25-
}
28+
if [ "${SYNC_OFFLINE:-0}" = "1" ]; then
29+
warn "SYNC_OFFLINE=1 — skipping shared config sync"
30+
exit 0
31+
fi
32+
33+
if ! command -v curl >/dev/null 2>&1; then
34+
warn "curl not found — skipping shared config sync"
35+
exit 0
36+
fi
37+
if ! command -v jq >/dev/null 2>&1; then
38+
warn "jq not found — skipping shared config sync"
39+
exit 0
40+
fi
2641

2742
detect_language() {
2843
if [ -f "pyproject.toml" ] || [ -f "setup.py" ]; then
@@ -32,16 +47,22 @@ detect_language() {
3247
elif [ -f "package.json" ]; then
3348
echo "node"
3449
else
35-
die "cannot detect project language — pass python, go, or node as argument"
50+
echo ""
3651
fi
3752
}
3853

3954
download_to() {
4055
local remote_path="$1"
4156
local local_path="$2"
4257
mkdir -p "$(dirname "$local_path")"
43-
curl -sfL "${BASE_URL}/${remote_path}" -o "$local_path"
44-
echo "synced: ${remote_path} -> ${local_path}"
58+
if curl -sfL --max-time 10 "${BASE_URL}/${remote_path}" -o "${local_path}.tmp"; then
59+
mv "${local_path}.tmp" "$local_path"
60+
echo "synced: ${remote_path} -> ${local_path}"
61+
else
62+
rm -f "${local_path}.tmp"
63+
warn "offline or fetch failed — skipped ${remote_path}"
64+
return 1
65+
fi
4566
}
4667

4768
ensure_gitignore() {
@@ -53,13 +74,17 @@ ensure_gitignore() {
5374
fi
5475
}
5576

56-
require curl
57-
require jq
58-
5977
LANG="${1:-$(detect_language)}"
60-
61-
# Fetch manifest
62-
MANIFEST_JSON=$(curl -sfL "${BASE_URL}/MANIFEST.json") || die "failed to fetch MANIFEST.json"
78+
if [ -z "$LANG" ]; then
79+
warn "cannot detect project language — pass python, go, or node as argument; skipping"
80+
exit 0
81+
fi
82+
83+
# Fetch manifest — if network down, skip the whole sync cleanly.
84+
MANIFEST_JSON=$(curl -sfL --max-time 10 "${BASE_URL}/MANIFEST.json" 2>/dev/null) || {
85+
warn "cannot fetch MANIFEST.json (offline?) — skipping sync"
86+
exit 0
87+
}
6388

6489
get_files() {
6590
# $1 = section (common | <lang>), $2 = mode (cache | committed)
@@ -70,21 +95,21 @@ get_files() {
7095
for section in common "$LANG"; do
7196
while IFS= read -r f; do
7297
[ -z "$f" ] && continue
73-
download_to "$f" "$f"
98+
download_to "$f" "$f" || true
7499
done < <(get_files "$section" "committed")
75100
done
76101

77102
# Sync cache files (common + language-specific) → .shared/
78103
for section in common "$LANG"; do
79104
while IFS= read -r f; do
80105
[ -z "$f" ] && continue
81-
download_to "$f" "${SHARED_DIR}/$f"
106+
download_to "$f" "${SHARED_DIR}/$f" || true
82107
done < <(get_files "$section" "cache")
83108
done
84109

85-
# Always refresh self for future syncs
86-
download_to "sync.sh" "${SHARED_DIR}/sync.sh"
87-
chmod +x "${SHARED_DIR}/sync.sh"
110+
# Refresh self for future syncs (best-effort)
111+
download_to "sync.sh" "${SHARED_DIR}/sync.sh" || true
112+
[ -f "${SHARED_DIR}/sync.sh" ] && chmod +x "${SHARED_DIR}/sync.sh"
88113

89114
ensure_gitignore
90115

0 commit comments

Comments
 (0)