mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-02-01 15:03:36 +00:00
fix: improve build_frontend reliability and cross-platform compatibility
Addresses concerns from PR #76 code review: - Add exception handling for stat() calls to prevent crashes from race conditions when files are deleted/modified during iteration - Add 2-second timestamp tolerance for FAT32 filesystem compatibility (FAT32 has 2-second mtime precision on USB drives/SD cards) - Add config file checks (package.json, vite.config.ts, tailwind.config.ts, tsconfig.json, etc.) that also require rebuilds when changed - Add logging to show which file triggered the rebuild for debugging Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
72
start_ui.py
72
start_ui.py
@@ -141,38 +141,90 @@ def install_npm_deps() -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def build_frontend() -> bool:
|
def build_frontend() -> bool:
|
||||||
"""Build the React frontend if dist doesn't exist or is stale."""
|
"""Build the React frontend if dist doesn't exist or is stale.
|
||||||
|
|
||||||
|
Staleness is determined by comparing modification times of:
|
||||||
|
- Source files in ui/src/
|
||||||
|
- Config files (package.json, vite.config.ts, etc.)
|
||||||
|
Against the newest file in ui/dist/
|
||||||
|
|
||||||
|
Includes a 2-second tolerance for FAT32 filesystem compatibility.
|
||||||
|
"""
|
||||||
dist_dir = UI_DIR / "dist"
|
dist_dir = UI_DIR / "dist"
|
||||||
src_dir = UI_DIR / "src"
|
src_dir = UI_DIR / "src"
|
||||||
|
|
||||||
|
# FAT32 has 2-second timestamp precision, so we add tolerance to avoid
|
||||||
|
# false negatives when projects are on USB drives or SD cards
|
||||||
|
TIMESTAMP_TOLERANCE = 2
|
||||||
|
|
||||||
|
# Config files that should trigger a rebuild when changed
|
||||||
|
CONFIG_FILES = [
|
||||||
|
"package.json",
|
||||||
|
"package-lock.json",
|
||||||
|
"vite.config.ts",
|
||||||
|
"tailwind.config.ts",
|
||||||
|
"tsconfig.json",
|
||||||
|
"tsconfig.node.json",
|
||||||
|
"postcss.config.js",
|
||||||
|
"index.html",
|
||||||
|
]
|
||||||
|
|
||||||
# Check if build is needed
|
# Check if build is needed
|
||||||
needs_build = False
|
needs_build = False
|
||||||
|
trigger_file = None
|
||||||
|
|
||||||
if not dist_dir.exists():
|
if not dist_dir.exists():
|
||||||
needs_build = True
|
needs_build = True
|
||||||
|
trigger_file = "dist/ directory missing"
|
||||||
elif src_dir.exists():
|
elif src_dir.exists():
|
||||||
# Find the newest file in dist/ directory
|
# Find the newest file in dist/ directory
|
||||||
newest_dist_mtime = 0
|
newest_dist_mtime = 0
|
||||||
for dist_file in dist_dir.rglob("*"):
|
for dist_file in dist_dir.rglob("*"):
|
||||||
if dist_file.is_file():
|
try:
|
||||||
file_mtime = dist_file.stat().st_mtime
|
if dist_file.is_file():
|
||||||
if file_mtime > newest_dist_mtime:
|
file_mtime = dist_file.stat().st_mtime
|
||||||
newest_dist_mtime = file_mtime
|
if file_mtime > newest_dist_mtime:
|
||||||
|
newest_dist_mtime = file_mtime
|
||||||
|
except (FileNotFoundError, PermissionError, OSError):
|
||||||
|
# File was deleted or became inaccessible during iteration
|
||||||
|
continue
|
||||||
|
|
||||||
# Check if any source file is newer than the newest dist file
|
|
||||||
if newest_dist_mtime > 0:
|
if newest_dist_mtime > 0:
|
||||||
for src_file in src_dir.rglob("*"):
|
# Check config files first (these always require rebuild)
|
||||||
if src_file.is_file() and src_file.stat().st_mtime > newest_dist_mtime:
|
for config_name in CONFIG_FILES:
|
||||||
needs_build = True
|
config_path = UI_DIR / config_name
|
||||||
break
|
try:
|
||||||
|
if config_path.exists():
|
||||||
|
if config_path.stat().st_mtime > newest_dist_mtime + TIMESTAMP_TOLERANCE:
|
||||||
|
needs_build = True
|
||||||
|
trigger_file = config_name
|
||||||
|
break
|
||||||
|
except (FileNotFoundError, PermissionError, OSError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check source files if no config triggered rebuild
|
||||||
|
if not needs_build:
|
||||||
|
for src_file in src_dir.rglob("*"):
|
||||||
|
try:
|
||||||
|
if src_file.is_file():
|
||||||
|
if src_file.stat().st_mtime > newest_dist_mtime + TIMESTAMP_TOLERANCE:
|
||||||
|
needs_build = True
|
||||||
|
trigger_file = str(src_file.relative_to(UI_DIR))
|
||||||
|
break
|
||||||
|
except (FileNotFoundError, PermissionError, OSError):
|
||||||
|
# File was deleted or became inaccessible during iteration
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
# No files found in dist, need to rebuild
|
# No files found in dist, need to rebuild
|
||||||
needs_build = True
|
needs_build = True
|
||||||
|
trigger_file = "dist/ directory is empty"
|
||||||
|
|
||||||
if not needs_build:
|
if not needs_build:
|
||||||
print(" Frontend already built (up to date)")
|
print(" Frontend already built (up to date)")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if trigger_file:
|
||||||
|
print(f" Rebuild triggered by: {trigger_file}")
|
||||||
print(" Building React frontend...")
|
print(" Building React frontend...")
|
||||||
npm_cmd = "npm.cmd" if sys.platform == "win32" else "npm"
|
npm_cmd = "npm.cmd" if sys.platform == "win32" else "npm"
|
||||||
return run_command([npm_cmd, "run", "build"], cwd=UI_DIR)
|
return run_command([npm_cmd, "run", "build"], cwd=UI_DIR)
|
||||||
|
|||||||
Reference in New Issue
Block a user