[pierre] fix: self-heal half-installed vite warm cache

An npm install killed mid-warm leaves node_modules/ populated but no .bin/, so _ensure_warm_cache (which only checked the dir exists) trusted the half-tree forever and every app symlinked to it died with `vite: command not found`. Gate the cache on .bin/vite existing, and harden the app's frontend/run.sh skip-install guard the same way.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
SirKentut
2026-06-20 21:23:33 -07:00
co-authored by Claude Opus 4.8
parent b7450f885a
commit 998e8605c6
2 changed files with 25 additions and 8 deletions
+18 -6
View File
@@ -286,15 +286,27 @@ def _warm_cache_dir() -> str:
return os.path.join(base, _warm_cache_digest())
def _warm_cache_is_complete(cache_modules: str) -> bool:
"""A populated node_modules/ dir is not proof of a *finished* install.
npm links package bins (node_modules/.bin/*) in the final phase, so an
install killed partway (e.g. Electron quit mid-warm) leaves the package
trees on disk but no .bin/. The old `os.path.isdir(node_modules)` check
then trusted that half-tree forever, every app symlinked to it, and
`npm run dev` died with `vite: command not found`. Require the one bin
every webapp-template app actually launches with so a partial cache is
treated as not-ready and repopulated instead of cached as good."""
return os.path.exists(os.path.join(cache_modules, ".bin", "vite"))
def _ensure_warm_cache() -> str | None:
"""Populate the warm-cache node_modules if missing. Returns the
absolute path to the populated `node_modules` directory, or None on
failure. Thread-safe; concurrent callers block on a single install
instead of racing. Idempotent and fast after the first call."""
"""Populate the warm-cache node_modules if missing or incomplete.
Returns the absolute path to the populated `node_modules` directory, or
None on failure. Thread-safe; concurrent callers block on a single
install instead of racing. Idempotent and fast after the first call."""
cache_dir = _warm_cache_dir()
cache_modules = os.path.join(cache_dir, "node_modules")
if os.path.isdir(cache_modules):
if _warm_cache_is_complete(cache_modules):
return cache_modules
# Prefer a pre-extracted bundled tree: junction the workspace straight at it,
@@ -305,7 +317,7 @@ def _ensure_warm_cache() -> str | None:
return bundled
with _warm_cache_lock:
if os.path.isdir(cache_modules):
if _warm_cache_is_complete(cache_modules):
return cache_modules
# Fast path: pre-built archive shipped inside the release. The
# build script generates this so users hitting OpenSwarm for the
@@ -43,8 +43,13 @@ fi
# to vite. Only run npm install when node_modules is genuinely missing
# or empty — e.g. a workspace seeded before the warm-cache existed, or
# the user's cache was cleared.
if [ -d node_modules ] && [ -n "$(ls -A node_modules 2>/dev/null)" ]; then
echo "Dependencies already present — skipping install."
# A non-empty node_modules is NOT proof of a finished install.
# non-empty -> skip" check then trusted that, and `npm run dev` died with
# `vite: command not found`. Gate on the bin we actually launch with so a
# broken/partial tree self-heals via install instead of being skipped.
#So thats why i explicitly have "/.bin/vite"
if [ -e node_modules/.bin/vite ]; then
echo "Dependencies already present - skipping install."
else
echo "Installing dependencies..."
"$NPM" install --prefer-offline --no-audit --no-fund