Phase 5 cleanup: remove legacy HTML components, fix nav-tree fragment
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m43s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m43s
- Remove old raw! layout components (~app-head, ~app-layout, ~oob-response, ~header-row, ~menu-row, ~oob-header, ~header-child) from layout.sexp - Convert nav-tree fragment from Jinja HTML to sexp source, fixing the "Unexpected character: ." parse error caused by HTML leaking into sexp - Add _as_sexp() helper to safely coerce HTML fragments to ~rich-text - Fix federation/sexp/search.sexpr extra closing paren - Remove dead _html() wrappers from blog and account sexp_components - Remove stale render import from cart sexp_components - Add dev_watcher.py to auto-reload on .sexp/.sexpr/.js/.css changes - Add test_parse_all.py to parse-check all 59 sexpr/sexp files - Fix test assertions for sx- attribute prefix (was hx-) - Add sexp.js version logging for cache debugging Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
69
shared/dev_watcher.py
Normal file
69
shared/dev_watcher.py
Normal file
@@ -0,0 +1,69 @@
|
||||
"""Watch non-Python files and trigger Hypercorn reload.
|
||||
|
||||
Hypercorn --reload only watches .py files. This script watches .sexp,
|
||||
.sexpr, .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 = {".sexp", ".sexpr", ".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/<service>/sexp 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()
|
||||
Reference in New Issue
Block a user