Every plain `sh tool/build.sh` invocation now reasserts a relative symlink website/releases/<tool>_alpha.html → ../../<tool>/dist/<tool>.html so the alpha hyperlinks always serve whatever dist currently holds. Idempotent — git sees no churn on rebuild. `--release alpha` still wins by overwriting the symlink with a real "alpha · <date> · <sha>" file; the next plain build re-symlinks it. Five existing alpha files become typechanges (regular file → symlink) — the one-time migration cost. The reassertion survives deployment because the website is served directly from the working tree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
227 lines
9.5 KiB
Bash
Executable file
227 lines
9.5 KiB
Bash
Executable file
#!/bin/sh
|
|
# =============================================================================
|
|
# ZDDC shared build helpers — sourced by each tool's build.sh
|
|
#
|
|
# Usage in a tool build.sh:
|
|
# root_dir=$(cd "$(dirname "$0")" && pwd)
|
|
# . "$root_dir/../shared/build-lib.sh"
|
|
#
|
|
# Provides:
|
|
# ensure_exists <path> — abort with error if file missing
|
|
# concat_files <file ...> — cat each relative path under $root_dir
|
|
# build_timestamp — ISO UTC timestamp string, set at source time;
|
|
# used as build_label for dev builds
|
|
# escape_js_close_tags <in> <out>
|
|
# — copy <in> to <out> with all '</' rewritten as
|
|
# '<\/' so the HTML parser cannot misread the
|
|
# inlined JS as containing a closing </script>.
|
|
# The JS engine treats \/ as a regular slash,
|
|
# so runtime behaviour is unchanged.
|
|
# compute_build_label <tool> [--release [<channel-or-version>]]
|
|
# — sets globals: build_label, build_version,
|
|
# is_release, is_red, channel.
|
|
# See "Channels and release args" below.
|
|
# promote_release <tool> — write to website/releases/ in the layout
|
|
# driven by $channel and $build_version. For
|
|
# stable, also update the _stable.html
|
|
# symlink and create the git tag.
|
|
# update_alpha <tool> — (re)point website/releases/<tool>_alpha.html
|
|
# as a relative symlink at the just-built dist
|
|
# file, so the site's alpha hyperlinks always
|
|
# serve whatever dist currently holds. Called
|
|
# automatically by each tool's build.sh on
|
|
# plain (non-release) builds. Idempotent —
|
|
# reasserting the link on every build leaves
|
|
# git clean. `--release alpha` still works:
|
|
# promote_release replaces the symlink with a
|
|
# real file labeled "alpha · <date> · <sha>"
|
|
# (a deliberate snapshot that persists until
|
|
# the next dev build re-symlinks).
|
|
#
|
|
# Channels and release args:
|
|
# <none> dev build, dist/ only, label "Built: <ts> BETA" (red).
|
|
# --release stable, auto-bump patch from latest tag (or 0.0.1).
|
|
# --release X.Y.Z stable, explicit version.
|
|
# --release alpha alpha channel; mutable file, no tag, label "alpha · <date> · <sha>" (red).
|
|
# --release beta beta channel; analogous to alpha.
|
|
# --release <other> error.
|
|
# =============================================================================
|
|
|
|
# Abort if root_dir is not set by the caller
|
|
if [ -z "${root_dir:-}" ]; then
|
|
echo "build-lib.sh: root_dir must be set before sourcing this file" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Fail hard on any missing source file
|
|
ensure_exists() {
|
|
_path="$1"
|
|
if [ ! -f "$_path" ]; then
|
|
echo "error: missing file: $_path" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Concatenate files listed as positional args, each relative to root_dir
|
|
concat_files() {
|
|
for _rel do
|
|
ensure_exists "$root_dir/$_rel"
|
|
cat "$root_dir/$_rel"
|
|
printf '\n'
|
|
done
|
|
}
|
|
|
|
# ISO UTC build timestamp — set once when this file is sourced
|
|
build_timestamp=$(date -u +"%Y-%m-%d %H:%M:%S")
|
|
|
|
# Rewrite '</script' (case-insensitive) in JS as '<\/script' so the HTML parser
|
|
# cannot mistake string contents for a closing </script> tag. Per the HTML5 spec
|
|
# only </script terminates a <script> block — other tags like </div> are safe
|
|
# inside a script's text content. Narrowly targeting </script avoids corrupting
|
|
# regex literals like /</g whose trailing letter is a flag, not a tag name.
|
|
# The JS engine treats '\/' the same as '/' inside a string, so behaviour is
|
|
# unchanged. See ARCHITECTURE.md "HTML Embedding Safety".
|
|
escape_js_close_tags() {
|
|
sed 's#</\([sS][cC][rR][iI][pP][tT]\)#<\\/\1#g' "$1" > "$2"
|
|
}
|
|
|
|
# Validate that $1 is a strict X.Y.Z numeric version, where each component
|
|
# is a non-empty numeric string. Exits with an error if not.
|
|
_validate_semver() {
|
|
_v="$1"
|
|
_bad() {
|
|
echo "error: invalid release argument: '$_v' (expected: alpha, beta, or X.Y.Z stable version)" >&2
|
|
exit 1
|
|
}
|
|
_v1="${_v%%.*}"
|
|
_rest="${_v#*.}"
|
|
[ "$_rest" = "$_v" ] && _bad
|
|
_v2="${_rest%%.*}"
|
|
_v3="${_rest#*.}"
|
|
{ [ "$_v3" = "$_rest" ] || [ "$_v3" != "${_v3%.*}" ]; } && _bad
|
|
case "$_v1" in '' | *[!0-9]*) _bad ;; esac
|
|
case "$_v2" in '' | *[!0-9]*) _bad ;; esac
|
|
case "$_v3" in '' | *[!0-9]*) _bad ;; esac
|
|
}
|
|
|
|
# Compute build label and channel. Reads positional args:
|
|
# compute_build_label <tool_name> [--release [<channel-or-version>]]
|
|
# Sets global variables:
|
|
# build_label — text rendered into the page's {{BUILD_LABEL}} slot
|
|
# build_version — bare semver string (stable releases only)
|
|
# is_release — "1" for any --release invocation, else "0"
|
|
# is_red — "1" if the label should render red+bold (dev/alpha/beta), else "0"
|
|
# channel — "stable" / "alpha" / "beta" / "" (dev)
|
|
compute_build_label() {
|
|
_tool="$1"
|
|
_flag="${2:-}"
|
|
_arg="${3:-}"
|
|
|
|
is_release=0
|
|
is_red=1
|
|
channel=""
|
|
build_version=""
|
|
|
|
if [ "$_flag" != "--release" ]; then
|
|
build_label="Built: ${build_timestamp} BETA"
|
|
return 0
|
|
fi
|
|
|
|
is_release=1
|
|
|
|
case "$_arg" in
|
|
alpha | beta)
|
|
channel="$_arg"
|
|
_date=$(date -u +"%Y-%m-%d")
|
|
_sha=$(git -C "$root_dir" rev-parse --short=7 HEAD 2>/dev/null || echo "unknown")
|
|
build_label="${channel} · ${_date} · ${_sha}"
|
|
return 0
|
|
;;
|
|
'')
|
|
# Auto-bump patch from latest stable tag for this tool.
|
|
_latest=$(git -C "$root_dir" tag --list "${_tool}-v*" --sort=-v:refname 2>/dev/null | head -1)
|
|
if [ -z "$_latest" ]; then
|
|
build_version="0.0.1"
|
|
else
|
|
_ver="${_latest#${_tool}-v}"
|
|
_major="${_ver%%.*}"
|
|
_rest="${_ver#*.}"
|
|
_minor="${_rest%%.*}"
|
|
_patch="${_rest#*.}"
|
|
_patch=$((_patch + 1))
|
|
build_version="${_major}.${_minor}.${_patch}"
|
|
fi
|
|
;;
|
|
*)
|
|
_validate_semver "$_arg"
|
|
build_version="$_arg"
|
|
;;
|
|
esac
|
|
|
|
channel="stable"
|
|
is_red=0
|
|
build_label="v${build_version}"
|
|
}
|
|
|
|
# Promote a built dist file to the appropriate slot under website/releases/.
|
|
# Reads from caller scope: $channel, $build_version, $output_html, $root_dir.
|
|
#
|
|
# Stable releases write website/releases/<tool>_v<version>.html, refresh the
|
|
# website/releases/<tool>_stable.html symlink, and tag <tool>-v<version> in
|
|
# git. Skips silently when the source has not changed since the latest tag.
|
|
#
|
|
# Alpha and beta channel releases overwrite website/releases/<tool>_<channel>.html
|
|
# in place with no tag (the embedded label carries date + commit SHA, so the
|
|
# source is recoverable from git directly).
|
|
promote_release() {
|
|
_tool="$1"
|
|
_releases_dir="$root_dir/../website/releases"
|
|
mkdir -p "$_releases_dir"
|
|
|
|
if [ "$channel" = "alpha" ] || [ "$channel" = "beta" ]; then
|
|
_dest="${_releases_dir}/${_tool}_${channel}.html"
|
|
cp "$output_html" "$_dest"
|
|
echo "Released $channel to $_dest"
|
|
return 0
|
|
fi
|
|
|
|
if [ "$channel" != "stable" ] || [ -z "$build_version" ]; then
|
|
echo "promote_release: refusing to promote — channel=$channel build_version=$build_version" >&2
|
|
exit 1
|
|
fi
|
|
|
|
_latest=$(git -C "$root_dir" tag --list "${_tool}-v*" --sort=-v:refname 2>/dev/null | head -1)
|
|
if [ -n "$_latest" ] && git -C "$root_dir" diff --quiet "$_latest" HEAD -- . ../shared 2>/dev/null; then
|
|
echo "${_tool}: no source changes since $_latest — skipping"
|
|
return 0
|
|
fi
|
|
|
|
_versioned="${_releases_dir}/${_tool}_v${build_version}.html"
|
|
cp "$output_html" "$_versioned"
|
|
echo "Released $_versioned"
|
|
|
|
# Symlink target is relative to its own directory so the link survives
|
|
# path moves and works regardless of where the website is mounted.
|
|
(cd "$_releases_dir" && ln -sfn "${_tool}_v${build_version}.html" "${_tool}_stable.html")
|
|
echo "Updated ${_tool}_stable.html -> ${_tool}_v${build_version}.html"
|
|
|
|
git -C "$root_dir" tag "${_tool}-v${build_version}"
|
|
echo "Tagged ${_tool}-v${build_version} — run: git push --tags"
|
|
}
|
|
|
|
# (Re)point website/releases/<tool>_alpha.html at the just-built dist file via
|
|
# a relative symlink, so the website's alpha hyperlinks always serve whatever
|
|
# dist currently holds. Called by each tool's build.sh whenever a release is
|
|
# NOT being cut. Idempotent: if the link already points at the right target,
|
|
# git sees no change. `--release alpha` overwrites this link with a real file
|
|
# (with the "alpha · date · sha" label); the next plain build re-symlinks it.
|
|
# Reads $output_html and $root_dir from caller scope.
|
|
update_alpha() {
|
|
_tool="$1"
|
|
_releases_dir="$root_dir/../website/releases"
|
|
_dist_basename=$(basename "$output_html")
|
|
_target="../../${_tool}/dist/${_dist_basename}"
|
|
mkdir -p "$_releases_dir"
|
|
(cd "$_releases_dir" && ln -sfn "$_target" "${_tool}_alpha.html")
|
|
echo "Linked ${_tool}_alpha.html -> $_target"
|
|
}
|