Finishes the 13-09 build-aiui.sh WIP checkpoint (30b2e02f) that survived the
operator reboot ground-truthed and confirmed correct: require_base_path,
frozen-lockfile install, verify_dist's asset-href/commit-attribution checks
all verified working against a real build. Fixed one grep-forbidden leftover
(a comment mentioning the retired scripts/aiui.pin path).
Rewires both AIUI sections of deploy-to-target.sh (primary --live path and
the --both/secondary path) and setup-aiui-server.sh to build/deploy from
aiui/packages/app/dist instead of the retired ../AIUI sibling checkout:
- Primary section now calls scripts/build-aiui.sh instead of an inline
`pnpm build`, then scripts/verify-aiui-deploy.sh after the copy. The
demo/aiui/ fallback now prints a loud, unmissable warning naming that it
is shipping a checked-in dist rather than a fresh build.
- Secondary/--both section retargeted to the in-repo dist path; its
fallback-to-.228-streaming behavior is otherwise unchanged.
- setup-aiui-server.sh calls scripts/build-aiui.sh automatically when the
dist is missing or stale, instead of printing a manual `cd ../AIUI/...`
command and exiting (D-15: enforced, not remembered).
No remaining `../AIUI` reference in either script.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
166 lines
7.6 KiB
Bash
Executable File
166 lines
7.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# build-aiui.sh — the ONE supported way to build AIUI for a node.
|
|
#
|
|
# D-19 (2026-08-03): AIUI is no longer a second repository at
|
|
# git.tx1138.com/lfg2025/AIUI. It was migrated in-repo to aiui/ via
|
|
# `git subtree`, full history intact. There is no second checkout to clone,
|
|
# no separate per-repo pin file to read or write, and no dirty-second-tree
|
|
# refusal to perform — this repo's own ordinary commit discipline is what
|
|
# keeps its history honest now, not a second-repo-specific check. D-15's
|
|
# *delivery* half still stands and is what this script enforces:
|
|
#
|
|
# - VITE_BASE_PATH must be exactly /aiui/. A wrong value produces a BLACK
|
|
# PAGE when embedded — the router base breaks, not the assets (this has
|
|
# burned this project before). Enforced here, not remembered by whoever
|
|
# runs the build.
|
|
# - A fresh checkout of this repo has no aiui/node_modules (unlike the old
|
|
# world, where a developer's separate AIUI clone was assumed already
|
|
# `pnpm install`ed) — this script installs from aiui/pnpm-lock.yaml
|
|
# with --frozen-lockfile before building, and treats a lockfile/
|
|
# package.json mismatch as a hard failure, not something to silently
|
|
# resolve.
|
|
# - The build runs AIUI's own real command (vue-tsc --noEmit && vite
|
|
# build via `pnpm build`), so a type error fails the build loudly
|
|
# instead of shipping a stale dist.
|
|
# - Before anything is copied anywhere, the emitted dist is verified:
|
|
# every local asset href carries the AIUI mount path, and this repo's
|
|
# own current commit (there is no second repo's SHA to pin — D-19 made
|
|
# them the same thing) is discoverable in the output, so a deployed
|
|
# node is attributable to a commit of THIS repo.
|
|
#
|
|
# Usage:
|
|
# bash scripts/build-aiui.sh
|
|
#
|
|
# On success, aiui/packages/app/dist/ is a fresh, verified AIUI build ready
|
|
# to be rsynced/tar'd to a node by scripts/deploy-to-target.sh or
|
|
# scripts/setup-aiui-server.sh.
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
AIUI_ROOT="$PROJECT_DIR/aiui"
|
|
AIUI_APP_DIR="$AIUI_ROOT/packages/app"
|
|
AIUI_DIST="$AIUI_APP_DIR/dist"
|
|
AIUI_MOUNT_PATH="/aiui/"
|
|
|
|
timestamp() { echo "[$(date +%H:%M:%S)]"; }
|
|
|
|
# ── require_base_path ──────────────────────────────────────────────────
|
|
# Fails loudly, not silently, when VITE_BASE_PATH is unset or wrong. The
|
|
# normal case never reaches the "unset" branch below: the caller sets a
|
|
# default via `: "${VITE_BASE_PATH:=$AIUI_MOUNT_PATH}"` before this runs.
|
|
# This function's job is to catch an operator override with the WRONG
|
|
# value (T-13-57: a wrong base path ships a black page to every node).
|
|
require_base_path() {
|
|
if [ -z "${VITE_BASE_PATH:-}" ]; then
|
|
echo "FATAL: VITE_BASE_PATH is unset." >&2
|
|
echo " AIUI must be built with VITE_BASE_PATH=${AIUI_MOUNT_PATH}" >&2
|
|
echo " or every asset href in the built index.html will be" >&2
|
|
echo " wrong and AIUI will render as a BLACK PAGE when" >&2
|
|
echo " embedded at ${AIUI_MOUNT_PATH} on a node." >&2
|
|
return 1
|
|
fi
|
|
if [ "$VITE_BASE_PATH" != "$AIUI_MOUNT_PATH" ]; then
|
|
echo "FATAL: VITE_BASE_PATH='$VITE_BASE_PATH' is wrong." >&2
|
|
echo " AIUI must be built with VITE_BASE_PATH=${AIUI_MOUNT_PATH}" >&2
|
|
echo " exactly. A wrong base path breaks the router base (not" >&2
|
|
echo " the assets) and ships a BLACK PAGE to every node this" >&2
|
|
echo " dist reaches." >&2
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# ── verify_dist ─────────────────────────────────────────────────────────
|
|
# Asserts the build is safe to ship, BEFORE anything is copied anywhere.
|
|
verify_dist() {
|
|
local index="$AIUI_DIST/index.html"
|
|
if [ ! -f "$index" ]; then
|
|
echo "FATAL: $index does not exist — the build did not produce a dist." >&2
|
|
return 1
|
|
fi
|
|
|
|
# Every local (leading-"/") src=/href= must carry the AIUI mount path.
|
|
# A hand-built bundle with the wrong base path gives a black page, and
|
|
# the router base is what actually breaks, not the assets.
|
|
local bad_refs
|
|
bad_refs=$(grep -oE '(src|href)="/[^"]*"' "$index" \
|
|
| grep -v -F "=\"${AIUI_MOUNT_PATH}" || true)
|
|
if [ -n "$bad_refs" ]; then
|
|
echo "FATAL: $index references local assets outside ${AIUI_MOUNT_PATH}:" >&2
|
|
echo "$bad_refs" | sed 's/^/ /' >&2
|
|
return 1
|
|
fi
|
|
|
|
# Attribute this build to THIS repo's own current commit (D-19: no
|
|
# second-repo pin file — this repo's own commit IS the answer now).
|
|
local commit_sha
|
|
commit_sha="$(git -C "$PROJECT_DIR" rev-parse HEAD)"
|
|
{
|
|
echo "commit=$commit_sha"
|
|
echo "built_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
echo "base_path=$VITE_BASE_PATH"
|
|
} > "$AIUI_DIST/BUILD-INFO"
|
|
if ! grep -rq "$commit_sha" "$AIUI_DIST/"; then
|
|
echo "FATAL: this repo's current commit ($commit_sha) is not" >&2
|
|
echo " discoverable anywhere under $AIUI_DIST — a deployed" >&2
|
|
echo " node would be unattributable to a source commit." >&2
|
|
return 1
|
|
fi
|
|
|
|
# Best-effort staleness check: if the source tree changed since the
|
|
# last recorded build but the emitted asset filenames are IDENTICAL,
|
|
# something didn't actually rebuild — Vite's content hashes should
|
|
# differ whenever the content they hash differs. State lives in a
|
|
# gitignored marker beside dist/ (dist/ itself gets wiped by every
|
|
# `vite build`, so it can't hold its own prior-run history).
|
|
local src_hash_file="$AIUI_APP_DIR/.build-aiui-last-src-hash"
|
|
local prev_asset_list="$AIUI_APP_DIR/.build-aiui-last-assets"
|
|
local cur_src_hash cur_assets
|
|
cur_src_hash=$(find "$AIUI_APP_DIR/src" -type f -print0 2>/dev/null \
|
|
| sort -z | xargs -0 sha256sum 2>/dev/null | sha256sum | awk '{print $1}')
|
|
cur_assets=$(ls "$AIUI_DIST/assets" 2>/dev/null | sort)
|
|
if [ -f "$src_hash_file" ] && [ -f "$prev_asset_list" ]; then
|
|
local prev_src_hash prev_assets
|
|
prev_src_hash=$(cat "$src_hash_file")
|
|
prev_assets=$(cat "$prev_asset_list")
|
|
if [ "$prev_src_hash" != "$cur_src_hash" ] && [ "$prev_assets" = "$cur_assets" ] && [ -n "$cur_assets" ]; then
|
|
echo "FATAL: source changed since the last build but the" >&2
|
|
echo " emitted asset filenames are IDENTICAL to the" >&2
|
|
echo " previous build — this looks like a stale/cached" >&2
|
|
echo " dist, not a fresh build." >&2
|
|
return 1
|
|
fi
|
|
fi
|
|
echo "$cur_src_hash" > "$src_hash_file"
|
|
echo "$cur_assets" > "$prev_asset_list"
|
|
|
|
return 0
|
|
}
|
|
|
|
echo "$(timestamp) build-aiui.sh: building AIUI from $AIUI_ROOT (in-repo, D-19)"
|
|
|
|
if [ ! -d "$AIUI_ROOT" ]; then
|
|
echo "FATAL: $AIUI_ROOT does not exist." >&2
|
|
echo " AIUI is expected in-repo at aiui/ — it is no longer a" >&2
|
|
echo " sibling checkout at ../AIUI (D-19)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
: "${VITE_BASE_PATH:=$AIUI_MOUNT_PATH}"
|
|
export VITE_BASE_PATH
|
|
require_base_path
|
|
|
|
echo "$(timestamp) Installing aiui/ workspace from its committed lockfile..."
|
|
(cd "$AIUI_ROOT" && pnpm install --frozen-lockfile)
|
|
|
|
echo "$(timestamp) Building AIUI (vue-tsc --noEmit && vite build)..."
|
|
(cd "$AIUI_APP_DIR" && VITE_BASE_PATH="$VITE_BASE_PATH" pnpm build)
|
|
|
|
echo "$(timestamp) Verifying dist..."
|
|
verify_dist
|
|
|
|
echo "$(timestamp) AIUI build OK — $AIUI_DIST attributable to $(git -C "$PROJECT_DIR" rev-parse --short HEAD)"
|