Something went wrong. Try again.
Web frontend and supporting services for lance.blue
Something went wrong. Try again.
5.8 kB · 153 lines
Shell
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154#!/usr/bin/env bash## The daily challenge's schedule: check it, and add a day to it.## scripts/daily.sh check is the schedule stocked and published?# scripts/daily.sh add <file.mms> [date]## Meant to run on a schedule. There is nothing to "roll over" at midnight —# which fight is today's is computed from the dates in the schedule, by the# site and by the API alike, so no job has to be up for the day to change.# What a job is for is the thing that *does* need a human: noticing that the# schedule is running out before it does, and saying so while there is still# time to write another one.## `check` fails when the schedule has fewer than DAYS_AHEAD days left, or when# the published copies have drifted from the scenarios. Both are conditions# somebody has to act on, which is what a non-zero exit is for.## `add` is the mechanical half of putting a new challenge in: it validates the# file, copies it in and appends it to the schedule. It deliberately does not# write the scenario — that is a design job, for a person or for whatever# generates one — and it cannot fill in the battle values, which come from# loading the fight in real MegaMek:## arena: java -cp ... arena.ScenarioReport <files> > report.json# here: cargo run -p headquarters-mms --features cli --bin mms -- index ...#set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"cd "$ROOT"
DAILY_DIR="services/api/src/matches/daily"SCHEDULE="$DAILY_DIR/schedule.json"INDEX="web/src/content/daily.json"
# How much runway the schedule should keep. Three days is enough that a check# failing on a Friday can be dealt with on Monday.DAYS_AHEAD="${DAYS_AHEAD:-3}"
die() { echo "daily: $*" >&2; exit 1; }
today() { date -u +%F; }
# The scheduled days, one per line, in file order.days() { python3 -c 'import json, sysfor slot in json.load(open(sys.argv[1])): print(slot["date"], slot["slug"])' "$SCHEDULE"}
check() { local now last remaining stale=0 now="$(today)"
[ -f "$SCHEDULE" ] || die "no schedule at $SCHEDULE"
# Every scheduled slug has a scenario, and every scenario is scheduled. local scheduled scheduled="$(days | awk '{print $2}' | sort)" local present present="$(find "$DAILY_DIR" -name '*.mms' -printf '%f\n' | sed 's/\.mms$//' | sort)" if [ "$scheduled" != "$present" ]; then echo "daily: the schedule and the scenario files disagree:" >&2 diff <(echo "$scheduled") <(echo "$present") >&2 || true stale=1 fi
# Every scenario parses. A challenge that does not is one nobody can play. # This is not the whole check — a file can parse and still name a board no # image has — which is arena's tests/scenario-load.sh. cargo run -q -p headquarters-mms --features cli --bin mms -- \ check "$DAILY_DIR"/*.mms >/dev/null || die "a scenario does not parse"
# The site's copy of the index is current. cargo run -q -p headquarters-mms --features cli --bin mms -- index \ --scenarios "$DAILY_DIR" \ --schedule "$SCHEDULE" \ --report "$DAILY_DIR/report.json" \ --out "$INDEX" --check >/dev/null || { echo "daily: $INDEX is stale; re-run mms index" >&2 stale=1 }
# And the downloadable copies are the files the API launches. npm --prefix web run --silent daily-public -- --check >/dev/null || { echo "daily: web/public/daily/ is out of step; run npm --prefix web run daily-public" >&2 stale=1 }
last="$(days | awk '{print $1}' | sort | tail -1)" remaining="$(( ( $(date -u -d "$last" +%s) - $(date -u -d "$now" +%s) ) / 86400 ))" echo "daily: today is $now; the schedule runs to $last ($remaining day(s) left)"
[ "$stale" -eq 0 ] || die "the published files are out of step with the scenarios"
if [ "$remaining" -lt "$DAYS_AHEAD" ]; then die "fewer than $DAYS_AHEAD days of challenges left; write another one" fi}
add() { local file="${1:-}" date="${2:-}" [ -n "$file" ] || die "usage: daily.sh add <file.mms> [YYYY-MM-DD]" [ -f "$file" ] || die "no such file: $file"
local slug slug="$(basename "$file" .mms)" [ -f "$DAILY_DIR/$slug.mms" ] && die "$slug is already a challenge"
# The day after the last scheduled one, unless told otherwise. A gap in the # schedule is allowed — for_day falls back to the most recent past challenge # — but it is never what somebody meant. if [ -z "$date" ]; then local last last="$(days | awk '{print $1}' | sort | tail -1)" date="$(date -u -d "$last + 1 day" +%F)" fi
cargo run -q -p headquarters-mms --features cli --bin mms -- check "$file" \ || die "$file does not parse"
cp "$file" "$DAILY_DIR/$slug.mms" python3 - "$SCHEDULE" "$slug" "$date" <<'PY'import json, syspath, slug, date = sys.argv[1], sys.argv[2], sys.argv[3]schedule = json.load(open(path))if any(s["date"] == date for s in schedule): sys.exit(f"daily: {date} already has a challenge")schedule.append({"slug": slug, "date": date})schedule.sort(key=lambda s: s["date"])# One entry per line, which is how this file is kept. json.dump(indent=2)# would spread every entry over four lines and turn adding one challenge# into a fifty-line diff over eleven that did not change.with open(path, "w") as fh: rows = [f' {{ "slug": "{s["slug"]}", "date": "{s["date"]}" }}' for s in schedule] fh.write("[\n" + ",\n".join(rows) + "\n]\n")PY echo "daily: $slug is scheduled for $date" echo echo "Still to do, because neither can be done from the .mms alone:" echo " 1. add it to CHALLENGES in $DAILY_DIR/../daily.rs" echo " 2. load it in arena's ScenarioReport, merge the result into" echo " $DAILY_DIR/report.json, then re-run mms index and daily-public"}
case "${1:-check}" in check) check ;; add) shift; add "$@" ;; *) die "usage: daily.sh <check|add>" ;;esac