Spaces:
Sleeping
Sleeping
chore(cache): add auto cache-bump hooks and script
Browse files- .githooks/pre-commit +17 -0
- .githooks/pre-push +20 -0
- README.md +19 -0
- scripts/bump_cache.py +188 -0
- static/index.html +3 -3
- static/map.html +3 -3
- static/sw.js +1 -1
- version.json +4 -0
.githooks/pre-commit
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env sh
|
2 |
+
# Auto-bump cache on commit and stage updated files
|
3 |
+
|
4 |
+
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
5 |
+
cd "$REPO_ROOT" || exit 1
|
6 |
+
|
7 |
+
# Run bump (timestamp only) so every commit invalidates browser cache
|
8 |
+
python scripts/bump_cache.py --now >/dev/null 2>&1 || {
|
9 |
+
echo "[pre-commit] bump_cache failed; ensure Python is installed and scripts/bump_cache.py exists" 1>&2
|
10 |
+
exit 1
|
11 |
+
}
|
12 |
+
|
13 |
+
# Stage changed files
|
14 |
+
git add static/sw.js static/index.html static/map.html version.json 2>/dev/null
|
15 |
+
|
16 |
+
echo "[pre-commit] Cache bust updated and staged."
|
17 |
+
exit 0
|
.githooks/pre-push
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env sh
|
2 |
+
# Safety: ensure cache bump ran; if not, run it here before push
|
3 |
+
|
4 |
+
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
5 |
+
cd "$REPO_ROOT" || exit 1
|
6 |
+
|
7 |
+
# Run bump (timestamp only) to be safe
|
8 |
+
python scripts/bump_cache.py --now >/dev/null 2>&1 || {
|
9 |
+
echo "[pre-push] bump_cache failed; ensure Python is installed and scripts/bump_cache.py exists" 1>&2
|
10 |
+
exit 1
|
11 |
+
}
|
12 |
+
|
13 |
+
# If files changed, create an auto-commit so pushed code matches cache version
|
14 |
+
if ! git diff --quiet -- static/sw.js static/index.html static/map.html version.json 2>/dev/null; then
|
15 |
+
git add static/sw.js static/index.html static/map.html version.json
|
16 |
+
git commit -m "chore(cache): auto-bump cache version before push" || true
|
17 |
+
echo "[pre-push] Auto-committed cache bump."
|
18 |
+
fi
|
19 |
+
|
20 |
+
exit 0
|
README.md
CHANGED
@@ -92,3 +92,22 @@ A **secure, robust, and high-performance** web application for mapping, tracking
|
|
92 |
---
|
93 |
|
94 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
---
|
93 |
|
94 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
95 |
+
|
96 |
+
## Developer workflow notes
|
97 |
+
|
98 |
+
### Automatic cache busting on commit/push
|
99 |
+
|
100 |
+
This repo includes a small utility and Git hooks to ensure browsers always fetch the latest static assets:
|
101 |
+
- scripts/bump_cache.py updates service worker, HTML inline version/timestamp, and script tag query params.
|
102 |
+
- .githooks/pre-commit runs the bump (timestamp-only) and stages changed files.
|
103 |
+
- .githooks/pre-push runs a final bump and, if necessary, auto-commits the change before pushing.
|
104 |
+
|
105 |
+
Setup (one-time):
|
106 |
+
- git config core.hooksPath .githooks
|
107 |
+
|
108 |
+
Usage:
|
109 |
+
- Just commit and push normally. The hooks will bump the timestamp each time.
|
110 |
+
- To bump semantic version too: python scripts/bump_cache.py --bump patch (or minor/major). The hooks will stage the changes for your next commit.
|
111 |
+
|
112 |
+
Note for Windows developers:
|
113 |
+
- Git for Windows ships with a POSIX shell that executes hooks. Ensure Python is available on PATH.
|
scripts/bump_cache.py
ADDED
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
Automated cache-busting version bump utility for TreeTrack.
|
4 |
+
|
5 |
+
Updates:
|
6 |
+
- static/sw.js: const VERSION = <timestamp>
|
7 |
+
- static/index.html and static/map.html:
|
8 |
+
* inline currentVersion = '<version>'
|
9 |
+
* inline timestamp = '<timestamp>'
|
10 |
+
* script tag query params (?v=<version>&t=<timestamp>) for tree-track-app.js and map.js
|
11 |
+
|
12 |
+
Usage examples:
|
13 |
+
python scripts/bump_cache.py --now # bump timestamp only
|
14 |
+
python scripts/bump_cache.py --bump patch # bump version 1.2.3 -> 1.2.4 and timestamp
|
15 |
+
python scripts/bump_cache.py --set-version 5.1.2 # set specific version and bump timestamp
|
16 |
+
|
17 |
+
This script maintains state in version.json at repository root.
|
18 |
+
"""
|
19 |
+
|
20 |
+
import argparse
|
21 |
+
import json
|
22 |
+
import os
|
23 |
+
import re
|
24 |
+
import sys
|
25 |
+
import time
|
26 |
+
from pathlib import Path
|
27 |
+
|
28 |
+
ROOT = Path(__file__).resolve().parents[1]
|
29 |
+
VERSION_FILE = ROOT / 'version.json'
|
30 |
+
FILES = {
|
31 |
+
'sw': ROOT / 'static' / 'sw.js',
|
32 |
+
'index': ROOT / 'static' / 'index.html',
|
33 |
+
'map': ROOT / 'static' / 'map.html',
|
34 |
+
}
|
35 |
+
|
36 |
+
SEMVER_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)$")
|
37 |
+
|
38 |
+
|
39 |
+
def read_text(p: Path) -> str:
|
40 |
+
try:
|
41 |
+
return p.read_text(encoding='utf-8')
|
42 |
+
except FileNotFoundError:
|
43 |
+
print(f"Error: file not found: {p}", file=sys.stderr)
|
44 |
+
sys.exit(1)
|
45 |
+
|
46 |
+
|
47 |
+
def write_text(p: Path, content: str) -> None:
|
48 |
+
p.write_text(content, encoding='utf-8', newline='\n')
|
49 |
+
|
50 |
+
|
51 |
+
def bump_semver(version: str, which: str) -> str:
|
52 |
+
m = SEMVER_RE.match(version)
|
53 |
+
if not m:
|
54 |
+
raise ValueError(f"Invalid semver: {version}")
|
55 |
+
major, minor, patch = map(int, m.groups())
|
56 |
+
if which == 'major':
|
57 |
+
major += 1
|
58 |
+
minor = 0
|
59 |
+
patch = 0
|
60 |
+
elif which == 'minor':
|
61 |
+
minor += 1
|
62 |
+
patch = 0
|
63 |
+
elif which == 'patch':
|
64 |
+
patch += 1
|
65 |
+
else:
|
66 |
+
raise ValueError(f"Unknown bump type: {which}")
|
67 |
+
return f"{major}.{minor}.{patch}"
|
68 |
+
|
69 |
+
|
70 |
+
def load_version() -> dict:
|
71 |
+
if VERSION_FILE.exists():
|
72 |
+
with VERSION_FILE.open('r', encoding='utf-8') as f:
|
73 |
+
data = json.load(f)
|
74 |
+
# minimal validation
|
75 |
+
if 'version' not in data or 'timestamp' not in data:
|
76 |
+
raise ValueError('version.json missing required fields')
|
77 |
+
return data
|
78 |
+
# default if not present
|
79 |
+
return {
|
80 |
+
'version': '0.1.0',
|
81 |
+
'timestamp': int(time.time()),
|
82 |
+
}
|
83 |
+
|
84 |
+
|
85 |
+
def save_version(version: str, timestamp: int) -> None:
|
86 |
+
with VERSION_FILE.open('w', encoding='utf-8') as f:
|
87 |
+
json.dump({'version': version, 'timestamp': timestamp}, f, indent=2)
|
88 |
+
f.write('\n')
|
89 |
+
|
90 |
+
|
91 |
+
def update_sw_js(content: str, timestamp: int) -> str:
|
92 |
+
# Replace: const VERSION = 1234567890;
|
93 |
+
new_content, n = re.subn(r"(const\s+VERSION\s*=\s*)(\d+)(\s*;)",
|
94 |
+
rf"\g<1>{timestamp}\g<3>", content)
|
95 |
+
if n == 0:
|
96 |
+
print('Warning: VERSION not updated in sw.js (pattern not found)')
|
97 |
+
return new_content
|
98 |
+
|
99 |
+
|
100 |
+
def update_html(content: str, version: str, timestamp: int, is_index: bool) -> str:
|
101 |
+
updated = content
|
102 |
+
|
103 |
+
# Update inline currentVersion = '<version>'
|
104 |
+
updated, n_ver = re.subn(r"(currentVersion\s*=\s*')[^']*(')", rf"\g<1>{version}\g<2>", updated)
|
105 |
+
if n_ver == 0:
|
106 |
+
print('Warning: currentVersion not updated (pattern not found)')
|
107 |
+
|
108 |
+
# Update inline timestamp = '<timestamp>'
|
109 |
+
updated, n_ts = re.subn(r"(timestamp\s*=\s*')[^']*(')", rf"\g<1>{timestamp}\g<2>", updated)
|
110 |
+
if n_ts == 0:
|
111 |
+
print('Warning: timestamp not updated (pattern not found)')
|
112 |
+
|
113 |
+
# Update script tag query params
|
114 |
+
# For index.html: tree-track-app.js?v=X&t=Y
|
115 |
+
# For map.html: /static/map.js?v=X&t=Y (we use same version for simplicity)
|
116 |
+
if is_index:
|
117 |
+
updated, n_tag = re.subn(r"(tree-track-app\.js\?v=)[^&']+(\&t=)[^'\"]+",
|
118 |
+
rf"\g<1>{version}\g<2>{timestamp}", updated)
|
119 |
+
if n_tag == 0:
|
120 |
+
print('Warning: tree-track-app.js cache params not updated (pattern not found)')
|
121 |
+
else:
|
122 |
+
updated, n_tag = re.subn(r"(/static/map\.js\?v=)[^&']+(\&t=)[^'\"]+",
|
123 |
+
rf"\g<1>{version}\g<2>{timestamp}", updated)
|
124 |
+
if n_tag == 0:
|
125 |
+
print('Warning: map.js cache params not updated (pattern not found)')
|
126 |
+
|
127 |
+
return updated
|
128 |
+
|
129 |
+
|
130 |
+
def main():
|
131 |
+
parser = argparse.ArgumentParser(description='Bump cache-busting version/timestamp across files')
|
132 |
+
group = parser.add_mutually_exclusive_group()
|
133 |
+
group.add_argument('--bump', choices=['major', 'minor', 'patch'], help='Semver bump type')
|
134 |
+
group.add_argument('--set-version', help='Set explicit version (x.y.z)')
|
135 |
+
parser.add_argument('--timestamp', type=int, help='Set explicit timestamp (epoch seconds)')
|
136 |
+
parser.add_argument('--now', action='store_true', help='Use current time as timestamp')
|
137 |
+
args = parser.parse_args()
|
138 |
+
|
139 |
+
state = load_version()
|
140 |
+
version = state['version']
|
141 |
+
timestamp = state['timestamp']
|
142 |
+
|
143 |
+
# Determine new version
|
144 |
+
if args.set_version:
|
145 |
+
if not SEMVER_RE.match(args.set_version):
|
146 |
+
print('Error: --set-version must be in x.y.z format', file=sys.stderr)
|
147 |
+
sys.exit(2)
|
148 |
+
version = args.set_version
|
149 |
+
elif args.bump:
|
150 |
+
version = bump_semver(version, args.bump)
|
151 |
+
|
152 |
+
# Determine new timestamp
|
153 |
+
if args.timestamp is not None:
|
154 |
+
timestamp = int(args.timestamp)
|
155 |
+
elif args.now or args.set_version or args.bump:
|
156 |
+
timestamp = int(time.time())
|
157 |
+
|
158 |
+
# Save version state
|
159 |
+
save_version(version, timestamp)
|
160 |
+
|
161 |
+
# Update files
|
162 |
+
sw_path = FILES['sw']
|
163 |
+
index_path = FILES['index']
|
164 |
+
map_path = FILES['map']
|
165 |
+
|
166 |
+
sw_content = read_text(sw_path)
|
167 |
+
index_content = read_text(index_path)
|
168 |
+
map_content = read_text(map_path)
|
169 |
+
|
170 |
+
sw_new = update_sw_js(sw_content, timestamp)
|
171 |
+
index_new = update_html(index_content, version, timestamp, is_index=True)
|
172 |
+
map_new = update_html(map_content, version, timestamp, is_index=False)
|
173 |
+
|
174 |
+
if sw_new != sw_content:
|
175 |
+
write_text(sw_path, sw_new)
|
176 |
+
print(f"Updated {sw_path}")
|
177 |
+
if index_new != index_content:
|
178 |
+
write_text(index_path, index_new)
|
179 |
+
print(f"Updated {index_path}")
|
180 |
+
if map_new != map_content:
|
181 |
+
write_text(map_path, map_new)
|
182 |
+
print(f"Updated {map_path}")
|
183 |
+
|
184 |
+
print(f"Done. version={version} timestamp={timestamp}")
|
185 |
+
|
186 |
+
|
187 |
+
if __name__ == '__main__':
|
188 |
+
main()
|
static/index.html
CHANGED
@@ -916,8 +916,8 @@
|
|
916 |
<script>
|
917 |
// Force refresh if we detect cached version
|
918 |
(function() {
|
919 |
-
const currentVersion = '5.1.
|
920 |
-
const timestamp = '
|
921 |
const lastVersion = sessionStorage.getItem('treetrack_version');
|
922 |
const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
|
923 |
|
@@ -1162,7 +1162,7 @@
|
|
1162 |
</div>
|
1163 |
</div>
|
1164 |
|
1165 |
-
<script type="module" src="/static/js/tree-track-app.js?v=5.
|
1166 |
|
1167 |
<script>
|
1168 |
// Initialize Granim background animation on page load
|
|
|
916 |
<script>
|
917 |
// Force refresh if we detect cached version
|
918 |
(function() {
|
919 |
+
const currentVersion = '5.1.1';
|
920 |
+
const timestamp = '1755112750'; // Cache-busting bump
|
921 |
const lastVersion = sessionStorage.getItem('treetrack_version');
|
922 |
const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
|
923 |
|
|
|
1162 |
</div>
|
1163 |
</div>
|
1164 |
|
1165 |
+
<script type="module" src="/static/js/tree-track-app.js?v=5.1.1&t=1755112750"></script>
|
1166 |
|
1167 |
<script>
|
1168 |
// Initialize Granim background animation on page load
|
static/map.html
CHANGED
@@ -812,8 +812,8 @@
|
|
812 |
<script>
|
813 |
// Force refresh if we detect cached version
|
814 |
(function() {
|
815 |
-
const currentVersion = '5.1.
|
816 |
-
const timestamp = '
|
817 |
const lastVersion = sessionStorage.getItem('treetrack_version');
|
818 |
const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
|
819 |
|
@@ -939,7 +939,7 @@ const timestamp = '1754659000'; // Current timestamp for cache busting
|
|
939 |
|
940 |
<!-- Leaflet JS -->
|
941 |
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
|
942 |
-
<script src="/static/map.js?v=
|
943 |
|
944 |
"default-state": {
|
945 |
gradients: [
|
|
|
812 |
<script>
|
813 |
// Force refresh if we detect cached version
|
814 |
(function() {
|
815 |
+
const currentVersion = '5.1.1';
|
816 |
+
const timestamp = '1755112750'; // Current timestamp for cache busting
|
817 |
const lastVersion = sessionStorage.getItem('treetrack_version');
|
818 |
const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
|
819 |
|
|
|
939 |
|
940 |
<!-- Leaflet JS -->
|
941 |
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
|
942 |
+
<script src="/static/map.js?v=5.1.1&t=1755112750">
|
943 |
|
944 |
"default-state": {
|
945 |
gradients: [
|
static/sw.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
// TreeTrack Service Worker - PWA and Offline Support
|
2 |
-
const VERSION =
|
3 |
const CACHE_NAME = `treetrack-v${VERSION}`;
|
4 |
const STATIC_CACHE = `static-v${VERSION}`;
|
5 |
const API_CACHE = `api-v${VERSION}`;
|
|
|
1 |
// TreeTrack Service Worker - PWA and Offline Support
|
2 |
+
const VERSION = 1755112750; // Cache busting bump - force clients to fetch new static assets and header image change
|
3 |
const CACHE_NAME = `treetrack-v${VERSION}`;
|
4 |
const STATIC_CACHE = `static-v${VERSION}`;
|
5 |
const API_CACHE = `api-v${VERSION}`;
|
version.json
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"version": "5.1.1",
|
3 |
+
"timestamp": 1755112750
|
4 |
+
}
|