mirror of
https://github.com/logos-co/logos-modules-release.git
synced 2026-08-27 12:41:15 +00:00
* Add "Force build" option to the release workflows (#5) "Release all modules" and each per-module "Release <module>" workflow now take a `force_build` toggle on the Run-workflow form (off by default). When on, it replaces the current published release even if the version is unchanged — e.g. the submodule pointer moved while metadata.json#version stayed the same. The toggle threads down the existing wrapper chain to the action's `skip_if_published` input (inverted), so signing config still lives in one place: release-all.yml / release-<module>.yml --force_build--> _release-module.yml --skip_if_published: !force_build--> logos-modules-release-action/release.yml@v1 - _release-module.yml: new `force_build` input -> skip_if_published - release-all.yml: dispatch input, passed through the matrix fan-out - release-module.yml.template: dispatch input (so generated per-module workflows get it too) - catalog.sh: matching `--force` flag for `release` / `release-all` - README: document the toggle Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * Regenerate per-module release workflows with Force build option The shared workflow machinery now carries the `force_build` toggle (synced from logos-modules-release-base via the merge of #5), but the 14 existing per-module `release-<module>.yml` files were generated from the old template and still used `workflow_dispatch: {}`. Regenerate them from the updated template so each per-module "Release <module>" run also exposes the Force build option (replace the current published release when the version is unchanged). Mechanically identical to re-running scripts/add-module.sh's template substitution; the only change per file is the force_build input + its passthrough to _release-module.yml. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
252 lines
8.7 KiB
Bash
Executable File
252 lines
8.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Drive this Logos module catalog's GitHub Actions workflows from the
|
|
# terminal — the gh-CLI alternative to opening the repo's Actions tab
|
|
# and clicking "Run workflow".
|
|
#
|
|
# Works in this repo AND in any fork: every call targets the current
|
|
# repo via `gh`, which reads the git remote. Nothing here is hardcoded
|
|
# to a specific owner/repo.
|
|
#
|
|
# Usage:
|
|
# ./scripts/catalog.sh <command> [args] [--watch]
|
|
#
|
|
# Commands:
|
|
# release [<module>] [--force]
|
|
# Trigger release-<module>.yml. With no <module>,
|
|
# list the modules this catalog publishes.
|
|
# release-all [--force] Trigger release-all.yml (re)release every module.
|
|
# rebuild-index Trigger rebuild-index.yml (regenerate index.json).
|
|
# unpublish <module> [<version>] [--dry-run] [--keep-tags]
|
|
# Trigger unpublish.yml. Removes a module from the
|
|
# catalog — every version, or just <version> if
|
|
# given. DESTRUCTIVE: deletes for real by default;
|
|
# pass --dry-run first to preview the matches.
|
|
# status Show recent workflow runs for this repo.
|
|
# watch [<run-id>] Follow a run to completion (latest run if no id).
|
|
#
|
|
# --watch on release / release-all / rebuild-index / unpublish: follow
|
|
# the run that was just triggered through to completion.
|
|
# --force on release / release-all: Force build — replace the current
|
|
# published release if it has the same version (otherwise an
|
|
# already-published version is skipped).
|
|
#
|
|
# Examples:
|
|
# ./scripts/catalog.sh release logos-chat-module
|
|
# ./scripts/catalog.sh release logos-chat-module --force
|
|
# ./scripts/catalog.sh release-all --watch
|
|
# ./scripts/catalog.sh rebuild-index
|
|
# ./scripts/catalog.sh unpublish logos-chat-module --dry-run
|
|
# ./scripts/catalog.sh unpublish logos-chat-module 1.2.3
|
|
# ./scripts/catalog.sh status
|
|
|
|
set -euo pipefail
|
|
|
|
# Resolve our own absolute path BEFORE cd'ing away — print_usage seds
|
|
# this file, and a relative $0 wouldn't resolve from the repo root.
|
|
SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
|
|
|
|
cd "$(git rev-parse --show-toplevel)"
|
|
|
|
WORKFLOW_DIR=".github/workflows"
|
|
|
|
die() { echo "error: $*" >&2; exit 1; }
|
|
|
|
print_usage() {
|
|
# Echo the comment header (lines 2..first blank), stripping the `# `
|
|
# prefix. Two plain substitutions — `\|` alternation isn't portable
|
|
# to the BSD sed macOS ships. Reads SCRIPT_PATH (absolute) rather
|
|
# than $0, which would be relative to the pre-cd directory.
|
|
sed -n '2,/^$/p' "$SCRIPT_PATH" | sed -e 's/^# //' -e 's/^#$//'
|
|
}
|
|
|
|
# ── flag + positional split ──────────────────────────────────────────
|
|
# Flags may appear anywhere; everything else is a positional arg.
|
|
WATCH=0
|
|
DRY_RUN=0
|
|
KEEP_TAGS=0
|
|
FORCE=0
|
|
ARGS=()
|
|
for a in "$@"; do
|
|
case "$a" in
|
|
--watch) WATCH=1 ;;
|
|
--dry-run) DRY_RUN=1 ;;
|
|
--keep-tags) KEEP_TAGS=1 ;;
|
|
--force) FORCE=1 ;;
|
|
-h|--help) print_usage; exit 0 ;;
|
|
-*) die "unknown flag: $a (see --help)" ;;
|
|
*) ARGS+=("$a") ;;
|
|
esac
|
|
done
|
|
|
|
if [ "${#ARGS[@]}" -eq 0 ]; then
|
|
print_usage
|
|
exit 2
|
|
fi
|
|
CMD="${ARGS[0]}"
|
|
|
|
# ── preflight ─────────────────────────────────────────────────────────
|
|
command -v gh >/dev/null 2>&1 \
|
|
|| die "the GitHub CLI ('gh') is not installed — see https://cli.github.com"
|
|
gh auth status >/dev/null 2>&1 \
|
|
|| die "not logged in to GitHub — run 'gh auth login' first"
|
|
[ -f "${WORKFLOW_DIR}/_release-module.yml" ] \
|
|
|| die "not a Logos catalog repo (no ${WORKFLOW_DIR}/_release-module.yml) — run from a clone of logos-modules-release-base or a fork"
|
|
|
|
# ── helpers ───────────────────────────────────────────────────────────
|
|
|
|
# Module names with a per-module release workflow: release-<module>.yml,
|
|
# excluding the umbrella release-all.yml and the .template.
|
|
list_modules() {
|
|
local f name
|
|
for f in "${WORKFLOW_DIR}"/release-*.yml; do
|
|
[ -e "$f" ] || continue # nullglob-safe: no matches -> skip
|
|
name="$(basename "$f")"
|
|
name="${name#release-}"
|
|
name="${name%.yml}"
|
|
[ "$name" = "all" ] && continue
|
|
echo "$name"
|
|
done
|
|
}
|
|
|
|
# Newest run id for a workflow file, or empty if none yet.
|
|
latest_run_id() {
|
|
gh run list --workflow "$1" --limit 1 \
|
|
--json databaseId --jq '.[0].databaseId // empty' 2>/dev/null || true
|
|
}
|
|
|
|
# Poll until a run newer than $2 appears for workflow $1, then watch it.
|
|
# `gh workflow run` returns no run id, so we diff against the pre-trigger
|
|
# newest. Returns non-zero if the run never registers within the window
|
|
# — the trigger itself still succeeded, but a caller that asked for
|
|
# --watch (e.g. from automation) must be able to tell watching failed.
|
|
follow_new_run() {
|
|
local wf="$1" before="$2" id="" tries=0
|
|
echo "==> waiting for the run to register…"
|
|
while [ "$tries" -lt 20 ]; do
|
|
sleep 2
|
|
id="$(latest_run_id "$wf")"
|
|
if [ -n "$id" ] && [ "$id" != "$before" ]; then
|
|
gh run watch "$id"
|
|
return 0
|
|
fi
|
|
tries=$((tries + 1))
|
|
done
|
|
echo " workflow was triggered, but couldn't resolve the new run to" >&2
|
|
echo " watch — check it with './scripts/catalog.sh status'." >&2
|
|
return 1
|
|
}
|
|
|
|
# Trigger a workflow file; optionally follow the run it created.
|
|
# Extra args after the workflow name pass straight to `gh workflow run`
|
|
# (used for unpublish's -f inputs).
|
|
run_workflow() {
|
|
local wf="$1"; shift
|
|
local before=""
|
|
[ "$WATCH" = "1" ] && before="$(latest_run_id "$wf")"
|
|
|
|
echo "==> triggering ${wf}"
|
|
gh workflow run "$wf" "$@"
|
|
|
|
if [ "$WATCH" = "1" ]; then
|
|
follow_new_run "$wf" "$before"
|
|
else
|
|
echo " queued. Track it: ./scripts/catalog.sh status (or re-run with --watch)"
|
|
fi
|
|
}
|
|
|
|
# ── commands ──────────────────────────────────────────────────────────
|
|
case "$CMD" in
|
|
release)
|
|
MODULE="${ARGS[1]:-}"
|
|
if [ -z "$MODULE" ]; then
|
|
echo "Modules in this catalog (pass one to 'release'):"
|
|
mods="$(list_modules)"
|
|
if [ -z "$mods" ]; then
|
|
echo " (none — add one with ./scripts/add-module.sh)"
|
|
else
|
|
echo "$mods" | sed 's/^/ /'
|
|
fi
|
|
exit 0
|
|
fi
|
|
WF="release-${MODULE}.yml"
|
|
if [ ! -f "${WORKFLOW_DIR}/${WF}" ]; then
|
|
echo "error: no release workflow for '${MODULE}'." >&2
|
|
echo "known modules:" >&2
|
|
list_modules | sed 's/^/ /' >&2
|
|
exit 1
|
|
fi
|
|
if [ "$FORCE" = "1" ]; then
|
|
run_workflow "$WF" -f "force_build=true"
|
|
else
|
|
run_workflow "$WF"
|
|
fi
|
|
;;
|
|
|
|
release-all)
|
|
if [ "$FORCE" = "1" ]; then
|
|
run_workflow "release-all.yml" -f "force_build=true"
|
|
else
|
|
run_workflow "release-all.yml"
|
|
fi
|
|
;;
|
|
|
|
rebuild-index)
|
|
run_workflow "rebuild-index.yml"
|
|
;;
|
|
|
|
unpublish)
|
|
MODULE="${ARGS[1]:-}"
|
|
VERSION="${ARGS[2]:-}"
|
|
[ -n "$MODULE" ] || die "unpublish needs a module name (the <name> in <name>-v<version> release tags)"
|
|
|
|
# Mirror the workflow input defaults: delete for real, drop tags too.
|
|
DELETE_TAGS="true"
|
|
[ "$KEEP_TAGS" = "1" ] && DELETE_TAGS="false"
|
|
DRY="false"
|
|
[ "$DRY_RUN" = "1" ] && DRY="true"
|
|
|
|
if [ "$DRY" = "true" ]; then
|
|
echo "==> dry run — nothing will be deleted"
|
|
else
|
|
target="all versions of '${MODULE}'"
|
|
[ -n "$VERSION" ] && target="'${MODULE}' v${VERSION}"
|
|
echo "!! this will DELETE ${target} from the catalog (irreversible)."
|
|
# Confirm only at an interactive terminal — a fat-finger guard.
|
|
# Piped / CI use proceeds straight through, matching how the
|
|
# workflow itself runs (no confirmation step).
|
|
if [ -t 0 ]; then
|
|
printf ' continue? [y/N] '
|
|
read -r reply
|
|
case "$reply" in
|
|
y|Y|yes|YES) ;;
|
|
*) echo " aborted."; exit 0 ;;
|
|
esac
|
|
fi
|
|
fi
|
|
|
|
run_workflow "unpublish.yml" \
|
|
-f "module=${MODULE}" \
|
|
-f "version=${VERSION}" \
|
|
-f "delete_tags=${DELETE_TAGS}" \
|
|
-f "dry_run=${DRY}"
|
|
;;
|
|
|
|
status)
|
|
echo "==> recent workflow runs"
|
|
gh run list --limit 15
|
|
;;
|
|
|
|
watch)
|
|
RUN_ID="${ARGS[1]:-}"
|
|
if [ -z "$RUN_ID" ]; then
|
|
RUN_ID="$(gh run list --limit 1 --json databaseId --jq '.[0].databaseId // empty' 2>/dev/null || true)"
|
|
[ -n "$RUN_ID" ] || die "no runs found to watch"
|
|
fi
|
|
gh run watch "$RUN_ID"
|
|
;;
|
|
|
|
*)
|
|
die "unknown command: ${CMD} (see --help)"
|
|
;;
|
|
esac
|