mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
87 lines
3.9 KiB
Bash
87 lines
3.9 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
# Build the React app ONLY when the built output is missing or stale.
|
||
|
|
#
|
||
|
|
# This is the single source of truth for producing app/build/. It is called
|
||
|
|
# from three places that must never drift apart:
|
||
|
|
# - repair.sh (on-device: first boot, manual repair, install, upgrade)
|
||
|
|
# - pi-gen/stage2/04-wrolpi/03-run-chroot.sh (bakes build/ into the Raspberry Pi image)
|
||
|
|
# - debian-live-config/.../9999-wrolpi.hook.chroot (bakes build/ into the Debian Live ISO)
|
||
|
|
#
|
||
|
|
# Both image builds run this at image-build time on the (powerful) build host, so
|
||
|
|
# the shipped image already contains app/build/ with a matching stamp. On the
|
||
|
|
# device the same script then finds the stamp current and does nothing -- a weak
|
||
|
|
# Pi (even 2GB) never has to run the multi-minute webpack build itself. It only
|
||
|
|
# rebuilds when the app source actually changed (e.g. an upgrade pulled new code)
|
||
|
|
# or when build/ is missing/corrupt.
|
||
|
|
#
|
||
|
|
# Offline-safe: this only ever runs `npm run build`, which uses the already
|
||
|
|
# installed node_modules. It never runs `npm install`, so repair.sh keeps its
|
||
|
|
# "will not use internet" guarantee. Installing node_modules stays the caller's
|
||
|
|
# job (the install/upgrade scripts and image hooks do it before calling here).
|
||
|
|
set -e
|
||
|
|
|
||
|
|
APP_DIR="${1:-/opt/wrolpi/app}"
|
||
|
|
cd "$APP_DIR"
|
||
|
|
|
||
|
|
# Hash of every input that determines the built output.
|
||
|
|
#
|
||
|
|
# KEEP IN SYNC: if a build-affecting input is ever added (craco.config.js, an
|
||
|
|
# .env / .env.production, a new config dir, etc.) add it here. A missed input
|
||
|
|
# means a stale build ships silently -- the worst failure mode, because it looks
|
||
|
|
# like everything worked.
|
||
|
|
app_build_stamp() {
|
||
|
|
{
|
||
|
|
sha256sum package.json package-lock.json tsconfig.json
|
||
|
|
find src public -type f -exec sha256sum {} +
|
||
|
|
} | sort | sha256sum | cut -d' ' -f1
|
||
|
|
}
|
||
|
|
|
||
|
|
# Verify the built output is actually intact, not just present.
|
||
|
|
#
|
||
|
|
# A matching stamp only proves the *inputs* are unchanged; it says nothing about
|
||
|
|
# whether the *outputs* survived. repair.sh exists precisely to fix a corrupted
|
||
|
|
# install, so a build/ that still has index.html + a stamp but is missing a JS/CSS
|
||
|
|
# chunk must NOT be treated as current -- otherwise repair would leave the UI
|
||
|
|
# broken where the old unconditional build restored it. Check that every asset
|
||
|
|
# referenced by build/asset-manifest.json exists on disk. node is guaranteed
|
||
|
|
# here: anything that can run `npm run build` can run node.
|
||
|
|
build_output_intact() {
|
||
|
|
[ -f build/asset-manifest.json ] || return 1
|
||
|
|
node -e '
|
||
|
|
const fs = require("fs");
|
||
|
|
let m;
|
||
|
|
try { m = JSON.parse(fs.readFileSync("build/asset-manifest.json", "utf8")); }
|
||
|
|
catch { process.exit(1); } // unparseable manifest -> rebuild
|
||
|
|
const missing = Object.values(m.files || {})
|
||
|
|
.map(p => "build/" + p.replace(/^\//, ""))
|
||
|
|
.filter(p => !fs.existsSync(p));
|
||
|
|
process.exit(missing.length ? 1 : 0);
|
||
|
|
' 2>/dev/null
|
||
|
|
}
|
||
|
|
|
||
|
|
need_build=false
|
||
|
|
if [ ! -f build/index.html ]; then
|
||
|
|
echo "app build is missing."
|
||
|
|
need_build=true
|
||
|
|
elif ! build_output_intact; then
|
||
|
|
echo "app build is present but incomplete/corrupt (missing referenced assets)."
|
||
|
|
need_build=true
|
||
|
|
elif [ "$(app_build_stamp)" != "$(cat build/.build-stamp 2>/dev/null)" ]; then
|
||
|
|
echo "app source changed since the last build."
|
||
|
|
need_build=true
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ "$need_build" = true ]; then
|
||
|
|
echo "Building the React production bundle (this can take several minutes)..."
|
||
|
|
# GENERATE_SOURCEMAP=false: source maps are a debugging aid for the deployed
|
||
|
|
# bundle that WROLPi never ships or uses; skipping them cuts build time and,
|
||
|
|
# importantly on low-RAM devices, peak memory.
|
||
|
|
GENERATE_SOURCEMAP=false BROWSERSLIST_IGNORE_OLD_DATA=true npm run build
|
||
|
|
# Stamp lives inside build/ so it is naturally absent whenever build/ is, and
|
||
|
|
# travels with the baked artifact into the image.
|
||
|
|
app_build_stamp > build/.build-stamp
|
||
|
|
echo "app build complete; wrote build/.build-stamp."
|
||
|
|
else
|
||
|
|
echo "app build is current -- skipping (stamp match)."
|
||
|
|
fi
|