build: simplify to stable + exact-version (drop alpha/beta as public concepts)
Releases publish only two things per tool now: a current-stable
canonical symlink and an immutable per-version file. No more channel
mirrors (_stable/_beta/_alpha) and no more partial-version pins
(_v<X.Y>, _v<X>) — those were debt from a release model that never
matched the project's actual usage.
The `./build beta` verb stays, but narrowed: it's an internal SHA
snapshot for the BMC dev chart pipeline (chart's appVersion pins to
"<X.Y.Z>-beta-<sha>" and the chart Dockerfile fetches that SHA from
git). No public artifact on /srv/zddc/releases/. The embedded/* +
chore commit produced by `./build beta` is the actual snapshot.
`./build alpha` is removed entirely.
build/build-lib.sh:
- Drop alpha verb; narrow beta verb to embedded regen + chore commit
- promote_release: stable cut writes <tool>_v<X.Y.Z>.html + <tool>.html
symlink + <tool>.html.sig companion symlink; beta is a no-op
- promote_zddc_server: same shape — per-version binary +
per-platform canonical symlink (zddc-server_<plat>) + .sig symlink
- write_zddc_server_stub: singular; emits per-version stubs +
one canonical zddc-server.html for current stable
- Delete _promote_channel, verify_channel_links, _channel_is_active
- Seed-from-live now copies only per-version files + .sig + pubkey.pem
(the canonical symlinks get rewritten by this cut; old layout files
get cleaned by deploy's --delete-after)
- build_releases_index: dropdown simplified to "latest stable +
pinned versions"; channels-explainer section removed; tool cards +
CTA URLs point at canonical <tool>.html / zddc-server_<plat>;
composer emits "stable" sentinel for `apps:` entries
- Fix the acl:{allow:[...]} footgun in the apps_pubkey example
apps.go:
- isValidChannelOrVersion: accept only "stable" + exact X.Y.Z
(drop alpha/beta and partial pins v0.0/v0)
- normalizeChannel: same
- Resolve URL composition: stable → canonical <prefix>/<app>.html
(no _stable_ suffix), exact-version → <prefix>/<app>_v<X.Y.Z>.html
- Tests rewritten to match (beta/alpha replaced with v0.0.4 / stable;
a new TestParseSpec_RejectsLegacyChannelsAndPartialPins locks in
that the removed forms now error)
browse/build.sh: gate promote_release on $is_release like every other
tool's build.sh (longstanding inconsistency that errored under the new
promote_release case-statement).
freshen-channel: deleted (no channels to freshen).
Net: -254 lines, all green on full `go test ./...`. Dev build verified
via `./build` (no-arg) — new label format "v<next>-dev · <ts> · <sha>".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
784ed21a34
commit
bdd14609d1
9 changed files with 378 additions and 632 deletions
|
|
@ -116,4 +116,7 @@ echo "Wrote $output_html"
|
|||
|
||||
# Promote AFTER the dist file exists so promote_release can copy from
|
||||
# $output_html. (The order matters — _promote_stable does cp $output_html ...)
|
||||
# Only fires on a release cut; plain dev builds leave release-output alone.
|
||||
if [ "$is_release" = "1" ]; then
|
||||
promote_release "$tool"
|
||||
fi
|
||||
|
|
|
|||
348
build
348
build
|
|
@ -6,68 +6,68 @@ set -eu
|
|||
# ./build dev build: assemble tool dist/, cross-compile
|
||||
# zddc-server binaries. Nothing else is touched
|
||||
# — no release artifacts produced, no deploy,
|
||||
# and zddc/internal/apps/embedded/ is left alone
|
||||
# zddc/internal/apps/embedded/ is left alone
|
||||
# (binary will embed whatever the last beta or
|
||||
# stable cut committed there).
|
||||
# ./build alpha cut alpha: produce a complete release bundle
|
||||
# in dist/release-output/ (cascades nothing).
|
||||
# Like dev, embedded/ is NOT updated — the
|
||||
# invariant is that alpha labels are never baked
|
||||
# into the binary.
|
||||
# ./build beta cut beta (cascades alpha → beta). Updates
|
||||
# embedded/ with beta-labeled tool HTMLs and
|
||||
# commits them — the dev image (which builds
|
||||
# from main) ships those bytes.
|
||||
# ./build release cut coordinated stable (cascades alpha + beta
|
||||
# → new stable; updates embedded/ with stable
|
||||
# labels, makes a release commit, tags all
|
||||
# seven tools at that commit). Prod images
|
||||
# (which build from the latest stable tag)
|
||||
# ship those bytes.
|
||||
# ./build beta internal SHA snapshot for the BMC dev chart.
|
||||
# Updates embedded/ with current tool HTMLs +
|
||||
# makes a `chore(embedded): cut v<X.Y.Z>-beta`
|
||||
# commit; the chart's appVersion pins to that
|
||||
# SHA via Dockerfile parsing. NO public
|
||||
# artifact in dist/release-output/.
|
||||
# ./build release cut coordinated stable. Updates embedded/
|
||||
# with stable-labeled bytes, makes a release
|
||||
# commit, tags all 8 artifacts at that commit,
|
||||
# writes <tool>_v<X.Y.Z>.html + <tool>.html
|
||||
# symlink for every tool and the zddc-server
|
||||
# per-platform binaries into
|
||||
# dist/release-output/.
|
||||
# ./build release X.Y.Z same, explicit version.
|
||||
# ./build help this message.
|
||||
#
|
||||
# Lockstep: every channel/release cut bumps all seven tools (6 HTML +
|
||||
# zddc-server) together. Coordinated next-stable = max(latest tag) + 1.
|
||||
# Lockstep: every release cut bumps all 8 tools (7 HTML + zddc-server)
|
||||
# together. Coordinated next-stable = max(latest tag) + 1.
|
||||
#
|
||||
# Channel/release cuts write a complete intended-live snapshot to
|
||||
# Stable release cuts write a complete intended-live snapshot to
|
||||
# ${ZDDC_DEPLOY_RELEASES_DIR:-$SCRIPT_DIR/dist/release-output}. The build
|
||||
# does NOT touch the live site — run `./deploy` (or `./deploy --releases`)
|
||||
# to rsync the snapshot into /srv/zddc/. The snapshot is built by seeding
|
||||
# from the current live state (so cascades and the verifier see a
|
||||
# complete world), then mutating the channel(s) being cut on top.
|
||||
# does NOT touch the live site — run `./deploy --releases` to rsync the
|
||||
# snapshot into /srv/zddc/. The snapshot is seeded from the current
|
||||
# live state's per-version files (so older immutable artifacts are
|
||||
# preserved), then this cut's <tool>.html canonical symlinks + new
|
||||
# per-version file are written on top.
|
||||
#
|
||||
# Bake-in invariant (what zddc-server's binary embeds via //go:embed):
|
||||
# - prod image (Dockerfile.prod, ZDDC_REF=stable): always stable bytes
|
||||
# - dev image (Dockerfile, ZDDC_REF=main): stable OR beta bytes
|
||||
# (whatever last beta/
|
||||
# stable cut wrote)
|
||||
# - alpha is NEVER baked in. Active dev iteration happens via the tool's
|
||||
# local dist/<tool>.html, not via the binary's embedded copy.
|
||||
# - prod image (Dockerfile.prod): always stable bytes — chart's
|
||||
# Dockerfile.prod fetches the source at the latest
|
||||
# zddc-server-vX.Y.Z tag.
|
||||
# - dev image (Dockerfile): stable OR beta-snapshot bytes — the
|
||||
# chart's appVersion is set to either "X.Y.Z" (stable)
|
||||
# or "X.Y.Z-beta-<sha>" (snapshot), and Dockerfile
|
||||
# fetches that ref. Dev builds (`./build` no-arg) do
|
||||
# NOT touch embedded/, so the binary's baked copy stays
|
||||
# at whatever the last beta or stable cut wrote.
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
|
||||
# Source build-lib.sh once at the top level so the helpers it provides
|
||||
# (promote_zddc_server, write_zddc_server_stubs_all, verify_channel_links,
|
||||
# _coordinated_next_stable) are in scope. Each tool's build.sh sources it
|
||||
# again — that's a no-op on already-defined functions.
|
||||
# (promote_zddc_server, write_zddc_server_stubs_all,
|
||||
# _coordinated_next_stable) are in scope. Each tool's build.sh sources
|
||||
# it again — that's a no-op on already-defined functions.
|
||||
root_dir="$SCRIPT_DIR"
|
||||
. "$SCRIPT_DIR/shared/build-lib.sh"
|
||||
|
||||
# --- Parse subcommand ------------------------------------------------------
|
||||
# RELEASE_CHANNEL empty means dev mode (build only, no website worktree
|
||||
# writes); set means a channel/release cut that promotes to the website
|
||||
# worktree under $ZDDC_DEPLOY_RELEASES_DIR.
|
||||
# RELEASE_CHANNEL empty means dev mode (build only, no release output);
|
||||
# "beta" means an internal SHA snapshot (regenerate embedded/ + commit,
|
||||
# no public artifact); "stable" means a coordinated release cut that
|
||||
# writes to dist/release-output/.
|
||||
RELEASE_CHANNEL=""
|
||||
RELEASE_VERSION=""
|
||||
|
||||
case "${1:-dev}" in
|
||||
dev|build)
|
||||
# Dev build: tool dist/ + zddc-server binaries only. Touches
|
||||
# nothing in the website worktree.
|
||||
;;
|
||||
alpha)
|
||||
RELEASE_CHANNEL="alpha"
|
||||
# nothing in release-output.
|
||||
;;
|
||||
beta)
|
||||
RELEASE_CHANNEL="beta"
|
||||
|
|
@ -84,7 +84,7 @@ case "${1:-dev}" in
|
|||
fi
|
||||
;;
|
||||
help | -h | --help)
|
||||
sed -n '4,22p' "$0" | sed 's/^# \{0,1\}//'
|
||||
sed -n '4,30p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
|
|
@ -112,32 +112,38 @@ export ZDDC_DEPLOY_RELEASES_DIR="${ZDDC_DEPLOY_RELEASES_DIR:-$SCRIPT_DIR/dist/re
|
|||
RELEASES_DIR="$ZDDC_DEPLOY_RELEASES_DIR"
|
||||
mkdir -p "$RELEASES_DIR"
|
||||
|
||||
# When cutting a channel/release, seed RELEASES_DIR from the current live
|
||||
# site so the resulting bundle is a complete intended-live snapshot, not
|
||||
# a sparse one-channel diff. Two reasons:
|
||||
# 1. Per-tool promote_release does cascade writes (beta cut → also
|
||||
# rewrites alpha to track beta; stable cut → resets alpha + beta).
|
||||
# The cascade itself is deterministic, but downstream artifacts that
|
||||
# were NOT touched by this cut (e.g. older versioned files, the
|
||||
# other channel mirrors, partial-version symlinks) still need to be
|
||||
# present in the bundle so `./deploy --releases` (rsync
|
||||
# --delete-after) doesn't wipe them off the live site.
|
||||
# 2. verify_channel_links cross-checks the full release tree; it
|
||||
# flags absent channels as missing. With seeding, a fresh
|
||||
# `dist/release-output/` matches live state, the cut mutates on
|
||||
# top, and the verifier sees a complete world.
|
||||
# Bootstrap case (no live site yet, or live releases dir empty) is
|
||||
# On a stable cut, seed RELEASES_DIR from the current live site so the
|
||||
# resulting bundle is a complete intended-live snapshot, not a sparse
|
||||
# diff. The seed copies the immutable per-version files
|
||||
# (<tool>_v<X.Y.Z>.html, zddc-server_v<X.Y.Z>_<plat>) plus their .sig
|
||||
# files. The cut then writes this version's new per-version files +
|
||||
# refreshes the canonical <tool>.html / zddc-server_<plat> symlinks on
|
||||
# top. `./deploy --releases` (rsync --delete-after) wipes any stale
|
||||
# files in /srv/zddc/releases/ that aren't in the bundle.
|
||||
#
|
||||
# We skip the seed for beta cuts (no public artifacts to produce).
|
||||
# Bootstrap case (no live site yet, or empty live releases dir) is
|
||||
# silently skipped — the very first stable cut populates everything.
|
||||
if [ -n "$RELEASE_CHANNEL" ]; then
|
||||
if [ "$RELEASE_CHANNEL" = "stable" ]; then
|
||||
LIVE_RELEASES="${ZDDC_LIVE_DIR:-/srv/zddc}/releases"
|
||||
if [ -d "$LIVE_RELEASES" ] && [ -n "$(ls -A "$LIVE_RELEASES" 2>/dev/null)" ]; then
|
||||
echo "=== Seeding $RELEASES_DIR from $LIVE_RELEASES ==="
|
||||
echo "=== Seeding $RELEASES_DIR from $LIVE_RELEASES (per-version artifacts only) ==="
|
||||
rm -rf "$RELEASES_DIR"
|
||||
mkdir -p "$RELEASES_DIR"
|
||||
# cp -a preserves the symlink graph (channel mirrors +
|
||||
# _v<X.Y> / _v<X> partial-version pins) so cascade decisions
|
||||
# downstream see the same world the live site has.
|
||||
cp -a "$LIVE_RELEASES/." "$RELEASES_DIR/"
|
||||
# Copy per-version immutable files + their .sig sidecars. The
|
||||
# canonical <tool>.html / zddc-server_<plat> symlinks will be
|
||||
# rewritten by this cut and old channel/partial-pin files from
|
||||
# the previous layout (if any still live there) will be cleaned
|
||||
# up by deploy's --delete-after rsync.
|
||||
find "$LIVE_RELEASES" -maxdepth 1 -type f \( \
|
||||
-name '*_v*.html' -o \
|
||||
-name '*_v*.html.sig' -o \
|
||||
-name 'zddc-server_v*' \
|
||||
\) -exec cp -a '{}' "$RELEASES_DIR/" \;
|
||||
# Also seed the public key (it lives at the releases root).
|
||||
if [ -f "$LIVE_RELEASES/pubkey.pem" ]; then
|
||||
cp -a "$LIVE_RELEASES/pubkey.pem" "$RELEASES_DIR/"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
|
@ -183,11 +189,10 @@ echo "Wrote zddc/dist/web/{index,archive,transmittal,classifier,form,tables,brow
|
|||
|
||||
# Mirror the cascade-served HTMLs into the apps embed source dir so the
|
||||
# next `go build` of zddc-server picks them up via //go:embed. ONLY happens
|
||||
# on a beta or stable cut — that's the project invariant: alpha labels are
|
||||
# never baked into the binary, beta labels go to the dev image (which builds
|
||||
# from main), and stable labels go to prod (which builds from the latest
|
||||
# stable tag). Plain `./build` and `./build alpha` leave the embedded files
|
||||
# untouched; whatever the last beta/stable cut committed remains in place.
|
||||
# on a beta or stable cut — beta cuts feed the dev image (chart pins by
|
||||
# SHA to the embedded-commit), stable cuts feed the prod image (chart
|
||||
# pins to the tag). Plain `./build` leaves embedded files untouched —
|
||||
# whatever the last beta or stable cut committed stays in place.
|
||||
EMBED_DIR="$SCRIPT_DIR/zddc/internal/apps/embedded"
|
||||
if [ "$RELEASE_CHANNEL" = "beta" ] || [ "$RELEASE_CHANNEL" = "stable" ]; then
|
||||
mkdir -p "$EMBED_DIR"
|
||||
|
|
@ -304,10 +309,12 @@ echo " binary version: $ZDDC_BINARY_VERSION"
|
|||
'
|
||||
|
||||
# --- Sign release artifacts -----------------------------------------------
|
||||
# After a channel/release cut has populated $RELEASES_DIR with the actual
|
||||
# bytes for this build, walk the dir and produce a detached Ed25519 .sig
|
||||
# alongside every real artifact. Symlinks (channel mirrors, partial-version
|
||||
# pins) skip — the .sig at the symlink's target is what counts.
|
||||
# After a stable cut has populated $RELEASES_DIR with the actual bytes
|
||||
# for this build, walk the dir and produce a detached Ed25519 .sig
|
||||
# alongside every immutable per-version artifact. Canonical symlinks
|
||||
# (<tool>.html, zddc-server_<plat>) skip — the .sig at the symlink's
|
||||
# target is what counts, and a companion .sig symlink (also written
|
||||
# by promote_release) chains the canonical .sig URL to that target.
|
||||
#
|
||||
# Operators verify with stdlib openssl:
|
||||
#
|
||||
|
|
@ -341,14 +348,15 @@ sign_release_artifacts() {
|
|||
fi
|
||||
# Collect the artifact list first so the signing loop runs in this
|
||||
# shell (no subshell counter scope issue). find: real files only
|
||||
# (-P, the default), matching <tool>_v*.html, <tool>_<channel>.html,
|
||||
# and zddc-server_v*_<plat>(.exe). Excludes the index, stub pages,
|
||||
# and any pre-existing .sig files.
|
||||
# (-P, the default), matching <tool>_v*.html and
|
||||
# zddc-server_v*_<plat>(.exe). The canonical symlinks (<tool>.html /
|
||||
# zddc-server_<plat>) don't get separate .sig files — verification
|
||||
# follows the symlink to the immutable per-version file whose .sig
|
||||
# is signed below. Excludes the index, stub pages, and pre-existing
|
||||
# .sig files.
|
||||
_list=$(find "$_dir" -maxdepth 1 -type f \( \
|
||||
-name '*_v*.html' -o \
|
||||
-name '*_stable.html' -o -name '*_beta.html' -o -name '*_alpha.html' -o \
|
||||
-name 'zddc-server_v*' -o \
|
||||
-name 'zddc-server_stable_*' -o -name 'zddc-server_beta_*' -o -name 'zddc-server_alpha_*' \
|
||||
-name 'zddc-server_v*' \
|
||||
\) ! -name '*.sig' ! -name 'index.html' ! -name 'zddc-server_*.html' 2>/dev/null)
|
||||
|
||||
_signed=0
|
||||
|
|
@ -377,22 +385,22 @@ sign_release_artifacts() {
|
|||
}
|
||||
|
||||
# --- Promote zddc-server release artifacts ---------------------------------
|
||||
# On a channel/release cut, copy the freshly cross-compiled binaries to
|
||||
# the website worktree's releases/ under their canonical names +
|
||||
# symlinks. promote_zddc_server also re-runs write_zddc_server_stubs_all
|
||||
# internally, so the matrix-cell stub pages get regenerated in the same
|
||||
# call. On a plain dev build, skip — we don't touch the worktree.
|
||||
if [ -n "$RELEASE_CHANNEL" ]; then
|
||||
# On a stable cut, copy the freshly cross-compiled binaries to the
|
||||
# release-output bundle under their canonical names + symlinks.
|
||||
# promote_zddc_server also re-runs write_zddc_server_stubs_all
|
||||
# internally, so the per-version + canonical stub pages get regenerated
|
||||
# in the same call. Beta cuts produce no public binary artifact.
|
||||
if [ "$RELEASE_CHANNEL" = "stable" ]; then
|
||||
echo ""
|
||||
echo "=== Promoting zddc-server $RELEASE_CHANNEL release ==="
|
||||
promote_zddc_server "$RELEASE_CHANNEL" "$RELEASE_VERSION" "$RELEASES_DIR" "$SCRIPT_DIR/zddc/dist"
|
||||
echo "=== Promoting zddc-server stable release ==="
|
||||
promote_zddc_server "stable" "$RELEASE_VERSION" "$RELEASES_DIR" "$SCRIPT_DIR/zddc/dist"
|
||||
fi
|
||||
|
||||
# Latest stable version, by following archive_stable.html → versioned target.
|
||||
# Latest stable version, by following archive.html → versioned target.
|
||||
# Returns "" if no stable cut exists yet (bootstrap state). All HTML tools
|
||||
# move in lockstep so any one of them is a valid probe; archive is canonical.
|
||||
_latest_stable_version() {
|
||||
_link="$RELEASES_DIR/archive_stable.html"
|
||||
_link="$RELEASES_DIR/archive.html"
|
||||
[ -L "$_link" ] || return 0
|
||||
_target=$(readlink "$_link")
|
||||
# archive_v0.0.8.html → 0.0.8
|
||||
|
|
@ -403,18 +411,6 @@ _latest_stable_version() {
|
|||
esac
|
||||
}
|
||||
|
||||
# Channel "active" iff the channel mirror is real bytes rather than a
|
||||
# symlink → stable. Used to surface alpha/beta in the dropdown only when
|
||||
# they meaningfully differ from stable. Probes archive (HTML lockstep
|
||||
# representative); zddc-server's probe is its per-platform binary.
|
||||
_channel_is_active() {
|
||||
_ch="$1" # alpha | beta
|
||||
_f="$RELEASES_DIR/archive_${_ch}.html"
|
||||
[ -L "$_f" ] && return 1 # symlink → tracks stable, not "active"
|
||||
[ -f "$_f" ] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
# Regenerate website/releases/index.html as the action-first install
|
||||
# guide (not a matrix). The page guides users to either self-host the
|
||||
# server or download individual tools, with one version dropdown that
|
||||
|
|
@ -450,9 +446,6 @@ build_releases_index() {
|
|||
| sort -Vr
|
||||
)
|
||||
|
||||
_alpha_active="0"; _channel_is_active alpha && _alpha_active="1"
|
||||
_beta_active="0"; _channel_is_active beta && _beta_active="1"
|
||||
|
||||
{
|
||||
cat <<HEAD
|
||||
<!DOCTYPE html>
|
||||
|
|
@ -503,39 +496,21 @@ build_releases_index() {
|
|||
<select id="version-picker">
|
||||
HEAD
|
||||
|
||||
# Channels — selectable directly so users can copy the channel-
|
||||
# mirror URLs (e.g. archive_stable.html) for bookmarks. stable is
|
||||
# the default. The label tells the truth about the channel's
|
||||
# current state: when stable is set, show which version it points
|
||||
# at; when alpha/beta is just a symlink to stable, mark as
|
||||
# "tracks stable" so picking it isn't surprising.
|
||||
printf ' <optgroup label="Channels (mutable URLs)">\n'
|
||||
# "latest" — the canonical URL <tool>.html, a symlink that always
|
||||
# follows the most recently cut stable. Use this when you want
|
||||
# auto-updates. Default option so the page works fully without JS.
|
||||
if [ -n "$_latest" ] && [ "$_latest" != "0.0.0" ]; then
|
||||
printf ' <option value="stable" selected>stable — currently v%s</option>\n' "$_latest"
|
||||
printf ' <option value="latest" selected>latest stable — currently v%s</option>\n' "$_latest"
|
||||
else
|
||||
printf ' <option value="stable" selected>stable</option>\n'
|
||||
printf ' <option value="latest" selected>latest stable</option>\n'
|
||||
fi
|
||||
if [ "$_beta_active" = "1" ]; then
|
||||
printf ' <option value="beta">beta — general testing</option>\n'
|
||||
else
|
||||
printf ' <option value="beta">beta — tracks stable</option>\n'
|
||||
fi
|
||||
if [ "$_alpha_active" = "1" ]; then
|
||||
printf ' <option value="alpha">alpha — active dev</option>\n'
|
||||
else
|
||||
printf ' <option value="alpha">alpha — tracks stable</option>\n'
|
||||
fi
|
||||
printf ' </optgroup>\n'
|
||||
|
||||
# Pinned per-version, latest first. These are the immutable URLs
|
||||
# for reproducibility. No "(current stable)" suffix because the
|
||||
# stable channel above already covers that.
|
||||
printf ' <optgroup label="Pinned versions (immutable URLs)">\n'
|
||||
# Pinned per-version, latest first. Immutable URLs — pin one
|
||||
# into your archive when you depend on a specific behavior.
|
||||
printf '%s\n' "$_all_versions" | while read -r _v; do
|
||||
[ -n "$_v" ] || continue
|
||||
printf ' <option value="v%s">v%s</option>\n' "$_v" "$_v"
|
||||
printf ' <option value="v%s">v%s (pinned)</option>\n' "$_v" "$_v"
|
||||
done
|
||||
printf ' </optgroup>\n'
|
||||
|
||||
cat <<'PICKER_END'
|
||||
</select>
|
||||
|
|
@ -552,24 +527,24 @@ PICKER_END
|
|||
# at least once. Until then, show an honest "not yet released"
|
||||
# placeholder rather than dangling download buttons.
|
||||
_zs_published="0"
|
||||
if [ -e "$RELEASES_DIR/zddc-server_stable_linux-amd64" ]; then
|
||||
if [ -e "$RELEASES_DIR/zddc-server_linux-amd64" ]; then
|
||||
_zs_published="1"
|
||||
fi
|
||||
|
||||
if [ "$_zs_published" = "1" ]; then
|
||||
# Default href is the channel-mirror URL (zddc-server_stable_<plat>)
|
||||
# because "stable" is the dropdown's selected option. Picking a
|
||||
# pinned version from the dropdown rewrites these to the
|
||||
# immutable per-version URL via the IIFE.
|
||||
# Default href is the canonical per-platform URL
|
||||
# (zddc-server_<plat>), a symlink that always points at the
|
||||
# latest stable. Picking a pinned version from the dropdown
|
||||
# rewrites these to the immutable per-version URL via JS.
|
||||
printf ' <a class="dl-primary"\n'
|
||||
printf ' data-tool="zddc-server"\n'
|
||||
printf ' data-platform="linux-amd64"\n'
|
||||
printf ' href="zddc-server_stable_linux-amd64"\n'
|
||||
printf ' href="zddc-server_linux-amd64"\n'
|
||||
printf ' id="dl-primary-binary">\n'
|
||||
printf ' <span class="dl-icon">⬇</span>\n'
|
||||
printf ' <span>Download <span id="dl-primary-platlabel">for Linux (x86_64)</span></span>\n'
|
||||
printf ' </a>\n'
|
||||
printf ' <span class="dl-primary-meta" id="dl-primary-meta">zddc-server_stable_linux-amd64</span>\n'
|
||||
printf ' <span class="dl-primary-meta" id="dl-primary-meta">zddc-server_linux-amd64</span>\n'
|
||||
|
||||
printf ' <div class="dl-secondary-row" id="dl-others">\n'
|
||||
printf ' <span>Other platforms:</span>\n'
|
||||
|
|
@ -581,7 +556,7 @@ PICKER_END
|
|||
_label="${_entry#*|}"
|
||||
_suffix=""
|
||||
case "$_plat" in *windows*) _suffix=".exe" ;; esac
|
||||
printf ' <a data-tool="zddc-server" data-platform="%s" href="zddc-server_stable_%s%s">%s</a>\n' \
|
||||
printf ' <a data-tool="zddc-server" data-platform="%s" href="zddc-server_%s%s">%s</a>\n' \
|
||||
"$_plat" "$_plat" "$_suffix" "$_label"
|
||||
done
|
||||
printf ' </div>\n'
|
||||
|
|
@ -622,9 +597,9 @@ PATH_B_OPEN
|
|||
_rest="${_entry#*|}"
|
||||
_name="${_rest%%|*}"
|
||||
_desc="${_rest#*|}"
|
||||
# Default href is the stable-channel mirror; the dropdown
|
||||
# rewires these per selection.
|
||||
printf ' <a class="tool-card" data-tool="%s" href="%s_stable.html">\n' "$_t" "$_t"
|
||||
# Default href is the canonical symlink <tool>.html; the
|
||||
# dropdown rewires these per selection.
|
||||
printf ' <a class="tool-card" data-tool="%s" href="%s.html">\n' "$_t" "$_t"
|
||||
printf ' <span class="tool-card__title">%s</span>\n' "$_name"
|
||||
printf ' <span class="tool-card__desc">%s</span>\n' "$_desc"
|
||||
printf ' <span class="tool-card__link">Download →</span>\n'
|
||||
|
|
@ -717,12 +692,12 @@ PIN_MID
|
|||
</div>
|
||||
<div class="pin-card">
|
||||
<h3>Verify a download</h3>
|
||||
<p>Each artifact has a matching <code class="inline">.sig</code> file alongside it (<code class="inline">archive_stable.html</code> → <code class="inline">archive_stable.html.sig</code>, etc.). Fetch both, then:</p>
|
||||
<pre>curl -O https://zddc.varasys.io/releases/archive_stable.html
|
||||
curl -O https://zddc.varasys.io/releases/archive_stable.html.sig
|
||||
<p>Each artifact has a matching <code class="inline">.sig</code> file alongside it (<code class="inline">archive.html</code> → <code class="inline">archive.html.sig</code>, etc.). Fetch both, then:</p>
|
||||
<pre>curl -O https://zddc.varasys.io/releases/archive.html
|
||||
curl -O https://zddc.varasys.io/releases/archive.html.sig
|
||||
openssl pkeyutl -verify -pubin -inkey pubkey.pem \
|
||||
-rawin -in archive_stable.html \
|
||||
-sigfile archive_stable.html.sig</pre>
|
||||
-rawin -in archive.html \
|
||||
-sigfile archive.html.sig</pre>
|
||||
<p style="font-size: 0.85rem;">Output is <code class="inline">Signature Verified Successfully</code> on a clean download. Any other output (or no output and a non-zero exit) means the bytes do not match the published signature — do not trust them.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -745,7 +720,8 @@ ZDDC_ROOT=/srv/zddc ./zddc-server</pre>
|
|||
<pre># <ZDDC_ROOT>/.zddc
|
||||
admins: [you@yourcompany.com]
|
||||
acl:
|
||||
allow: ["*@yourcompany.com"]
|
||||
permissions:
|
||||
'*@yourcompany.com': r
|
||||
apps_pubkey: |
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MCowBQYDK2VwAyEAXXaxIUIyOFnhD1eZs02nEt3xZ8izOi7bURFcpJ9iWZY=
|
||||
|
|
@ -756,25 +732,6 @@ apps_pubkey: |
|
|||
<p style="margin-top: var(--spacing-md); font-size: 0.9rem; color: var(--color-text-muted);">When configured, the resolver fetches the <code class="inline">.sig</code> automatically on every URL-pinned <code class="inline">apps:</code> entry and rejects any unsigned or invalid-signature artifact, falling back to the embedded copy. Operators enforcing signature verification on locally-saved artifacts (Path A, "drop a copy into your archive") run the <code class="inline">openssl</code> verify command above as part of their save workflow.</p>
|
||||
</section>
|
||||
|
||||
<!-- ───────────── Channels explainer ───────────── -->
|
||||
<section class="card" style="border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--spacing-lg) var(--spacing-xl); margin-top: var(--spacing-xl); margin-bottom: var(--spacing-xl);">
|
||||
<h2 style="margin-top:0;">Channels</h2>
|
||||
<p>Three channels, applied in lockstep across all tools. Pre-release channels exist to soak changes; <strong>stable</strong> is what production runs.</p>
|
||||
<div class="channel-explainer">
|
||||
<div>
|
||||
<h4 class="alpha">alpha</h4>
|
||||
<p>Active dev iteration. Rebuilds without notice. Look here for the very latest.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="beta">beta</h4>
|
||||
<p>Ready for general testing. Has soaked through alpha. Still mutable — pin to a versioned URL for reproducibility.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="stable">stable</h4>
|
||||
<p>Ready to ship. Every per-version file is immutable; <code>_stable</code> follows the latest cut. Channel cuts cascade: stable cut resets beta and alpha to track stable.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
|
|
@ -809,18 +766,21 @@ apps_pubkey: |
|
|||
var primaryMeta = document.getElementById('dl-primary-meta');
|
||||
var others = document.getElementById('dl-others');
|
||||
|
||||
function isChannel(v) {
|
||||
return v === 'stable' || v === 'beta' || v === 'alpha';
|
||||
}
|
||||
function platBinaryName(slug, plat) {
|
||||
// slug is a channel name ("stable") or a pinned version ("v0.0.8").
|
||||
// The on-disk name uses the slug as-is in both cases since the
|
||||
// channel-mirror filenames are zddc-server_<channel>_<plat> and
|
||||
// per-version are zddc-server_v<X.Y.Z>_<plat>.
|
||||
// slug === "latest" → canonical symlink zddc-server_<plat>;
|
||||
// slug === "v<X.Y.Z>" → immutable per-version zddc-server_v<X.Y.Z>_<plat>.
|
||||
var suf = (plat.indexOf('windows') === 0) ? '.exe' : '';
|
||||
if (slug === 'latest') {
|
||||
return 'zddc-server_' + plat + suf;
|
||||
}
|
||||
return 'zddc-server_' + slug + '_' + plat + suf;
|
||||
}
|
||||
function htmlAssetName(tool, slug) {
|
||||
// slug === "latest" → canonical symlink <tool>.html;
|
||||
// slug === "v<X.Y.Z>" → immutable per-version <tool>_v<X.Y.Z>.html.
|
||||
if (slug === 'latest') {
|
||||
return tool + '.html';
|
||||
}
|
||||
return tool + '_' + slug + '.html';
|
||||
}
|
||||
|
||||
|
|
@ -838,21 +798,25 @@ apps_pubkey: |
|
|||
}
|
||||
|
||||
// Single source of truth: the dropdown's current value drives every
|
||||
// download link's href. Static markup ships with the stable-channel
|
||||
// mirror (`<tool>_stable.html`, `zddc-server_stable_<plat>`) so the
|
||||
// page works fully without JS — the JS just keeps things in sync
|
||||
// when the user picks a different channel or pins a version.
|
||||
// download link's href. Static markup ships with the canonical
|
||||
// URLs (`<tool>.html`, `zddc-server_<plat>`) so the page works
|
||||
// fully without JS — the JS just keeps things in sync when the
|
||||
// user pins a specific version.
|
||||
var picker = document.getElementById('version-picker');
|
||||
if (!picker) return;
|
||||
|
||||
function rewire(slug) {
|
||||
// slug ∈ {"stable", "beta", "alpha"} | "v<X.Y.Z>". Every link with
|
||||
// a data-tool attribute is a download URL the dropdown owns.
|
||||
// slug === "latest" | "v<X.Y.Z>". Every link with a data-tool
|
||||
// attribute is a download URL the dropdown owns.
|
||||
document.querySelectorAll('[data-tool]').forEach(function(a) {
|
||||
var tool = a.dataset.tool;
|
||||
var plat = a.dataset.platform || '';
|
||||
if (tool === 'zddc-server') {
|
||||
a.href = plat ? platBinaryName(slug, plat) : ('zddc-server_' + slug + '.html');
|
||||
if (plat) {
|
||||
a.href = platBinaryName(slug, plat);
|
||||
} else {
|
||||
a.href = (slug === 'latest') ? 'zddc-server.html' : ('zddc-server_' + slug + '.html');
|
||||
}
|
||||
} else {
|
||||
a.href = htmlAssetName(tool, slug);
|
||||
}
|
||||
|
|
@ -891,13 +855,17 @@ apps_pubkey: |
|
|||
var optionsHTML = picker.innerHTML;
|
||||
selects.forEach(function(sel) {
|
||||
sel.innerHTML = optionsHTML;
|
||||
sel.value = 'stable'; // default per-app
|
||||
sel.value = 'latest'; // default per-app
|
||||
});
|
||||
|
||||
function rebuild() {
|
||||
// The picker uses "latest" as its sentinel; the .zddc apps:
|
||||
// cascade resolves "stable" the same way (follow the upstream
|
||||
// canonical URL), so emit "stable" for the YAML user.
|
||||
var lines = ['apps:'];
|
||||
selects.forEach(function(sel) {
|
||||
lines.push(' ' + sel.dataset.app + ': ' + sel.value);
|
||||
var val = (sel.value === 'latest') ? 'stable' : sel.value;
|
||||
lines.push(' ' + sel.dataset.app + ': ' + val);
|
||||
});
|
||||
textarea.value = lines.join('\n') + '\n';
|
||||
}
|
||||
|
|
@ -934,9 +902,9 @@ PIN_END
|
|||
echo "Wrote $_out"
|
||||
}
|
||||
|
||||
# Matrix index + verifier only run when we touched the website
|
||||
# worktree. Dev builds leave the worktree alone.
|
||||
if [ -n "$RELEASE_CHANNEL" ]; then
|
||||
# Sign artifacts + regenerate releases/index.html on stable cuts.
|
||||
# Beta cuts produce no public artifact, so nothing to sign or index.
|
||||
if [ "$RELEASE_CHANNEL" = "stable" ]; then
|
||||
echo ""
|
||||
echo "=== Signing release artifacts ==="
|
||||
sign_release_artifacts "$RELEASES_DIR"
|
||||
|
|
@ -944,10 +912,6 @@ if [ -n "$RELEASE_CHANNEL" ]; then
|
|||
echo ""
|
||||
echo "=== Building releases/index.html ==="
|
||||
build_releases_index
|
||||
|
||||
echo ""
|
||||
echo "=== Verifying channel links ==="
|
||||
verify_channel_links "$RELEASES_DIR"
|
||||
fi
|
||||
|
||||
# --- Embedded commit (stable + beta cuts) ---------------------------------
|
||||
|
|
@ -956,12 +920,11 @@ fi
|
|||
#
|
||||
# 1. Stable: the next tag block needs HEAD to point at the bytes the
|
||||
# stable binary will serve. Without this commit, tags would land on
|
||||
# the source-side commit (with alpha-dirty embedded/*) and prod
|
||||
# images compiled from `git checkout zddc-server-vX.Y.Z` would
|
||||
# ship alpha bytes. (Original justification — preserved.)
|
||||
# the source-side commit (with stale embedded/*) and prod images
|
||||
# compiled from `git checkout zddc-server-vX.Y.Z` would ship stale
|
||||
# bytes. (Original justification — preserved.)
|
||||
#
|
||||
# 2. Beta: the dev pipeline pins the chart's appVersion to a SHA
|
||||
# (.forgejo/scripts/notify-chart-bump.sh reads HEAD). For that
|
||||
# 2. Beta: the dev chart pipeline pins appVersion to a SHA. For that
|
||||
# pin to point at a SHA where embedded/* matches what the binary
|
||||
# will serve, HEAD has to advance past the source-side commit.
|
||||
# Without this commit, the chart pin lags one commit and the dev
|
||||
|
|
@ -1037,7 +1000,8 @@ if [ -z "$RELEASE_CHANNEL" ]; then
|
|||
echo " tool/dist/*.html ready"
|
||||
echo " zddc/dist/zddc-server-* binaries ready"
|
||||
echo ""
|
||||
echo "To cut alpha into a deployable bundle: ./build alpha"
|
||||
echo "For an internal SHA snapshot (BMC dev chart): ./build beta"
|
||||
echo "To cut a stable release: ./build release"
|
||||
else
|
||||
echo "Cut: $RELEASE_CHANNEL"
|
||||
if [ -n "$RELEASE_VERSION" ]; then
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
#!/bin/sh
|
||||
# =============================================================================
|
||||
# freshen-channel — rebuild a tool's alpha or beta channel from its current
|
||||
# stable tag, so users tracking that channel are never on code older than
|
||||
# current stable.
|
||||
#
|
||||
# Usage:
|
||||
# ./freshen-channel <tool> <channel>
|
||||
# tool archive | transmittal | classifier | browse | landing | form | tables
|
||||
# channel alpha | beta
|
||||
#
|
||||
# Why this exists:
|
||||
# Stable releases do NOT automatically clobber alpha/beta files (see
|
||||
# AGENTS.md "Channel discipline" rule 4). After cutting stable v0.0.5,
|
||||
# users pinned to alpha may be on an older build than current stable —
|
||||
# that violates the stale-channel rule. Run this to drag alpha (or
|
||||
# beta) forward to whatever stable currently is.
|
||||
#
|
||||
# What it does:
|
||||
# 1. Finds the latest <tool>-v* tag.
|
||||
# 2. Creates a temporary git worktree at that tag — does NOT touch
|
||||
# your current branch or working tree.
|
||||
# 3. Runs <tool>/build.sh --release <channel> inside the worktree.
|
||||
# 4. Copies the resulting <tool>_<channel>.html into the main repo's
|
||||
# website/releases/.
|
||||
# 5. Removes the worktree.
|
||||
#
|
||||
# The on-page label of the freshened build will be
|
||||
# `<channel> · <today> · <stable-tag-sha>` — the SHA encodes which
|
||||
# stable was used as the source, so anyone debugging can `git checkout`
|
||||
# that exact commit.
|
||||
#
|
||||
# Note: the build pipeline used is the one AT THE TAG, not the latest
|
||||
# main. That is intentional — pure reproducibility. If you have made
|
||||
# build-system improvements since stable was cut and want the freshen
|
||||
# to use them, cut a new stable that includes those changes first.
|
||||
# =============================================================================
|
||||
set -eu
|
||||
|
||||
TOOL="${1:-}"
|
||||
CHANNEL="${2:-}"
|
||||
|
||||
case "$TOOL" in
|
||||
archive | transmittal | classifier | browse | landing | form | tables) ;;
|
||||
*)
|
||||
echo "usage: $0 <tool> <channel>" >&2
|
||||
echo " tool: archive | transmittal | classifier | browse | landing | form | tables" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$CHANNEL" in
|
||||
alpha | beta) ;;
|
||||
*)
|
||||
echo "usage: $0 <tool> <channel>" >&2
|
||||
echo " channel: alpha | beta (stable is what you are freshening FROM)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
REPO=$(cd "$(dirname "$0")" && pwd)
|
||||
|
||||
# Find the latest stable tag for the tool.
|
||||
LATEST_TAG=$(git -C "$REPO" tag --list "${TOOL}-v*" --sort=-v:refname | head -1)
|
||||
if [ -z "$LATEST_TAG" ]; then
|
||||
echo "error: no stable tag found for ${TOOL} (looking for ${TOOL}-v*)" >&2
|
||||
echo " cut a stable release first: sh ${TOOL}/build.sh --release [version]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Temporary detached worktree at the stable tag. Cleaned up on exit.
|
||||
WT=$(mktemp -d)
|
||||
cleanup() {
|
||||
git -C "$REPO" worktree remove --force "$WT" >/dev/null 2>&1 || true
|
||||
rm -rf "$WT"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
echo "Freshening ${TOOL} ${CHANNEL} from ${LATEST_TAG}"
|
||||
git -C "$REPO" worktree add --quiet --detach "$WT" "$LATEST_TAG"
|
||||
|
||||
# Build in the worktree. The tool's build.sh resolves its release dir
|
||||
# from $ZDDC_DEPLOY_RELEASES_DIR (default $REPO/dist/release-output);
|
||||
# pass through whatever the parent process has set so freshen-channel
|
||||
# honors the same target as the regular build.
|
||||
DEPLOY_DIR="${ZDDC_DEPLOY_RELEASES_DIR:-$REPO/dist/release-output}"
|
||||
mkdir -p "$DEPLOY_DIR"
|
||||
ZDDC_DEPLOY_RELEASES_DIR="$DEPLOY_DIR" \
|
||||
sh "$WT/${TOOL}/build.sh" --release "$CHANNEL"
|
||||
|
||||
DST="$DEPLOY_DIR/${TOOL}_${CHANNEL}.html"
|
||||
if [ ! -f "$DST" ]; then
|
||||
echo "error: build did not produce $DST" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Wrote $DST"
|
||||
echo "Done. ${CHANNEL} channel for ${TOOL} now reflects ${LATEST_TAG}."
|
||||
echo "Run ./deploy --releases to push it to the live site."
|
||||
|
|
@ -17,42 +17,39 @@
|
|||
# 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>]]
|
||||
# compute_build_label <tool> [--release [<beta-or-version>]]
|
||||
# — sets globals: build_label, build_version,
|
||||
# is_release, is_red, channel.
|
||||
# See "Channels and release args" below.
|
||||
# promote_release <tool> — for stable / alpha / beta, copy the dist
|
||||
# HTML into the release-output bundle
|
||||
# (default $root_dir/../dist/release-output;
|
||||
# override $ZDDC_DEPLOY_RELEASES_DIR). Stable
|
||||
# cuts write the immutable per-version file +
|
||||
# refresh five symlinks (_v<X.Y>, _v<X>,
|
||||
# _stable, _beta, _alpha) and tag
|
||||
# <tool>-v<X.Y.Z>. Alpha/beta cuts
|
||||
# overwrite the channel mirror in place
|
||||
# and cascade alpha → beta. No git tags
|
||||
# for alpha/beta cuts. The bundle is a
|
||||
# complete intended-live snapshot — the
|
||||
# top-level ./build seeds it from
|
||||
# /srv/zddc/releases/ before per-tool
|
||||
# promote runs, then ./deploy --releases
|
||||
# rsyncs it back. See ARCHITECTURE.md
|
||||
# "Channels" for the full table.
|
||||
# See "Release args" below.
|
||||
# promote_release <tool> — for stable cuts, copy the dist HTML into
|
||||
# the release-output bundle (default
|
||||
# $root_dir/../dist/release-output;
|
||||
# override $ZDDC_DEPLOY_RELEASES_DIR).
|
||||
# Writes the immutable per-version file
|
||||
# <tool>_v<X.Y.Z>.html plus the canonical
|
||||
# symlink <tool>.html pointing at it.
|
||||
# Tagging is centralized in the top-level
|
||||
# ./build (after the embedded commit).
|
||||
# Beta cuts produce NO public artifact —
|
||||
# they are an internal SHA snapshot for
|
||||
# the BMC dev chart pipeline; the
|
||||
# embedded/* regeneration + chore commit
|
||||
# in the top-level ./build is the actual
|
||||
# artifact (chart appVersion pins to that
|
||||
# SHA, Dockerfile fetches it from git).
|
||||
#
|
||||
# Channels and release args:
|
||||
# Release args:
|
||||
# <none> dev build, tool/dist/ only, label
|
||||
# "v<next-stable>-alpha · <ts> · <sha>[-dirty]" (red).
|
||||
# No release-output side-effect. To produce a deployable
|
||||
# bundle, re-run with `--release alpha`.
|
||||
# --release stable, auto-bump patch from latest tag (or 0.0.1).
|
||||
# Writes per-version file + symlinks; tags vX.Y.Z.
|
||||
# --release X.Y.Z stable, explicit version.
|
||||
# --release alpha alpha channel cut at HEAD;
|
||||
# label "v<next-stable>-alpha · <date> · <sha>" (red).
|
||||
# Overwrites <tool>_alpha.html. No tag.
|
||||
# --release beta beta channel; label "v<next-stable>-beta · <date> · <sha>".
|
||||
# Overwrites <tool>_beta.html. Cascades <tool>_alpha.html
|
||||
# → <tool>_beta.html (symlink). No tag.
|
||||
# "v<next-stable>-dev · <ts> · <sha>[-dirty]" (red).
|
||||
# No release-output side-effect.
|
||||
# --release stable cut, auto-bump patch from latest tag (or 0.0.1).
|
||||
# Writes <tool>_v<X.Y.Z>.html + <tool>.html symlink;
|
||||
# tagged later by ./build.
|
||||
# --release X.Y.Z stable cut, explicit version.
|
||||
# --release beta internal SHA snapshot for the BMC dev chart. Build
|
||||
# label is "v<next-stable>-beta · <date> · <sha>";
|
||||
# no public artifact, no tag. The top-level ./build
|
||||
# regenerates zddc/internal/apps/embedded/ + commits.
|
||||
# --release <other> error.
|
||||
# =============================================================================
|
||||
|
||||
|
|
@ -117,7 +114,7 @@ escape_js_close_tags() {
|
|||
_validate_semver() {
|
||||
_v="$1"
|
||||
_bad() {
|
||||
echo "error: invalid release argument: '$_v' (expected: alpha, beta, or X.Y.Z stable version)" >&2
|
||||
echo "error: invalid release argument: '$_v' (expected: beta, or X.Y.Z stable version)" >&2
|
||||
exit 1
|
||||
}
|
||||
_v1="${_v%%.*}"
|
||||
|
|
@ -172,28 +169,25 @@ _source_commit_short_sha() {
|
|||
}
|
||||
|
||||
# Compute build label and channel. Reads positional args:
|
||||
# compute_build_label <tool_name> [--release [<channel-or-version>]]
|
||||
# compute_build_label <tool_name> [--release [<beta-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)
|
||||
# is_red — "1" if the label should render red+bold (dev/beta), else "0"
|
||||
# channel — "stable" / "beta" / "dev"
|
||||
#
|
||||
# Versioning: pre-release semver. The next-stable target is computed from
|
||||
# the latest clean tool-vX.Y.Z tag (patch-bump). Plain builds and
|
||||
# `--release alpha`/`--release beta` carry the next-stable target as a
|
||||
# pre-release suffix in the on-page label so users can see which stable
|
||||
# the alpha/beta is working toward. Stable releases write a clean
|
||||
# vX.Y.Z label and tag.
|
||||
# the latest clean tool-vX.Y.Z tag (patch-bump). Plain dev builds and
|
||||
# `--release beta` carry the next-stable target as a pre-release suffix
|
||||
# in the on-page label so users can see which stable the snapshot is
|
||||
# working toward. Stable releases write a clean vX.Y.Z label and tag.
|
||||
#
|
||||
# HTML tools do NOT tag alpha/beta cuts (consistent with current
|
||||
# behavior — alpha and beta artifacts are mutable files, not immutable
|
||||
# per-build snapshots). Plain dev builds and `--release alpha|beta`
|
||||
# cuts share the same on-page label format — full UTC timestamp + short
|
||||
# source SHA — so testers see one rendering shape regardless of how the
|
||||
# build was produced. A plain dev build may carry a "-dirty" SHA suffix
|
||||
# when the working tree has uncommitted changes; release cuts don't.
|
||||
# HTML tools do NOT tag beta cuts — beta produces no public artifact
|
||||
# (the chart pins by SHA via appVersion). Plain dev builds and beta
|
||||
# cuts share the same on-page label format (full UTC timestamp + short
|
||||
# source SHA). A plain dev build may carry a "-dirty" SHA suffix when
|
||||
# the working tree has uncommitted changes; release cuts don't.
|
||||
compute_build_label() {
|
||||
_tool="$1"
|
||||
_flag="${2:-}"
|
||||
|
|
@ -208,17 +202,17 @@ compute_build_label() {
|
|||
_next_stable=$(_next_stable_for_tool "$_tool")
|
||||
|
||||
if [ "$_flag" != "--release" ]; then
|
||||
# Plain builds are dev builds — labeled as the alpha channel because
|
||||
# that's what the next formal cut would produce, but no Codeberg upload
|
||||
# happens until `--release alpha` is invoked. Full timestamp (granular
|
||||
# than date) and -dirty marker distinguish iterative dev builds from
|
||||
# formal `--release alpha` cuts (which stamp date-only).
|
||||
# Plain builds are dev iteration — tool/dist/ only, no release
|
||||
# output. The label includes the next-stable target so a developer
|
||||
# opening the local dist file can see which version-in-progress
|
||||
# they're looking at. Full timestamp + dirty marker distinguish
|
||||
# iterative dev builds from formal cuts.
|
||||
_sha=$(git -C "$root_dir" rev-parse --short=7 HEAD 2>/dev/null || echo "unknown")
|
||||
if ! git -C "$root_dir" diff --quiet HEAD 2>/dev/null; then
|
||||
_sha="${_sha}-dirty"
|
||||
fi
|
||||
channel="alpha"
|
||||
build_label="v${_next_stable}-alpha · ${build_timestamp} · ${_sha}"
|
||||
channel="dev"
|
||||
build_label="v${_next_stable}-dev · ${build_timestamp} · ${_sha}"
|
||||
_emit_build_label_sidecar "$_tool"
|
||||
return 0
|
||||
fi
|
||||
|
|
@ -226,14 +220,16 @@ compute_build_label() {
|
|||
is_release=1
|
||||
|
||||
case "$_arg" in
|
||||
alpha | beta)
|
||||
channel="$_arg"
|
||||
# Full UTC timestamp + short source SHA — same format as
|
||||
# plain dev builds. _source_commit_short_sha walks past
|
||||
# any `chore(embedded): cut …` auto-commit at HEAD so a
|
||||
# re-cut on unchanged source produces the same SHA.
|
||||
beta)
|
||||
channel="beta"
|
||||
# Internal SHA snapshot for the BMC dev chart. The chart's
|
||||
# appVersion gets set to "<next>-beta-<sha>" and the
|
||||
# Dockerfile parses the suffix to fetch this SHA from git.
|
||||
# _source_commit_short_sha walks past any `chore(embedded):
|
||||
# cut …` auto-commit at HEAD so a re-cut on unchanged
|
||||
# source produces the same SHA.
|
||||
_sha=$(_source_commit_short_sha)
|
||||
build_label="v${_next_stable}-${channel} · ${build_timestamp} · ${_sha}"
|
||||
build_label="v${_next_stable}-beta · ${build_timestamp} · ${_sha}"
|
||||
_emit_build_label_sidecar "$_tool"
|
||||
return 0
|
||||
;;
|
||||
|
|
@ -266,14 +262,14 @@ _emit_build_label_sidecar() {
|
|||
|
||||
# Tools that participate in the lockstep release. Source of truth — used
|
||||
# by helpers that enumerate "all release artifacts" (matrix render,
|
||||
# coordinated next-stable, channel-link verifier).
|
||||
# coordinated next-stable).
|
||||
ZDDC_RELEASE_TOOLS="archive transmittal classifier landing form tables browse zddc-server"
|
||||
|
||||
# Compute the next-stable target for a single tool — patch-bump of its own
|
||||
# latest <tool>-vX.Y.Z tag. Used by compute_build_label so a tool's
|
||||
# alpha/beta on-page label still reads against its own history (e.g. an
|
||||
# alpha cut for a tool that's been quiet still labels itself targeting that
|
||||
# tool's next stable, even when the lockstep convention is in force).
|
||||
# on-page label reads against its own history (e.g. a beta cut for a
|
||||
# tool that's been quiet still labels itself targeting that tool's next
|
||||
# stable, even when the lockstep convention is in force).
|
||||
_next_stable_for_tool() {
|
||||
_t="$1"
|
||||
_latest=$(git -C "$root_dir" tag --list "${_t}-v*" 2>/dev/null \
|
||||
|
|
@ -315,30 +311,22 @@ _coordinated_next_stable() {
|
|||
}
|
||||
|
||||
# Promote a built dist file to the release-output bundle. Reads from caller
|
||||
# scope: $channel ("stable" / "alpha" / "beta"), $build_version (stable only),
|
||||
# scope: $channel ("stable" / "beta"), $build_version (stable only),
|
||||
# $output_html, $root_dir. Bundle path resolves from $ZDDC_DEPLOY_RELEASES_DIR
|
||||
# (default $root_dir/../dist/release-output).
|
||||
#
|
||||
# Stable cuts:
|
||||
# 1. Skip if source unchanged since latest stable tag.
|
||||
# 2. Copy dist HTML → <bundle>/<tool>_v<X.Y.Z>.html (immutable).
|
||||
# 3. Refresh symlinks: _v<X.Y>, _v<X>, _stable, _beta, _alpha all → the
|
||||
# new versioned file. Cascade rule: stable cut means beta and alpha
|
||||
# reset to stable (no active dev on either downstream channel).
|
||||
# 4. Tag the commit <tool>-v<X.Y.Z>.
|
||||
# 1. Copy dist HTML → <bundle>/<tool>_v<X.Y.Z>.html (immutable).
|
||||
# 2. Refresh canonical symlink: <bundle>/<tool>.html → the new versioned file.
|
||||
# 3. Tag the commit <tool>-v<X.Y.Z> (centralized in the top-level ./build).
|
||||
#
|
||||
# Alpha/beta cuts:
|
||||
# 1. Overwrite <bundle>/<tool>_<channel>.html with dist HTML
|
||||
# (replaces a symlink with real bytes if one was there).
|
||||
# 2. For beta: cascade <tool>_alpha.html → <tool>_beta.html (symlink),
|
||||
# since alpha defaults to beta when no active alpha.
|
||||
# 3. No tag — channel URLs are stable URLs by design; counters defeat
|
||||
# that. The on-page label encodes <date> · <sha> for traceability.
|
||||
# Beta cuts:
|
||||
# No public artifact. The chart's Dockerfile fetches the source at the
|
||||
# SHA pinned in chart appVersion and compiles its own binary; the
|
||||
# embedded/* regeneration + chore commit in the top-level ./build is
|
||||
# the actual snapshot.
|
||||
#
|
||||
# Plain dev builds (no --release): never call promote_release.
|
||||
#
|
||||
# No Codeberg upload — HTML tools live in git. zddc-server's release.sh
|
||||
# handles binary uploads to Codeberg directly (different distribution model).
|
||||
promote_release() {
|
||||
_tool="$1"
|
||||
# The top-level `./build` exports $ZDDC_DEPLOY_RELEASES_DIR pointing
|
||||
|
|
@ -363,18 +351,16 @@ promote_release() {
|
|||
# every tool, even when a tool's source hasn't changed since
|
||||
# its last tag. The bytes are identical (build is deterministic
|
||||
# at the same source), so the overwrite is a no-op on disk;
|
||||
# but the symlink chain (_v<X.Y>, _v<X>, _stable, _beta, _alpha)
|
||||
# gets advanced to the new version, which is the actual goal.
|
||||
#
|
||||
# The previous "skip if no source changes since $_latest" check
|
||||
# was a relic of per-tool independent versioning. It broke
|
||||
# CI re-cuts at a tag commit (HEAD == latest tag → diff empty
|
||||
# → skip → dist/release-output/ stays seeded at the previous
|
||||
# version → deploy publishes the previous version).
|
||||
# but the canonical symlink <tool>.html advances to the new
|
||||
# version, which is the actual goal.
|
||||
_promote_stable "$_tool" "$build_version" "$_releases_dir"
|
||||
;;
|
||||
alpha | beta)
|
||||
_promote_channel "$_tool" "$channel" "$_releases_dir"
|
||||
beta)
|
||||
# Internal SHA snapshot for the BMC dev chart. No public
|
||||
# artifact: the chart fetches the source at the SHA via git,
|
||||
# the embedded/* regeneration + chore commit (in the top-
|
||||
# level ./build) IS the artifact.
|
||||
echo " ${_tool}: beta is internal (no public artifact)"
|
||||
;;
|
||||
*)
|
||||
echo "promote_release: unknown channel '$channel'" >&2
|
||||
|
|
@ -383,35 +369,31 @@ promote_release() {
|
|||
esac
|
||||
}
|
||||
|
||||
# Stable cut: per-version file + 5 symlinks. Tagging is centralized in
|
||||
# the top-level ./build (it commits embedded artifacts FIRST, then tags
|
||||
# at the new commit — see "Release commit + tag" block at the bottom of
|
||||
# the script). _promote_stable historically created tags itself, but
|
||||
# that placed them on the source-side commit before embedded files were
|
||||
# folded in, leaving prod binaries with alpha-dirty bytes baked in.
|
||||
# Stable cut: per-version immutable file + canonical symlink. Tagging is
|
||||
# centralized in the top-level ./build (it commits embedded artifacts
|
||||
# FIRST, then tags at the new commit — see "Release commit + tag" block
|
||||
# at the bottom of the script). _promote_stable historically created
|
||||
# tags itself, but that placed them on the source-side commit before
|
||||
# embedded files were folded in, leaving prod binaries with stale bytes
|
||||
# baked in.
|
||||
_promote_stable() {
|
||||
_t="$1"
|
||||
_ver="$2"
|
||||
_rdir="$3"
|
||||
|
||||
_major="${_ver%%.*}"
|
||||
_rest="${_ver#*.}"
|
||||
_minor="${_rest%%.*}"
|
||||
_versioned="${_t}_v${_ver}.html"
|
||||
_canonical="${_t}.html"
|
||||
|
||||
cp "$output_html" "$_rdir/$_versioned"
|
||||
echo "Wrote $_rdir/$_versioned"
|
||||
|
||||
# Refresh the 5 symlinks. Cascade: stable cut → beta + alpha both
|
||||
# reset to stable (no active dev on either downstream channel).
|
||||
for _sym in "${_t}_v${_major}.${_minor}.html" \
|
||||
"${_t}_v${_major}.html" \
|
||||
"${_t}_stable.html" \
|
||||
"${_t}_beta.html" \
|
||||
"${_t}_alpha.html"; do
|
||||
ln -sfn "$_versioned" "$_rdir/$_sym"
|
||||
echo " $_sym → $_versioned"
|
||||
done
|
||||
ln -sfn "$_versioned" "$_rdir/$_canonical"
|
||||
echo " $_canonical → $_versioned"
|
||||
|
||||
# Companion .sig symlink so `curl <canonical>.sig` resolves. The
|
||||
# actual .sig file is written by sign_release_artifacts; this
|
||||
# symlink points there.
|
||||
ln -sfn "${_versioned}.sig" "$_rdir/${_canonical}.sig"
|
||||
|
||||
# Pre-flight check only: if the tag already exists pointing at a
|
||||
# commit that is NOT an ancestor of HEAD, the operator needs to
|
||||
|
|
@ -431,28 +413,6 @@ _promote_stable() {
|
|||
echo "Released ${_t} v${_ver} (stable; tagging deferred to top-level build)"
|
||||
}
|
||||
|
||||
# Alpha/beta cut: overwrite mutable channel mirror; cascade alpha → beta
|
||||
# on a beta cut (alpha defaults to beta when no active alpha).
|
||||
_promote_channel() {
|
||||
_t="$1"
|
||||
_ch="$2"
|
||||
_rdir="$3"
|
||||
|
||||
_file="${_t}_${_ch}.html"
|
||||
# Replace symlink (if present) with real bytes by removing first;
|
||||
# cp -f follows symlinks and would overwrite the symlink target.
|
||||
rm -f "$_rdir/$_file"
|
||||
cp "$output_html" "$_rdir/$_file"
|
||||
echo "Wrote $_rdir/$_file"
|
||||
|
||||
if [ "$_ch" = "beta" ]; then
|
||||
ln -sfn "$_file" "$_rdir/${_t}_alpha.html"
|
||||
echo " ${_t}_alpha.html → $_file"
|
||||
fi
|
||||
|
||||
echo "Released ${_t} ${_ch}"
|
||||
}
|
||||
|
||||
# Platforms zddc-server is cross-compiled for. The first three are
|
||||
# extension-less (Linux/macOS); Windows gets .exe. The build always emits
|
||||
# all four; the matrix cell's stub page links each by its <platform> tag.
|
||||
|
|
@ -470,32 +430,36 @@ _zddc_server_platform_label() {
|
|||
esac
|
||||
}
|
||||
|
||||
# Resolve a zddc-server binary's filename for one (version, platform).
|
||||
# Returns the bare name (no path); ".exe" suffix on windows.
|
||||
# Resolve a zddc-server binary's filename for one (slug, platform).
|
||||
# Returns the bare name (no path); ".exe" suffix on windows. Empty slug
|
||||
# means the canonical "current stable" symlink (zddc-server_<plat>);
|
||||
# non-empty slug is a per-version asset (zddc-server_v<X.Y.Z>_<plat>).
|
||||
_zddc_server_binary_name() {
|
||||
_ver_or_chan="$1"
|
||||
_slug="$1"
|
||||
_plat="$2"
|
||||
_suffix=""
|
||||
case "$_plat" in *windows*) _suffix=".exe" ;; esac
|
||||
if echo "$_ver_or_chan" | grep -qE '^v[0-9]'; then
|
||||
# Per-version asset, e.g. zddc-server_v0.0.8_linux-amd64
|
||||
printf 'zddc-server_%s_%s%s' "$_ver_or_chan" "$_plat" "$_suffix"
|
||||
if [ -z "$_slug" ]; then
|
||||
printf 'zddc-server_%s%s' "$_plat" "$_suffix"
|
||||
else
|
||||
# Channel mirror, e.g. zddc-server_stable_linux-amd64
|
||||
printf 'zddc-server_%s_%s%s' "$_ver_or_chan" "$_plat" "$_suffix"
|
||||
printf 'zddc-server_%s_%s%s' "$_slug" "$_plat" "$_suffix"
|
||||
fi
|
||||
}
|
||||
|
||||
# Write the small HTML index page that becomes the matrix cell's link for
|
||||
# a zddc-server release. Lists each platform binary with a download link.
|
||||
# Write the small HTML index page that becomes the entry point for a
|
||||
# zddc-server release. Lists each platform binary with a download link.
|
||||
# $1 — release directory (absolute)
|
||||
# $2 — slug (e.g. v0.0.8, v0.0, stable, beta, alpha)
|
||||
# $3 — display label (e.g. "v0.0.8", "stable channel")
|
||||
# $2 — slug ("" for canonical "current stable", or "v0.0.8" per-version)
|
||||
# $3 — display label (e.g. "current stable", "v0.0.8")
|
||||
write_zddc_server_stub() {
|
||||
_rdir="$1"
|
||||
_slug="$2"
|
||||
_label="$3"
|
||||
if [ -z "$_slug" ]; then
|
||||
_out="$_rdir/zddc-server.html"
|
||||
else
|
||||
_out="$_rdir/zddc-server_${_slug}.html"
|
||||
fi
|
||||
|
||||
{
|
||||
cat <<HEAD
|
||||
|
|
@ -541,68 +505,40 @@ TAIL
|
|||
}
|
||||
|
||||
# Refresh every zddc-server stub page based on what's currently in the
|
||||
# release-output bundle. Driven by the existing per-version binary files +
|
||||
# symlinks that the release flow already maintains; just emits the HTML
|
||||
# wrappers for them. Safe to run on every cut (idempotent).
|
||||
# release-output bundle: one per-version stub per zddc-server_v*_*
|
||||
# binary set, plus a canonical zddc-server.html if the latest-stable
|
||||
# symlinks are in place. Indexed off linux-amd64 since all four
|
||||
# platforms ship in lockstep.
|
||||
#
|
||||
# $1 — releases dir (absolute)
|
||||
write_zddc_server_stubs_all() {
|
||||
_rdir="$1"
|
||||
|
||||
# Every per-version stable binary that exists. We index off
|
||||
# linux-amd64 specifically since all four platforms ship in lockstep
|
||||
# — if the linux build is missing the version is incomplete anyway.
|
||||
# Per-version stubs (immutable).
|
||||
for _bin in "$_rdir"/zddc-server_v*_linux-amd64; do
|
||||
[ -e "$_bin" ] || continue
|
||||
_name=$(basename "$_bin")
|
||||
# zddc-server_vX.Y.Z_linux-amd64 → vX.Y.Z
|
||||
_slug=$(echo "$_name" | sed -E 's/^zddc-server_(v[^_]+)_linux-amd64$/\1/')
|
||||
# Skip partial-version pins (vX.Y, vX) — these are written
|
||||
# separately below from symlink resolution.
|
||||
case "$_slug" in
|
||||
v*.*.*) write_zddc_server_stub "$_rdir" "$_slug" "$_slug" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Partial-version + channel stubs follow the symlink chain. If the
|
||||
# symlink resolves to a real binary, write the stub; otherwise skip.
|
||||
for _slug in stable beta alpha; do
|
||||
_probe="$_rdir/zddc-server_${_slug}_linux-amd64"
|
||||
if [ -e "$_probe" ]; then
|
||||
write_zddc_server_stub "$_rdir" "$_slug" "${_slug} channel"
|
||||
fi
|
||||
done
|
||||
|
||||
# vX.Y and vX partial pins — derive the slug list from the per-version
|
||||
# binaries so we only emit pages we actually have artifacts for.
|
||||
_all_versions=$(find "$_rdir" -maxdepth 1 -name 'zddc-server_v*_linux-amd64' \
|
||||
| sed -E 's|^.*/zddc-server_(v[0-9]+\.[0-9]+\.[0-9]+)_linux-amd64$|\1|' \
|
||||
| sort -Vu)
|
||||
if [ -n "$_all_versions" ]; then
|
||||
# vX.Y pins — pick the highest patch within each X.Y, then make
|
||||
# sure the symlink and stub exist.
|
||||
echo "$_all_versions" | sed -E 's|^v([0-9]+\.[0-9]+)\.[0-9]+$|\1|' | sort -Vu | while read -r _xy; do
|
||||
_probe="$_rdir/zddc-server_v${_xy}_linux-amd64"
|
||||
if [ -e "$_probe" ]; then
|
||||
write_zddc_server_stub "$_rdir" "v${_xy}" "v${_xy}"
|
||||
fi
|
||||
done
|
||||
# vX pins.
|
||||
echo "$_all_versions" | sed -E 's|^v([0-9]+)\..*$|\1|' | sort -Vu | while read -r _x; do
|
||||
_probe="$_rdir/zddc-server_v${_x}_linux-amd64"
|
||||
if [ -e "$_probe" ]; then
|
||||
write_zddc_server_stub "$_rdir" "v${_x}" "v${_x}"
|
||||
fi
|
||||
done
|
||||
# Canonical stub (follows the latest-stable symlink). Probes the
|
||||
# linux-amd64 canonical name; if it exists, the platform symlinks
|
||||
# are in place and we can write the entry page.
|
||||
if [ -e "$_rdir/zddc-server_linux-amd64" ]; then
|
||||
write_zddc_server_stub "$_rdir" "" "current stable"
|
||||
fi
|
||||
}
|
||||
|
||||
# Promote a freshly-cross-compiled set of zddc-server binaries to the
|
||||
# release-output bundle. Called by the top-level ./build on a release cut.
|
||||
# release-output bundle. Called by the top-level ./build on a stable
|
||||
# release cut. Beta cuts produce no public artifact (the chart's
|
||||
# Dockerfile compiles from source at the SHA pinned in appVersion).
|
||||
#
|
||||
# $1 — channel ("stable" | "alpha" | "beta")
|
||||
# $2 — version (X.Y.Z; required for stable; ignored for alpha/beta but
|
||||
# passed through so labels can include the next-stable target)
|
||||
# $1 — channel ("stable" | "beta")
|
||||
# $2 — version (X.Y.Z; required for stable; ignored for beta)
|
||||
# $3 — releases dir (absolute)
|
||||
# $4 — dist dir holding cross-compiled binaries (absolute)
|
||||
promote_zddc_server() {
|
||||
|
|
@ -628,27 +564,21 @@ promote_zddc_server() {
|
|||
echo "promote_zddc_server: stable cut requires version" >&2
|
||||
return 1
|
||||
fi
|
||||
_major="${_ver%%.*}"
|
||||
_rest="${_ver#*.}"
|
||||
_minor="${_rest%%.*}"
|
||||
|
||||
# Per-version: copy each binary to its immutable name + refresh
|
||||
# the partial-version + channel symlinks. Mirrors the HTML-tool
|
||||
# cascade: stable cut → beta + alpha both reset to stable.
|
||||
# Per-version immutable + canonical per-platform symlink.
|
||||
for _plat in $ZDDC_SERVER_PLATFORMS; do
|
||||
_suffix=""
|
||||
case "$_plat" in *windows*) _suffix=".exe" ;; esac
|
||||
_src="$_dist/zddc-server-${_plat}${_suffix}"
|
||||
_versioned="zddc-server_v${_ver}_${_plat}${_suffix}"
|
||||
_canonical="zddc-server_${_plat}${_suffix}"
|
||||
cp "$_src" "$_rdir/$_versioned"
|
||||
echo "Wrote $_rdir/$_versioned"
|
||||
for _sym in "zddc-server_v${_major}.${_minor}_${_plat}${_suffix}" \
|
||||
"zddc-server_v${_major}_${_plat}${_suffix}" \
|
||||
"zddc-server_stable_${_plat}${_suffix}" \
|
||||
"zddc-server_beta_${_plat}${_suffix}" \
|
||||
"zddc-server_alpha_${_plat}${_suffix}"; do
|
||||
ln -sfn "$_versioned" "$_rdir/$_sym"
|
||||
done
|
||||
ln -sfn "$_versioned" "$_rdir/$_canonical"
|
||||
echo " $_canonical → $_versioned"
|
||||
# Companion .sig symlink — see _promote_stable for the
|
||||
# same pattern.
|
||||
ln -sfn "${_versioned}.sig" "$_rdir/${_canonical}.sig"
|
||||
done
|
||||
|
||||
# Pre-flight tag check only — actual tagging happens in the
|
||||
|
|
@ -667,22 +597,11 @@ promote_zddc_server() {
|
|||
fi
|
||||
echo "Released zddc-server v${_ver} (stable; tagging deferred to top-level build)"
|
||||
;;
|
||||
alpha | beta)
|
||||
# Mutable channel mirror per platform; cascade alpha → beta on
|
||||
# a beta cut.
|
||||
for _plat in $ZDDC_SERVER_PLATFORMS; do
|
||||
_suffix=""
|
||||
case "$_plat" in *windows*) _suffix=".exe" ;; esac
|
||||
_src="$_dist/zddc-server-${_plat}${_suffix}"
|
||||
_file="zddc-server_${_ch}_${_plat}${_suffix}"
|
||||
rm -f "$_rdir/$_file"
|
||||
cp "$_src" "$_rdir/$_file"
|
||||
echo "Wrote $_rdir/$_file"
|
||||
if [ "$_ch" = "beta" ]; then
|
||||
ln -sfn "$_file" "$_rdir/zddc-server_alpha_${_plat}${_suffix}"
|
||||
fi
|
||||
done
|
||||
echo "Released zddc-server ${_ch}"
|
||||
beta)
|
||||
# Internal SHA snapshot — the chart's Dockerfile fetches the
|
||||
# source at that SHA and compiles its own binary. No public
|
||||
# binary is published.
|
||||
echo " zddc-server: beta is internal (no public artifact)"
|
||||
;;
|
||||
*)
|
||||
echo "promote_zddc_server: unknown channel '$_ch'" >&2
|
||||
|
|
@ -690,70 +609,6 @@ promote_zddc_server() {
|
|||
;;
|
||||
esac
|
||||
|
||||
# Refresh every stub page (covers the new release plus any pre-existing).
|
||||
# Refresh stub pages (per-version + canonical).
|
||||
write_zddc_server_stubs_all "$_rdir"
|
||||
}
|
||||
|
||||
# Verify every channel link for every release tool exists and resolves.
|
||||
# Runs at the end of every build. Fails the build if anything is dangling.
|
||||
# Channel verification covers both HTML tools (one .html per channel) and
|
||||
# zddc-server (one stub HTML + four binaries per channel).
|
||||
#
|
||||
# Bootstrap-friendly: if zddc-server has no per-version artifacts at all
|
||||
# (i.e. no release has been cut yet under the new lockstep model), the
|
||||
# zddc-server entries are skipped with a heads-up rather than failing. The
|
||||
# first stable cut materializes them.
|
||||
verify_channel_links() {
|
||||
_rdir="$1"
|
||||
_missing=0
|
||||
_verified=0
|
||||
|
||||
for _t in archive transmittal classifier landing form tables browse; do
|
||||
for _ch in stable beta alpha; do
|
||||
_f="$_rdir/${_t}_${_ch}.html"
|
||||
if [ -e "$_f" ]; then
|
||||
_verified=$((_verified + 1))
|
||||
else
|
||||
echo " MISSING: ${_t}_${_ch}.html" >&2
|
||||
_missing=$((_missing + 1))
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# zddc-server's stable cut anchors the channel chain (cascade rule:
|
||||
# stable cut → alpha + beta both reset to stable). Until stable
|
||||
# exists, the verifier runs in bootstrap mode and skips — alpha/beta
|
||||
# cuts in isolation are valid bootstrap state but have no cascade
|
||||
# fallback target yet.
|
||||
_zs_stable_exists=$(find "$_rdir" -maxdepth 1 -name 'zddc-server_stable_linux-amd64' -print -quit 2>/dev/null)
|
||||
if [ -z "$_zs_stable_exists" ]; then
|
||||
echo " (zddc-server stable not yet cut — run 'sh build.sh --release' to anchor the channel chain)"
|
||||
else
|
||||
for _ch in stable beta alpha; do
|
||||
_f="$_rdir/zddc-server_${_ch}.html"
|
||||
if [ -e "$_f" ]; then
|
||||
_verified=$((_verified + 1))
|
||||
else
|
||||
echo " MISSING: zddc-server_${_ch}.html" >&2
|
||||
_missing=$((_missing + 1))
|
||||
fi
|
||||
for _plat in $ZDDC_SERVER_PLATFORMS; do
|
||||
_suffix=""
|
||||
case "$_plat" in *windows*) _suffix=".exe" ;; esac
|
||||
_f="$_rdir/zddc-server_${_ch}_${_plat}${_suffix}"
|
||||
if [ -e "$_f" ]; then
|
||||
_verified=$((_verified + 1))
|
||||
else
|
||||
echo " MISSING: zddc-server_${_ch}_${_plat}${_suffix}" >&2
|
||||
_missing=$((_missing + 1))
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$_missing" -gt 0 ]; then
|
||||
echo "channel-link verification: $_missing missing artifact(s)" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "channel-link verification: $_verified link(s) ok"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@
|
|||
//
|
||||
// Spec forms (each is a string value in `.zddc apps:`):
|
||||
//
|
||||
// :stable / :beta / :alpha / :v0.0.4 / :v0.0 / :v0 — channel-only
|
||||
// stable / beta / alpha / v0.0.4 / v0.0 / v0 — channel-only (no leading colon)
|
||||
// :stable / :v0.0.4 — channel-only
|
||||
// stable / v0.0.4 / 0.0.4 — channel-only (no leading colon)
|
||||
// https://host/path — URL-prefix only (combines with cascade channel)
|
||||
// https://host/path:stable — URL-prefix + channel (composes)
|
||||
// https://host/path/file.html — terminal full URL (used as-is)
|
||||
|
|
@ -84,7 +84,7 @@ type SpecComponents struct {
|
|||
// Composable forms — either or both may be set, both may be empty
|
||||
// (caller should treat empty-everything as a no-op).
|
||||
URLPrefix string // "https://host/path" (no trailing /)
|
||||
Channel string // "stable" / "beta" / "alpha" / "v0.0.4" / "v0.0" / "v0"
|
||||
Channel string // "stable" (latest), "v0.0.4" (exact version pin)
|
||||
}
|
||||
|
||||
// IsTerminal reports whether this spec terminates composition.
|
||||
|
|
@ -144,7 +144,7 @@ func ParseSpec(spec, zddcDir, root string) (SpecComponents, error) {
|
|||
// https://host:8080/path:stable → URLPrefix=https://host:8080/path, Channel=stable
|
||||
// https://host:8080/path → URLPrefix=https://host:8080/path
|
||||
// https://host/path/file.html → FullURL=https://host/path/file.html (terminal)
|
||||
// https://host/path/file.html:beta → error (terminal URL with extra suffix)
|
||||
// https://host/path/file.html:stable → error (terminal URL with extra suffix)
|
||||
func parseURLSpec(spec string) (SpecComponents, error) {
|
||||
// Locate the channel separator: last `:` that comes after the last `/`.
|
||||
lastSlash := strings.LastIndex(spec, "/")
|
||||
|
|
@ -191,10 +191,12 @@ func parseURLSpec(spec string) (SpecComponents, error) {
|
|||
return out, nil
|
||||
}
|
||||
|
||||
// isValidChannelOrVersion reports whether s is `stable`/`beta`/`alpha` or a
|
||||
// version like `v0.0.4`/`0.0.4`/`v0.0`/`v0`.
|
||||
// isValidChannelOrVersion reports whether s is `stable` (the canonical
|
||||
// "current stable" alias) or an exact-version pin like `v0.0.4` / `0.0.4`.
|
||||
// Partial pins (`v0.0`, `v0`) and the legacy `beta`/`alpha` channels
|
||||
// are no longer accepted — the upstream publishes only stable + exact.
|
||||
func isValidChannelOrVersion(s string) bool {
|
||||
if s == "stable" || s == "beta" || s == "alpha" {
|
||||
if s == "stable" {
|
||||
return true
|
||||
}
|
||||
rest := strings.TrimPrefix(s, "v")
|
||||
|
|
@ -202,7 +204,7 @@ func isValidChannelOrVersion(s string) bool {
|
|||
return false
|
||||
}
|
||||
parts := strings.Split(rest, ".")
|
||||
if len(parts) > 3 {
|
||||
if len(parts) != 3 {
|
||||
return false
|
||||
}
|
||||
for _, p := range parts {
|
||||
|
|
@ -221,7 +223,7 @@ func isValidChannelOrVersion(s string) bool {
|
|||
// normalizeChannel ensures versions carry the `v` prefix (so the resulting
|
||||
// filename is `<app>_v<X.Y.Z>.html` per upstream convention).
|
||||
func normalizeChannel(s string) string {
|
||||
if s == "stable" || s == "beta" || s == "alpha" {
|
||||
if s == "stable" {
|
||||
return s
|
||||
}
|
||||
if !strings.HasPrefix(s, "v") {
|
||||
|
|
@ -359,9 +361,18 @@ func (s *appsState) finalize() (Source, bool, error) {
|
|||
if channel == "" {
|
||||
channel = DefaultChannel
|
||||
}
|
||||
// channel == "stable" → canonical URL <prefix>/<app>.html (a
|
||||
// symlink that always follows the latest stable cut).
|
||||
// channel == "v<X.Y.Z>" → immutable per-version URL.
|
||||
var name string
|
||||
if channel == "stable" {
|
||||
name = s.app + ".html"
|
||||
} else {
|
||||
name = s.app + "_" + channel + ".html"
|
||||
}
|
||||
return Source{
|
||||
App: s.app,
|
||||
URL: urlPrefix + "/" + s.app + "_" + channel + ".html",
|
||||
URL: urlPrefix + "/" + name,
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,15 +11,13 @@ import (
|
|||
// ── ParseSpec ────────────────────────────────────────────────────────────
|
||||
|
||||
func TestParseSpec_Channels(t *testing.T) {
|
||||
// "stable" is the only channel alias (latest stable). beta and alpha
|
||||
// channels no longer exist as public concepts.
|
||||
cases := []struct {
|
||||
spec, wantChan string
|
||||
}{
|
||||
{"stable", "stable"},
|
||||
{"beta", "beta"},
|
||||
{"alpha", "alpha"},
|
||||
{":stable", "stable"},
|
||||
{":beta", "beta"},
|
||||
{":alpha", "alpha"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.spec, func(t *testing.T) {
|
||||
|
|
@ -38,18 +36,17 @@ func TestParseSpec_Channels(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseSpec_Versions(t *testing.T) {
|
||||
// Exact-version pins only. Partial pins (v0.0, v0) no longer exist
|
||||
// — the upstream publishes <tool>.html (current stable) and
|
||||
// <tool>_v<X.Y.Z>.html (exact-version immutable). Bare "0.0.4"
|
||||
// (no v prefix) is normalized to "v0.0.4".
|
||||
cases := []struct {
|
||||
spec, wantChan string
|
||||
}{
|
||||
{"v0.0.4", "v0.0.4"},
|
||||
{"0.0.4", "v0.0.4"},
|
||||
{"v0.0", "v0.0"},
|
||||
{"0.0", "v0.0"},
|
||||
{"v0", "v0"},
|
||||
{"0", "v0"},
|
||||
{":v0.0.4", "v0.0.4"},
|
||||
{":0.0.4", "v0.0.4"},
|
||||
{":v0", "v0"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.spec, func(t *testing.T) {
|
||||
|
|
@ -64,6 +61,19 @@ func TestParseSpec_Versions(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestParseSpec_RejectsLegacyChannelsAndPartialPins(t *testing.T) {
|
||||
// alpha/beta channels and partial-version pins are no longer valid.
|
||||
rejected := []string{"alpha", "beta", ":alpha", ":beta", "v0.0", "v0", "0.0", "0", ":v0.0"}
|
||||
for _, spec := range rejected {
|
||||
t.Run(spec, func(t *testing.T) {
|
||||
_, err := ParseSpec(spec, "/root", "/root")
|
||||
if err == nil {
|
||||
t.Errorf("expected error for %q, got none", spec)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSpec_URLPrefix(t *testing.T) {
|
||||
cases := []struct {
|
||||
spec, wantPrefix, wantChan string
|
||||
|
|
@ -71,14 +81,13 @@ func TestParseSpec_URLPrefix(t *testing.T) {
|
|||
{"https://my-mirror.example/releases", "https://my-mirror.example/releases", ""},
|
||||
{"https://my-mirror.example/releases/", "https://my-mirror.example/releases", ""}, // trailing slash stripped
|
||||
{"https://my-mirror.example/releases:stable", "https://my-mirror.example/releases", "stable"},
|
||||
{"https://my-mirror.example/releases:beta", "https://my-mirror.example/releases", "beta"},
|
||||
{"https://my-mirror.example/releases:v0.0.4", "https://my-mirror.example/releases", "v0.0.4"},
|
||||
// Port colon must NOT be confused with channel separator.
|
||||
{"https://my-mirror.example:8080/releases", "https://my-mirror.example:8080/releases", ""},
|
||||
{"https://my-mirror.example:8080/releases:stable", "https://my-mirror.example:8080/releases", "stable"},
|
||||
// Colon embedded in path before final slash — treated as part of path.
|
||||
{"https://host/some:thing/releases", "https://host/some:thing/releases", ""},
|
||||
{"https://host/some:thing/releases:beta", "https://host/some:thing/releases", "beta"},
|
||||
{"https://host/some:thing/releases:v0.0.4", "https://host/some:thing/releases", "v0.0.4"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.spec, func(t *testing.T) {
|
||||
|
|
@ -192,13 +201,15 @@ func TestResolve_NoEntries(t *testing.T) {
|
|||
func TestResolve_PerAppChannelOnly(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
chain := zddc.PolicyChain{Levels: []zddc.ZddcFile{{
|
||||
Apps: map[string]string{"archive": "beta"},
|
||||
Apps: map[string]string{"archive": "stable"},
|
||||
}}}
|
||||
src, has, err := Resolve(chain, "archive", root, root)
|
||||
if err != nil || !has {
|
||||
t.Fatalf("has=%v err=%v", has, err)
|
||||
}
|
||||
want := DefaultUpstreamReleases + "/archive_beta.html"
|
||||
// stable channel → canonical URL (no _stable_ suffix); the upstream
|
||||
// publishes a symlink at this URL pointing at the latest version.
|
||||
want := DefaultUpstreamReleases + "/archive.html"
|
||||
if src.URL != want {
|
||||
t.Errorf("got URL=%q, want %q", src.URL, want)
|
||||
}
|
||||
|
|
@ -223,40 +234,40 @@ func TestResolve_DefaultProvidesURLAndChannel(t *testing.T) {
|
|||
root := t.TempDir()
|
||||
chain := zddc.PolicyChain{Levels: []zddc.ZddcFile{{
|
||||
Apps: map[string]string{
|
||||
"default": "https://mirror.example/releases:beta",
|
||||
"default": "https://mirror.example/releases:v0.0.4",
|
||||
},
|
||||
}}}
|
||||
src, has, err := Resolve(chain, "archive", root, root)
|
||||
if err != nil || !has {
|
||||
t.Fatalf("has=%v err=%v", has, err)
|
||||
}
|
||||
if src.URL != "https://mirror.example/releases/archive_beta.html" {
|
||||
if src.URL != "https://mirror.example/releases/archive_v0.0.4.html" {
|
||||
t.Errorf("got URL=%q", src.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve_DefaultPlusPerAppChannelOverride(t *testing.T) {
|
||||
// User's example: default=https://zddc.varasys.io/releases:stable,
|
||||
// classifier=:beta → mirror URL with classifier_beta.html.
|
||||
// default=https://zddc.varasys.io/releases:stable, classifier=:v0.0.4
|
||||
// → classifier pinned to v0.0.4 on the same mirror.
|
||||
root := t.TempDir()
|
||||
chain := zddc.PolicyChain{Levels: []zddc.ZddcFile{{
|
||||
Apps: map[string]string{
|
||||
"default": "https://zddc.varasys.io/releases:stable",
|
||||
"classifier": ":beta",
|
||||
"classifier": ":v0.0.4",
|
||||
},
|
||||
}}}
|
||||
src, _, err := Resolve(chain, "classifier", root, root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if src.URL != "https://zddc.varasys.io/releases/classifier_beta.html" {
|
||||
if src.URL != "https://zddc.varasys.io/releases/classifier_v0.0.4.html" {
|
||||
t.Errorf("got URL=%q", src.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve_DefaultPlusPerAppURLPrefixOverride(t *testing.T) {
|
||||
// User's example: default=...:stable, archive=https://my.local.stuff/releases
|
||||
// → custom URL + default channel (stable).
|
||||
// default=...:stable, archive=https://my.local.stuff/releases
|
||||
// → custom URL + default channel (stable, canonical filename).
|
||||
root := t.TempDir()
|
||||
chain := zddc.PolicyChain{Levels: []zddc.ZddcFile{{
|
||||
Apps: map[string]string{
|
||||
|
|
@ -268,7 +279,7 @@ func TestResolve_DefaultPlusPerAppURLPrefixOverride(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if src.URL != "https://my.local.stuff/releases/archive_stable.html" {
|
||||
if src.URL != "https://my.local.stuff/releases/archive.html" {
|
||||
t.Errorf("got URL=%q", src.URL)
|
||||
}
|
||||
}
|
||||
|
|
@ -278,13 +289,13 @@ func TestResolve_DeeperLevelOverridesParentChannel(t *testing.T) {
|
|||
requestDir := filepath.Join(root, "Project-A")
|
||||
chain := zddc.PolicyChain{Levels: []zddc.ZddcFile{
|
||||
{Apps: map[string]string{"default": ":stable"}},
|
||||
{Apps: map[string]string{"default": ":beta"}},
|
||||
{Apps: map[string]string{"default": ":v0.0.4"}},
|
||||
}}
|
||||
src, _, err := Resolve(chain, "archive", root, requestDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := DefaultUpstreamReleases + "/archive_beta.html"
|
||||
want := DefaultUpstreamReleases + "/archive_v0.0.4.html"
|
||||
if src.URL != want {
|
||||
t.Errorf("got URL=%q, want %q", src.URL, want)
|
||||
}
|
||||
|
|
@ -301,8 +312,9 @@ func TestResolve_DeeperLevelOverridesParentURL(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// b.example URL prefix wins; channel inherited (stable).
|
||||
want := "https://b.example/releases/archive_stable.html"
|
||||
// b.example URL prefix wins; channel inherited (stable → canonical
|
||||
// filename, no _stable_ suffix).
|
||||
want := "https://b.example/releases/archive.html"
|
||||
if src.URL != want {
|
||||
t.Errorf("got URL=%q, want %q", src.URL, want)
|
||||
}
|
||||
|
|
@ -329,13 +341,13 @@ func TestResolve_DeeperNonTerminalOverridesParentTerminal(t *testing.T) {
|
|||
requestDir := filepath.Join(root, "Project-A")
|
||||
chain := zddc.PolicyChain{Levels: []zddc.ZddcFile{
|
||||
{Apps: map[string]string{"archive": "https://a.example/archive.html"}}, // terminal
|
||||
{Apps: map[string]string{"archive": "alpha"}}, // non-terminal
|
||||
{Apps: map[string]string{"archive": "v0.0.4"}}, // non-terminal
|
||||
}}
|
||||
src, _, err := Resolve(chain, "archive", root, requestDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := DefaultUpstreamReleases + "/archive_alpha.html"
|
||||
want := DefaultUpstreamReleases + "/archive_v0.0.4.html"
|
||||
if src.URL != want {
|
||||
t.Errorf("got URL=%q, want %q", src.URL, want)
|
||||
}
|
||||
|
|
@ -382,7 +394,7 @@ func TestResolve_PerAppOverridesDefaultAtSameLevel(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if src2.URL != "https://a.example/releases/classifier_stable.html" {
|
||||
if src2.URL != "https://a.example/releases/classifier.html" {
|
||||
t.Errorf("got classifier URL=%q (want a.example default)", src2.URL)
|
||||
}
|
||||
}
|
||||
|
|
@ -417,9 +429,9 @@ func TestPreviewLine(t *testing.T) {
|
|||
}
|
||||
})
|
||||
t.Run("default channel → URL", func(t *testing.T) {
|
||||
chain := zddc.PolicyChain{Levels: []zddc.ZddcFile{{Apps: map[string]string{"default": ":beta"}}}}
|
||||
chain := zddc.PolicyChain{Levels: []zddc.ZddcFile{{Apps: map[string]string{"default": ":v0.0.4"}}}}
|
||||
got := PreviewLine(chain, "archive", root, root)
|
||||
if !strings.Contains(got, "archive_beta.html") {
|
||||
if !strings.Contains(got, "archive_v0.0.4.html") {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -221,15 +221,15 @@ func TestServer_FetchFailFallsBackToEmbedded(t *testing.T) {
|
|||
func TestServer_VParam_CacheHitServesFromCache(t *testing.T) {
|
||||
srv, _, root := newTestServer(t, []byte("ignored"))
|
||||
// Pre-populate the cache with a known URL.
|
||||
cachedURL := "https://zddc.varasys.io/releases/archive_beta.html"
|
||||
cachedBody := []byte("CACHED beta archive")
|
||||
cachedURL := "https://zddc.varasys.io/releases/archive_v0.0.4.html"
|
||||
cachedBody := []byte("CACHED v0.0.4 archive")
|
||||
if err := srv.Cache.Write(cachedURL, cachedBody); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
chain := zddc.PolicyChain{Levels: []zddc.ZddcFile{{}}}
|
||||
rec := httptest.NewRecorder()
|
||||
srv.Serve(rec, httptest.NewRequest(http.MethodGet, "/archive.html?v=beta", nil), "archive", chain, root)
|
||||
srv.Serve(rec, httptest.NewRequest(http.MethodGet, "/archive.html?v=v0.0.4", nil), "archive", chain, root)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
|
@ -245,7 +245,7 @@ func TestServer_VParam_CacheMissReturns404(t *testing.T) {
|
|||
srv, _, root := newTestServer(t, []byte("ignored"))
|
||||
chain := zddc.PolicyChain{Levels: []zddc.ZddcFile{{}}}
|
||||
rec := httptest.NewRecorder()
|
||||
srv.Serve(rec, httptest.NewRequest(http.MethodGet, "/archive.html?v=beta", nil), "archive", chain, root)
|
||||
srv.Serve(rec, httptest.NewRequest(http.MethodGet, "/archive.html?v=v0.0.4", nil), "archive", chain, root)
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Fatalf("status=%d (want 404)", rec.Code)
|
||||
}
|
||||
|
|
@ -277,19 +277,19 @@ func TestServer_VParam_BadSpecReturns400(t *testing.T) {
|
|||
func TestServer_VParam_CombinesWithCascadeURLPrefix(t *testing.T) {
|
||||
// Cascade has a default URL prefix; ?v=:beta should resolve against it.
|
||||
srv, _, root := newTestServer(t, []byte("ignored"))
|
||||
cachedURL := "https://my-mirror.example/releases/archive_beta.html"
|
||||
if err := srv.Cache.Write(cachedURL, []byte("MIRROR beta")); err != nil {
|
||||
cachedURL := "https://my-mirror.example/releases/archive_v0.0.4.html"
|
||||
if err := srv.Cache.Write(cachedURL, []byte("MIRROR v0.0.4")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
chain := zddc.PolicyChain{Levels: []zddc.ZddcFile{{
|
||||
Apps: map[string]string{"default": "https://my-mirror.example/releases:stable"},
|
||||
}}}
|
||||
rec := httptest.NewRecorder()
|
||||
srv.Serve(rec, httptest.NewRequest(http.MethodGet, "/archive.html?v=:beta", nil), "archive", chain, root)
|
||||
srv.Serve(rec, httptest.NewRequest(http.MethodGet, "/archive.html?v=:v0.0.4", nil), "archive", chain, root)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if rec.Body.String() != "MIRROR beta" {
|
||||
if rec.Body.String() != "MIRROR v0.0.4" {
|
||||
t.Errorf("body=%q", rec.Body.String())
|
||||
}
|
||||
if got := rec.Header().Get("X-ZDDC-Source"); got != "cache:"+cachedURL {
|
||||
|
|
@ -299,9 +299,9 @@ func TestServer_VParam_CombinesWithCascadeURLPrefix(t *testing.T) {
|
|||
|
||||
func TestServer_VParam_OverridesPathTerminalFromCascade(t *testing.T) {
|
||||
// Operator's cascade specifies a path source. User passes ?v=stable.
|
||||
// ?v= overrides → resolves to canonical/archive_stable.html, then cache check.
|
||||
// ?v= overrides → resolves to canonical/archive.html, then cache check.
|
||||
srv, _, root := newTestServer(t, []byte("ignored"))
|
||||
cachedURL := "https://zddc.varasys.io/releases/archive_stable.html"
|
||||
cachedURL := "https://zddc.varasys.io/releases/archive.html"
|
||||
if err := srv.Cache.Write(cachedURL, []byte("CACHED stable")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
// embeddedVersionsRaw is the manifest written by the top-level build.sh
|
||||
// at compile time. Format is one `<app>=<build label>` line per app —
|
||||
// e.g. `archive=v0.0.5-alpha · 2026-05-01 14:00:00 · abc1234`. An empty
|
||||
// e.g. `archive=v0.0.5-beta · 2026-05-01 14:00:00 · abc1234`. An empty
|
||||
// or missing value indicates the embedded slot was not populated (a fresh
|
||||
// clone where build.sh hasn't run yet).
|
||||
//
|
||||
|
|
|
|||
|
|
@ -1515,7 +1515,7 @@ body.is-elevated::after {
|
|||
</svg>
|
||||
<div class="header-title-group">
|
||||
<span class="app-header__title" id="table-title">ZDDC Table</span>
|
||||
<span class="build-timestamp">v0.0.17</span>
|
||||
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.18-dev · 2026-05-20 14:16:52 · 784ed21-dirty</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
|
|
|
|||
Loading…
Reference in a new issue