From 3d9d385615d05a5bab0ad85a83b392957ed23f91 Mon Sep 17 00:00:00 2001 From: Natalie Date: Fri, 17 Apr 2026 14:54:14 -0700 Subject: [PATCH] =?UTF-8?q?feat(@projects/@magic-civilization):=20?= =?UTF-8?q?=E2=9C=A8=20enable=20multi-episode=20guide=20deployment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- public/games/age-of-dwarves/guide/src/App.tsx | 13 +++- run | 3 + scripts/run/deploy.sh | 74 +++++++++++++++++++ tools/objectives-report.py | 10 ++- 4 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 scripts/run/deploy.sh diff --git a/public/games/age-of-dwarves/guide/src/App.tsx b/public/games/age-of-dwarves/guide/src/App.tsx index b5bdfe62..ed98cc4c 100644 --- a/public/games/age-of-dwarves/guide/src/App.tsx +++ b/public/games/age-of-dwarves/guide/src/App.tsx @@ -36,6 +36,15 @@ import { const DEFAULT_PREFS: PlayerPreferences = { race: 'random', gender: 'random', name: '', colorMode: 'dark', dyslexicFont: false, fontSize: 'md' } +// Episode scope for the EpisodeProvider. Default: 1 (Age of Dwarves — Game 1 +// Early Access only). Set `VITE_DEV_GUIDE=1` at build time to render every +// subtree — used for the contributor-facing dev +// deploy at https://mc.next.black.local, where Game 2/3 work-in-progress +// content should be visible alongside the shipping Game 1 scope. +const DEV_GUIDE_ALL_EPISODES = 999 +const ACTIVE_EPISODE: number = + import.meta.env.VITE_DEV_GUIDE === '1' ? DEV_GUIDE_ALL_EPISODES : 1 + export default function App(): ReactElement { const { preferences, isFirstVisit, save } = usePlayerPreferences() const [searchParams] = useSearchParams() @@ -171,7 +180,7 @@ export default function App(): ReactElement { if (noGui) { return ( - + + diff --git a/run b/run index d803fd63..4a219336 100755 --- a/run +++ b/run @@ -73,6 +73,9 @@ usage() { echo -e "${YELLOW}Tools${NC}" echo " tools:spritegen Sprite generation pipeline" echo " setup Install/verify all dev dependencies (auto-detects OS)" + echo "" + echo -e "${YELLOW}Deploy${NC}" + echo " deploy:guide:next Build dev guide (all episodes) + rsync to mc.next.black.local" } # ── Install args parser (shared by install:* targets) ──────────────── diff --git a/scripts/run/deploy.sh b/scripts/run/deploy.sh new file mode 100644 index 00000000..c8bb01f6 --- /dev/null +++ b/scripts/run/deploy.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Deploy commands: guide:next (dev guide to mc.next.black.local). +# +# This module is Tourguide-owned (see .project/team-leads/tourguide.md). +# +# Host configuration (from .env or shell rc): +# NEXT_DEPLOY_HOST — SSH target for mc.next.black.local static root. +# Default: "lilith@black.local". +# NEXT_DEPLOY_PATH — destination directory on the remote host. +# Default: "/bigdisk/next/mc/". +# +# The target host-nginx vhost + bind-mount are set up in +# `/bigdisk/nginx/nginx.conf` + `/bigdisk/nginx/docker-compose.yml` on +# black.local. Infra + objective: p1-15. + +: "${NEXT_DEPLOY_HOST:=lilith@black.local}" +: "${NEXT_DEPLOY_PATH:=/bigdisk/next/mc/}" + +cmd_deploy_guide_next() { + # Build the dev bundle (all EpisodeGate subtrees visible) + rsync to black. + # Safe to run repeatedly — rsync --delete replaces the target dir with dist/. + + # Prerequisite: WASM artifact present locally. The Vite build imports from + # the @magic-civ/physics-rs alias which resolves to .local/build/wasm/. + if [ ! -f "$REPO_ROOT/.local/build/wasm/magic_civ_physics.js" ]; then + echo -e "${RED}✗ .local/build/wasm/magic_civ_physics.js missing — can't build guide.${NC}" + echo -e "${RED} Build locally: (cd src/simulator && bash build-wasm.sh)${NC}" + echo -e "${RED} Or rsync from apricot:${NC}" + echo -e "${RED} rsync -a \"\$AUTOPLAY_HOST\":\"\$PROJECT_ROOT_REMOTE\"/.local/build/wasm/ .local/build/wasm/${NC}" + return 1 + fi + + echo -e "${BLUE}[1/4] Building dev bundle (VITE_DEV_GUIDE=1 pnpm build)...${NC}" + if ! (cd "$GUIDE_DIR" && VITE_DEV_GUIDE=1 pnpm build 2>&1); then + echo -e "${RED}✗ pnpm build failed${NC}" + return 1 + fi + + local dist="$GUIDE_DIR/dist" + if [ ! -f "$dist/index.html" ]; then + echo -e "${RED}✗ $dist/index.html not produced — build reported success but emitted no bundle.${NC}" + return 1 + fi + local size + size="$(du -sh "$dist" | cut -f1)" + echo -e "${GREEN}✓ dist/ ready ($size)${NC}" + + echo -e "${BLUE}[2/4] Verifying SSH to $NEXT_DEPLOY_HOST...${NC}" + if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "$NEXT_DEPLOY_HOST" "test -d $NEXT_DEPLOY_PATH && echo ok" >/dev/null 2>&1; then + echo -e "${RED}✗ can't reach $NEXT_DEPLOY_HOST:$NEXT_DEPLOY_PATH (VPN or permissions).${NC}" + return 1 + fi + echo -e "${GREEN}✓ reachable${NC}" + + echo -e "${BLUE}[3/4] Rsyncing dist/ → $NEXT_DEPLOY_HOST:$NEXT_DEPLOY_PATH${NC}" + if ! rsync -az --delete "$dist/" "$NEXT_DEPLOY_HOST:$NEXT_DEPLOY_PATH"; then + echo -e "${RED}✗ rsync failed${NC}" + return 1 + fi + echo -e "${GREEN}✓ deployed${NC}" + + echo -e "${BLUE}[4/4] Probing https://mc.next.black.local ...${NC}" + local http_status + http_status="$(curl -sk -o /dev/null -w "%{http_code}" --max-time 10 https://mc.next.black.local)" + if [ "$http_status" = "200" ]; then + echo -e "${GREEN}✓ https://mc.next.black.local → HTTP 200${NC}" + else + echo -e "${YELLOW}! https://mc.next.black.local → HTTP $http_status (expected 200)${NC}" + echo -e "${YELLOW} Deploy may still be correct; check the vhost config on $NEXT_DEPLOY_HOST.${NC}" + return 1 + fi + + echo -e "${GREEN}Deployed dev guide to https://mc.next.black.local${NC}" +} diff --git a/tools/objectives-report.py b/tools/objectives-report.py index 2f8bbe73..c2f77233 100644 --- a/tools/objectives-report.py +++ b/tools/objectives-report.py @@ -25,16 +25,24 @@ TEAM_LEADS_DIR = REPO / ".project" / "team-leads" OUT = OBJ_DIR / "README.md" JSON_OUT = REPO / "public" / "games" / "age-of-dwarves" / "data" / "objectives.json" -VALID_STATUS = {"done", "partial", "stub", "missing", "oos"} +VALID_STATUS = {"done", "partial", "stub", "missing", "oos", "superseded"} VALID_PRIORITY = {"p0", "p1", "p2", "p3"} VALID_SCOPE = {"game1", "game2", "game3"} +# Statuses that appear in the totals tables, Left-To-Do-by-Lead, priority +# groups, and in the structured JSON export consumed by the guide's +# ProgressReportPage. `superseded` is intentionally excluded — those files are +# index stubs that redirect to child objectives; counting them would double- +# count the children and the guide's ObjectiveStatus union would widen. +ACTIVE_STATUSES = {"done", "partial", "stub", "missing", "oos"} + STATUS_ICON = { "done": "✅", "partial": "🟡", "stub": "🔴", "missing": "❌", "oos": "⚫", + "superseded": "♻️", }