From 254db670556b0172b1de90a6ce2651d84e267e5f Mon Sep 17 00:00:00 2001 From: jay Date: Thu, 11 Jun 2026 12:03:55 -0400 Subject: [PATCH] =?UTF-8?q?Deploy:=20stage=20static=20sync=20(assets?= =?UTF-8?q?=E2=86=92shell=E2=86=92SW)=20to=20avoid=20deploy-race=20blank?= =?UTF-8?q?=20screens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per Codex. Shared deploy/sync-static.sh used by both publish scripts: sync new hashed chunks first WITHOUT pruning old ones (grace window so in-flight/old clients keep chunks they still need), then other assets, then index.html, then service-worker.js last — so a new shell never appears before its chunks exist. Old immutable chunks pruned after 14 days to bound disk growth. Co-Authored-By: Claude Opus 4.8 (1M context) --- deploy/publish-web.sh | 4 ++-- deploy/publish.sh | 4 ++-- deploy/sync-static.sh | 20 ++++++++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) create mode 100755 deploy/sync-static.sh diff --git a/deploy/publish-web.sh b/deploy/publish-web.sh index b3b5509..3da1de2 100755 --- a/deploy/publish-web.sh +++ b/deploy/publish-web.sh @@ -11,7 +11,7 @@ site="/home/jay/srv/sites/upbeatbytes" echo "→ building frontend" ( cd "$repo/frontend" && npm run build ) -echo "→ syncing static site to $site" -rsync -a --delete "$repo/frontend/build/" "$site/" +echo "→ syncing static site to $site (assets first, shell + SW last)" +bash "$repo/deploy/sync-static.sh" "$repo/frontend/build" "$site" echo "✓ Published frontend → https://upbeatbytes.com" diff --git a/deploy/publish.sh b/deploy/publish.sh index cadd5d6..f20c870 100755 --- a/deploy/publish.sh +++ b/deploy/publish.sh @@ -10,8 +10,8 @@ api_compose="/home/jay/srv/upbeatbytes/compose.yaml" echo "→ building frontend" ( cd "$repo/frontend" && npm run build ) -echo "→ syncing static site to $site" -rsync -a --delete "$repo/frontend/build/" "$site/" +echo "→ syncing static site to $site (assets first, shell + SW last)" +bash "$repo/deploy/sync-static.sh" "$repo/frontend/build" "$site" echo "→ rebuilding/refreshing API container" docker compose -f "$api_compose" up -d --build diff --git a/deploy/sync-static.sh b/deploy/sync-static.sh new file mode 100755 index 0000000..d523672 --- /dev/null +++ b/deploy/sync-static.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Sync the built static site to the live root in an order that avoids deploy-race +# blank screens. rsync isn't atomic, so a naïve `rsync --delete` can briefly serve +# a NEW index.html that points at chunks not synced yet (→ failed load), or delete +# old chunks an in-flight client still needs. Instead: +# 1. new hashed chunks first, and DON'T prune old ones (grace window) +# 2. other static assets (version.json, env.js, icons…), pruning removed files +# 3. the shell HTML — only once its chunks already exist +# 4. the service worker last — a returning client adopts it only after the rest +# Old immutable chunks are pruned after a grace window to bound disk growth. +set -euo pipefail +src="$1"; site="$2" + +rsync -a "$src/_app/immutable/" "$site/_app/immutable/" +rsync -a --delete \ + --exclude='_app/immutable/***' --exclude='index.html' --exclude='service-worker.js' \ + "$src/" "$site/" +rsync -a "$src/index.html" "$site/index.html" +rsync -a "$src/service-worker.js" "$site/service-worker.js" +find "$site/_app/immutable" -type f -mtime +14 -delete 2>/dev/null || true