#!/bin/bash # Build a LEAN per-site artifact from a site profile (ADR-013 Phase 5). # # A site declares its plugins in a profile (deploy/site-profile.example.json). # This resolves the hard-dependency closure, builds the frontend carrying only # those plugins (via SITE_PLUGINS -> scripts/stage-frontend.mjs), and stages a # backend tree containing core + only the chosen plugin dirs. A plugin a site # did not choose ends up in neither the bundle nor the image. # # Usage: scripts/build-site.sh [output-dir] set -euo pipefail REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PROFILE="${1:?usage: build-site.sh [output-dir]}" OUT="${2:-$REPO/build/site}" [ -f "$PROFILE" ] || { echo "profile not found: $PROFILE"; exit 1; } # Resolve the chosen plugins + their hard-dependency closure from the manifests. CLOSURE=$(python3 - "$PROFILE" "$REPO" <<'PY' import json, sys, os profile_path, repo = sys.argv[1], sys.argv[2] chosen = json.load(open(profile_path)).get('plugins', []) plugins_dir = os.path.join(repo, 'plugins') def deps(name): mpath = os.path.join(plugins_dir, name, 'manifest.json') if not os.path.exists(mpath): sys.exit(f'profile plugin not found on disk: {name}') out = [] for dep in json.load(open(mpath)).get('dependencies', []): # name-only (strip any PEP440 range) for sep in '><=!~ ': dep = dep.split(sep)[0] out.append(dep.strip()) return out closure, seen = [], set() def add(name): if name in seen: return seen.add(name) for d in deps(name): add(d) closure.append(name) for p in chosen: add(p) print(','.join(closure)) PY ) echo "Site profile: $PROFILE" echo "Plugin closure: $CLOSURE" # Frontend: build carrying only the closure's plugins. echo "==> Building frontend (SITE_PLUGINS=$CLOSURE) ..." ( cd "$REPO/frontend" && SITE_PLUGINS="$CLOSURE" npm run build --silent ) # Backend: stage core + only the chosen plugin dirs. echo "==> Staging backend into $OUT ..." rm -rf "$OUT" mkdir -p "$OUT/plugins" rsync -a --exclude '__pycache__' --exclude '*.pyc' "$REPO/shopdb" "$OUT/" for name in ${CLOSURE//,/ }; do rsync -a --exclude '__pycache__' --exclude '*.pyc' \ "$REPO/plugins/$name" "$OUT/plugins/" done cp -r "$REPO/frontend/dist" "$OUT/frontend-dist" echo "" echo "Lean site staged at: $OUT" echo " backend plugins: $(ls "$OUT/plugins" | tr '\n' ' ')" echo " (a plugin not listed is absent from both the backend tree and the bundle)"