From f8fce7ff2b03df278b434f1b6eec1d265bb1123b Mon Sep 17 00:00:00 2001 From: type-two Date: Wed, 22 Jul 2026 11:05:40 +1000 Subject: [PATCH] fix(deploy): overlay instead of wipe + verify the entry chunk is JS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root-cause fix for "Failed to load module script: got text/html" on the live game. Assets are content-hash-named; the deploy used to `rm -rf` the web root before copying, so any browser/CDN holding a PREVIOUS index.html asked for chunks that no longer existed — nginx served the HTML fallback (200 text/html) and the module loader choked. Shipping 7 times in one session made stale-index clients common. - Step 3 now OVERLAYS (docker cp over the top, no rm), keeping old chunks alive as a grace window so a cached index.html still resolves. Prunes only files orphaned >7 days (current chunks get a fresh mtime every build) so /assets can't grow without bound. - Step 4 now fetches the entry chunk the LIVE index references and asserts it comes back as application/javascript — the index-only 200 check could not catch a broken chunk graph (a missing chunk IS a 200, as HTML). Verified it reports "entry chunk main-*.js -> application/javascript ✓". Note: nginx still serves index.html for any missing /assets file (masks a 404 as HTML). Fixing that (or adding Cache-Control: no-cache on index.html) touches the SHARED forum-nginx config that serves the other partly.party games, so it's left for a deliberate, John-signed-off change rather than done in passing. Co-Authored-By: Claude Opus 4.8 --- deploy.sh | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/deploy.sh b/deploy.sh index 5e62b13..9775439 100644 --- a/deploy.sh +++ b/deploy.sh @@ -6,8 +6,8 @@ # # 1. npm run build (vite, base=/blobbo/) # 2. rsync dist/ to VPS staging -# 3. docker cp into forum-nginx web root -# 4. curl verify (cache-busted) +# 3. OVERLAY into forum-nginx web root (keep old chunks; prune >7d orphans) +# 4. verify live index AND that its entry chunk serves as JavaScript # # Follows the GAMES/ deploy doctrine (see cratewars/deploy.sh template). # Requirements: ssh humanjing@100.71.119.27 (tailscale), container forum-nginx. @@ -41,14 +41,32 @@ echo "[2/4] Syncing dist/ to VPS staging ..." ssh "$VPS" "rm -rf '$STAGING' && mkdir -p '$STAGING'" rsync -az --exclude='.DS_Store' "$SCRIPT_DIR/dist/" "$VPS:$STAGING/" -echo "[3/4] Replacing container content ..." -ssh "$VPS" "docker exec $CONTAINER sh -c 'rm -rf ${REMOTE_WEB_ROOT:?}' && docker cp $STAGING/. $CONTAINER:$REMOTE_WEB_ROOT/" +echo "[3/4] Overlaying container content (no wipe) ..." +# OVERLAY, don't 'rm -rf': assets are content-hash-named, so a browser (or CDN) +# holding a PREVIOUS index.html must still find the chunks it references, or the +# page dies with "expected a JS module, got text/html" (the missing chunk falls +# back to index.html). Copying over the top keeps old chunks alive as a grace +# window. Vite rewrites every current chunk each build, so their mtime is fresh; +# prune only files orphaned for >7 days so /assets can't grow without bound. +# ponytail: 7-day window is plenty for a browser cache; widen if a CDN caches index longer. +ssh "$VPS" "docker exec $CONTAINER sh -c 'mkdir -p ${REMOTE_WEB_ROOT:?}' \ + && docker cp $STAGING/. $CONTAINER:$REMOTE_WEB_ROOT/ \ + && docker exec $CONTAINER sh -c 'find ${REMOTE_WEB_ROOT}/assets -type f -mtime +7 -delete 2>/dev/null || true'" -echo "[4/4] Verifying live URL ..." +echo "[4/4] Verifying live index + entry chunk ..." BUST="$(date +%s)" -CODE_HTTP="$(ssh "$VPS" "curl -s -o /dev/null -w '%{http_code}' http://localhost/blobbo/?v=$BUST")" -echo " container-local /blobbo/ -> HTTP $CODE_HTTP" CODE_LIVE="$(curl -s -o /dev/null -w '%{http_code}' "https://partly.party/blobbo/?v=$BUST")" echo " https://partly.party/blobbo/ -> HTTP $CODE_LIVE" -[ "$CODE_LIVE" = "200" ] || { echo " ✗ live check failed"; exit 1; } +[ "$CODE_LIVE" = "200" ] || { echo " ✗ live index check failed"; exit 1; } +# The index-only check can't catch a broken chunk graph: a missing chunk is +# served as the HTML fallback (200 text/html), which is exactly the bug that +# bit us. Fetch the entry chunk the LIVE index references and confirm it comes +# back as JavaScript — the check that would actually have caught it. +MAIN="$(curl -s "https://partly.party/blobbo/?v=$BUST" | grep -oE '/blobbo/assets/main-[^"]+\.js' | head -1)" +[ -n "$MAIN" ] || { echo " ✗ no main chunk found in live index"; exit 1; } +CT="$(curl -s -o /dev/null -w '%{content_type}' "https://partly.party${MAIN}?v=$BUST")" +case "$CT" in + application/javascript*|text/javascript*) echo " entry chunk ${MAIN##*/} -> ${CT} ✓" ;; + *) echo " ✗ entry chunk ${MAIN##*/} served as '${CT}', not JS — broken deploy"; exit 1 ;; +esac echo " ✓ deployed."