"""Watch non-Python files and trigger Hypercorn reload. Hypercorn --reload only watches .py files. This script watches .sx, .sx, .js, and .css files and touches a sentinel .py file when they change, causing Hypercorn to restart. Usage (from entrypoint.sh, before exec hypercorn): python3 -m shared.dev_watcher & """ import os import time import sys WATCH_EXTENSIONS = {".sx", ".sx", ".js", ".css"} SENTINEL = os.path.join(os.path.dirname(__file__), "_reload_sentinel.py") POLL_INTERVAL = 1.5 # seconds def _collect_mtimes(roots): mtimes = {} for root in roots: for dirpath, _dirs, files in os.walk(root): for fn in files: ext = os.path.splitext(fn)[1] if ext in WATCH_EXTENSIONS: path = os.path.join(dirpath, fn) try: mtimes[path] = os.path.getmtime(path) except OSError: pass return mtimes def main(): # Watch /app/shared and /app//sx plus static dirs roots = [] for entry in os.listdir("/app"): full = os.path.join("/app", entry) if os.path.isdir(full): roots.append(full) if not roots: roots = ["/app"] # Ensure sentinel exists if not os.path.exists(SENTINEL): with open(SENTINEL, "w") as f: f.write("# reload sentinel\n") prev = _collect_mtimes(roots) while True: time.sleep(POLL_INTERVAL) curr = _collect_mtimes(roots) changed = [] for path, mtime in curr.items(): if path not in prev or prev[path] != mtime: changed.append(path) if changed: names = ", ".join(os.path.basename(p) for p in changed[:3]) if len(changed) > 3: names += f" (+{len(changed) - 3} more)" print(f"[dev_watcher] Changed: {names} — triggering reload", flush=True) os.utime(SENTINEL, None) prev = curr if __name__ == "__main__": main()