#!/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 — abort with error if file missing # concat_files — 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 # — copy to with all '. # The JS engine treats \/ as a regular slash, # so runtime behaviour is unchanged. # compute_build_label [--release []] # — sets globals: build_label, build_version, # is_release, is_red, channel. # See "Channels and release args" below. # promote_release — 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 — copy the just-built dist file to # website/releases/_alpha.html so the # site's alpha hyperlinks always serve the # latest dev build. Called automatically by # each tool's build.sh on plain (non-release) # builds. Plain copy (not symlink) so it # survives deployments whose web server only # mounts website/. `--release alpha` writes # the same file with the formal # "alpha · · " label; the next # plain build clobbers it. # # Channels and release args: # dev build, dist/ only, label "Built: 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 · · " (red). # --release beta beta channel; analogous to alpha. # --release 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 ' tag. Per the HTML5 spec # only block — other tags like are safe # inside a script's text content. Narrowly targeting "$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 [--release []] # 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/_v.html, refresh the # website/releases/_stable.html symlink, and tag -v in # git. Skips silently when the source has not changed since the latest tag. # # Alpha and beta channel releases overwrite website/releases/_.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" } # Mirror the just-built dist file to website/releases/_alpha.html so # the website's alpha hyperlinks always serve whatever dist currently holds. # Plain copy (not symlink): symlinks pointing outside website/ break under # deployments whose web server only mounts website/ (notably the canonical # Caddy setup at /etc/containers/systemd/caddy.container, which mounts # /home/user/src/zddc/website read-only and cannot follow ../ paths to # landing/dist or archive/dist on the host filesystem). # # Trade-off: every dev build that touches a tool's source dirties the # corresponding _alpha.html file in git. Commit those alongside the source # change (or `git checkout` them before pushing) since the alpha channel is # explicitly mutable. # # `--release alpha` overwrites the same file with a "alpha · date · sha" # labeled build; the next plain build clobbers it again. That's the alpha- # is-mutable contract. # # Reads $output_html and $root_dir from caller scope. update_alpha() { _tool="$1" _releases_dir="$root_dir/../website/releases" _dest="${_releases_dir}/${_tool}_alpha.html" mkdir -p "$_releases_dir" # rm first: if the dest is currently a symlink to dist (legacy from the # earlier symlink approach), `cp` would follow the symlink and try to # write to the same file it's reading from. Removing first replaces the # symlink with a plain regular file copy. rm -f "$_dest" cp "$output_html" "$_dest" echo "Mirrored to $_dest" }