feat: initial coa-pack scaffolding (manifest + build_pack + workflow + docs)
This commit is contained in:
Executable
+204
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env bash
|
||||
# build_pack.sh - assemble ExilesPack-<YYYY.MM.DD>.zip (or ExilesPack-<tag>.zip with --tag)
|
||||
#
|
||||
# Reads manifest.yaml, fetches the latest release of every `include: true` repo
|
||||
# from git.sub-net.at/Exiles, downloads the chosen zip asset(s), unpacks them
|
||||
# into staging/, and rolls the combined result up into dist/ExilesPack-*.zip.
|
||||
#
|
||||
# We unpack the per-addon release zips (which contain just the addon folder,
|
||||
# nothing else), so .git/.github/.gitea/README.md never enter the pack.
|
||||
#
|
||||
# Deps: bash, curl, jq, unzip, zip. No yq needed - the manifest is parsed
|
||||
# with a tiny awk pass tailored to its simple shape.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Paths + args
|
||||
# ---------------------------------------------------------------------------
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MANIFEST="$REPO_ROOT/manifest.yaml"
|
||||
TMP_DIR="$REPO_ROOT/tmp"
|
||||
STAGING_DIR="$REPO_ROOT/staging"
|
||||
DIST_DIR="$REPO_ROOT/dist"
|
||||
|
||||
API="https://git.sub-net.at/api/v1"
|
||||
ORG="Exiles"
|
||||
|
||||
TAG=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--tag) TAG="$2"; shift 2 ;;
|
||||
--tag=*) TAG="${1#--tag=}"; shift ;;
|
||||
-h|--help)
|
||||
cat <<USAGE
|
||||
Usage: $0 [--tag <tag>]
|
||||
|
||||
Without --tag, the pack is named ExilesPack-<UTC YYYY.MM.DD>.zip.
|
||||
With --tag, the pack is named ExilesPack-<tag>.zip.
|
||||
USAGE
|
||||
exit 0 ;;
|
||||
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$TAG" ]; then
|
||||
PACK_NAME="ExilesPack-$(date -u +%Y.%m.%d).zip"
|
||||
else
|
||||
PACK_NAME="ExilesPack-${TAG}.zip"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
log() { printf '[build_pack] %s\n' "$*"; }
|
||||
warn() { printf '[build_pack] WARN: %s\n' "$*" >&2; }
|
||||
die() { printf '[build_pack] ERROR: %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
# Parse manifest.yaml -> emit "<repo>" for every `include: true` entry.
|
||||
# The manifest is a small fixed-shape YAML (no anchors, no nested lists), so a
|
||||
# 20-line awk pass is more honest than pulling in a yq dependency.
|
||||
manifest_includes() {
|
||||
awk '
|
||||
/^[[:space:]]*-[[:space:]]*repo:[[:space:]]*/ {
|
||||
sub(/^[[:space:]]*-[[:space:]]*repo:[[:space:]]*/, "")
|
||||
gsub(/[[:space:]]+$/, "")
|
||||
repo = $0
|
||||
include = ""
|
||||
next
|
||||
}
|
||||
/^[[:space:]]+include:[[:space:]]*/ {
|
||||
sub(/^[[:space:]]+include:[[:space:]]*/, "")
|
||||
gsub(/[[:space:]]+$/, "")
|
||||
include = $0
|
||||
if (repo != "" && include == "true") { print repo }
|
||||
repo = ""; include = ""
|
||||
}
|
||||
' "$MANIFEST"
|
||||
}
|
||||
|
||||
# Latest release JSON for a repo, or empty string if none.
|
||||
latest_release_json() {
|
||||
local repo="$1" json
|
||||
json="$(curl -fsS "$API/repos/$ORG/$repo/releases?limit=1" 2>/dev/null || echo '[]')"
|
||||
# `releases?limit=1` returns an array. Strip the wrapper.
|
||||
if [ "$(printf '%s' "$json" | jq -r 'type')" = "array" ]; then
|
||||
printf '%s' "$json" | jq -c '.[0] // empty'
|
||||
else
|
||||
printf ''
|
||||
fi
|
||||
}
|
||||
|
||||
# Download one URL with one retry. Stream to file.
|
||||
fetch_with_retry() {
|
||||
local url="$1" out="$2"
|
||||
for attempt in 1 2; do
|
||||
if curl -fsSL --retry 0 -o "$out" "$url"; then
|
||||
return 0
|
||||
fi
|
||||
warn "download attempt $attempt failed for $url"
|
||||
sleep 2
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reset workspace
|
||||
# ---------------------------------------------------------------------------
|
||||
rm -rf "$TMP_DIR" "$STAGING_DIR"
|
||||
mkdir -p "$TMP_DIR" "$STAGING_DIR" "$DIST_DIR"
|
||||
|
||||
INCLUDED=0
|
||||
SKIPPED=0
|
||||
SKIPPED_REPOS=()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per-repo: pick assets, download, extract
|
||||
# ---------------------------------------------------------------------------
|
||||
while read -r repo; do
|
||||
[ -z "$repo" ] && continue
|
||||
log "processing $repo"
|
||||
|
||||
rel="$(latest_release_json "$repo")"
|
||||
if [ -z "$rel" ] || [ "$rel" = "null" ]; then
|
||||
warn "$repo has no releases yet - skipping"
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
SKIPPED_REPOS+=("$repo")
|
||||
continue
|
||||
fi
|
||||
|
||||
tag="$(printf '%s' "$rel" | jq -r '.tag_name')"
|
||||
log " latest release: $tag"
|
||||
|
||||
# Prefer <repo>-all.zip if present, otherwise take every *.zip asset.
|
||||
assets_all="$(printf '%s' "$rel" | jq -r --arg n "${repo}-all.zip" \
|
||||
'.assets[]? | select(.name == $n) | .browser_download_url')"
|
||||
if [ -n "$assets_all" ]; then
|
||||
download_urls="$assets_all"
|
||||
log " using ${repo}-all.zip"
|
||||
else
|
||||
download_urls="$(printf '%s' "$rel" | jq -r \
|
||||
'.assets[]? | select(.name | endswith(".zip")) | .browser_download_url')"
|
||||
fi
|
||||
|
||||
if [ -z "$download_urls" ]; then
|
||||
warn "$repo release $tag has no .zip assets - skipping"
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
SKIPPED_REPOS+=("$repo")
|
||||
continue
|
||||
fi
|
||||
|
||||
mkdir -p "$TMP_DIR/$repo"
|
||||
any_ok=0
|
||||
while IFS= read -r url; do
|
||||
[ -z "$url" ] && continue
|
||||
name="$(basename "$url")"
|
||||
log " downloading $name"
|
||||
if fetch_with_retry "$url" "$TMP_DIR/$repo/$name"; then
|
||||
if unzip -q -o "$TMP_DIR/$repo/$name" -d "$STAGING_DIR"; then
|
||||
any_ok=1
|
||||
else
|
||||
warn " unzip failed for $name"
|
||||
fi
|
||||
else
|
||||
warn " could not download $name after retry"
|
||||
fi
|
||||
done <<<"$download_urls"
|
||||
|
||||
if [ "$any_ok" -eq 1 ]; then
|
||||
INCLUDED=$((INCLUDED + 1))
|
||||
else
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
SKIPPED_REPOS+=("$repo")
|
||||
fi
|
||||
done < <(manifest_includes)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Build the pack
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ ! -d "$STAGING_DIR" ] || [ -z "$(ls -A "$STAGING_DIR" 2>/dev/null)" ]; then
|
||||
die "staging is empty - no addons were successfully fetched"
|
||||
fi
|
||||
|
||||
OUT="$DIST_DIR/$PACK_NAME"
|
||||
rm -f "$OUT"
|
||||
( cd "$STAGING_DIR" && zip -qr "$OUT" . )
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Summary
|
||||
# ---------------------------------------------------------------------------
|
||||
ADDON_FOLDERS="$(cd "$STAGING_DIR" && find . -mindepth 1 -maxdepth 1 -type d | wc -l)"
|
||||
SIZE="$(stat -c '%s' "$OUT")"
|
||||
SHA="$(sha256sum "$OUT" | awk '{print $1}')"
|
||||
|
||||
echo
|
||||
echo "============================================================"
|
||||
echo " pack: $PACK_NAME"
|
||||
echo " path: $OUT"
|
||||
echo " addon dirs: $ADDON_FOLDERS"
|
||||
echo " size: $SIZE bytes"
|
||||
echo " sha256: $SHA"
|
||||
echo " repos in: $INCLUDED"
|
||||
echo " repos out: $SKIPPED${SKIPPED_REPOS[*]:+ (${SKIPPED_REPOS[*]})}"
|
||||
echo "============================================================"
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
# List the most-recent release tag for every includable repo in manifest.yaml.
|
||||
# Pure read-only via Gitea API. No auth needed - the Exiles org repos are public.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MANIFEST="$REPO_ROOT/manifest.yaml"
|
||||
|
||||
API="https://git.sub-net.at/api/v1"
|
||||
ORG="Exiles"
|
||||
|
||||
# Same minimal-awk parser as build_pack.sh.
|
||||
manifest_includes() {
|
||||
awk '
|
||||
/^[[:space:]]*-[[:space:]]*repo:[[:space:]]*/ {
|
||||
sub(/^[[:space:]]*-[[:space:]]*repo:[[:space:]]*/, "")
|
||||
gsub(/[[:space:]]+$/, "")
|
||||
repo = $0; include = ""; next
|
||||
}
|
||||
/^[[:space:]]+include:[[:space:]]*/ {
|
||||
sub(/^[[:space:]]+include:[[:space:]]*/, "")
|
||||
gsub(/[[:space:]]+$/, "")
|
||||
include = $0
|
||||
if (repo != "" && include == "true") { print repo }
|
||||
repo = ""; include = ""
|
||||
}
|
||||
' "$MANIFEST"
|
||||
}
|
||||
|
||||
while read -r repo; do
|
||||
[ -z "$repo" ] && continue
|
||||
json="$(curl -fsS "$API/repos/$ORG/$repo/releases?limit=1" 2>/dev/null || echo '[]')"
|
||||
tag="$(printf '%s' "$json" | jq -r '.[0].tag_name // empty')"
|
||||
date="$(printf '%s' "$json" | jq -r '.[0].published_at // empty' | cut -dT -f1)"
|
||||
if [ -z "$tag" ]; then
|
||||
printf '%-30s (no releases)\n' "$repo"
|
||||
else
|
||||
printf '%-30s %-20s %s\n' "$repo" "$tag" "$date"
|
||||
fi
|
||||
done < <(manifest_includes)
|
||||
Reference in New Issue
Block a user