Files
FanaticPythoner (Nathan Trudeau) 0c159e91fb Initial Commit
2026-04-27 15:56:43 -04:00

163 lines
6.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# shellcheck shell=bash
#
# Render or execute a setup plan defined by one of the orchestrator's
# setup-steps manifests. The welcome repo does not hardcode step
# lists; every step is read from the manifest at runtime.
#
# Default manifest / recipe is the operator pair
# (operator_setup_steps.json / operator-setup). setup.sh in this
# scaffold always targets the operator pair; the --manifest and
# --recipe flags here are the single override point for callers that
# need the contributor pair (or any future pair).
#
# Modes:
# default : print the plan.
# --run : exec `just <recipe>` inside the orchestrator.
#
# Manifest + recipe selection:
# --manifest NAME json file under <orchestrator>/scripts/
# (default: operator_setup_steps.json).
# --recipe NAME just recipe to exec under --run
# (default: operator-setup).
#
# Flags (forwarded to the orchestrator under --run):
# --headless, --no-browser headless mode
# --yes non-interactive
# --dry-run, --plan-only plan-only execution
# --skip-optional skip optional steps
# --only ID[,ID...] run the listed step ids
# -h, --help usage
set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
# shellcheck disable=SC1091
. "$here/common.sh"
load_env
MODE="print" # "print" or "run"
# Operator-first defaults. `just next-steps` prints the operator plan
# and `just run-next-steps` execs `just operator-setup`. The
# contributor pair is reachable only via explicit --manifest /
# --recipe overrides.
MANIFEST_FILE="operator_setup_steps.json"
RECIPE="operator-setup"
forward_args=()
usage() {
cat <<'USAGE'
Usage: just next-steps [--run] [flags]
just run-next-steps [flags]
Manifest + recipe:
--manifest NAME json file under <orchestrator>/scripts/
(default: operator_setup_steps.json)
--recipe NAME just recipe to exec under --run
(default: operator-setup)
Flags (forwarded): --headless, --no-browser, --yes, --dry-run,
--skip-optional, --only ID[,ID...]
Step list source: <orchestrator>/scripts/<MANIFEST_FILE>.
USAGE
}
while [ $# -gt 0 ]; do
case "$1" in
--run) MODE=run; shift ;;
--manifest) shift; [ $# -gt 0 ] || die "--manifest needs an argument"; MANIFEST_FILE="$1"; shift ;;
--manifest=*) MANIFEST_FILE="${1#--manifest=}"; shift ;;
--recipe) shift; [ $# -gt 0 ] || die "--recipe needs an argument"; RECIPE="$1"; shift ;;
--recipe=*) RECIPE="${1#--recipe=}"; shift ;;
--headless|--no-browser) forward_args+=("--headless"); shift ;;
--yes) forward_args+=("--yes"); shift ;;
--dry-run|--plan-only) forward_args+=("--dry-run"); shift ;;
--skip-optional) forward_args+=("--skip-optional"); shift ;;
--only) shift; [ $# -gt 0 ] || die "--only needs an argument"; forward_args+=("--only" "$1"); shift ;;
--only=*) forward_args+=("--only" "${1#--only=}"); shift ;;
-h|--help) usage; exit 0 ;;
--) shift; break ;;
*) die "unexpected argument: $1 (try --help)" ;;
esac
done
# Locate the orchestrator checkout.
workspace_root="${FORGE_WORKSPACE_ROOT:-.}"
repo_url="${FORGE_ORCHESTRATOR_REPO_URL:-}"
if [ -z "$repo_url" ]; then
die "FORGE_ORCHESTRATOR_REPO_URL is unset; run 'just init-env' and edit .env"
fi
repo_name="$(basename "$repo_url" .git)"
orchestrator="$workspace_root/$repo_name"
if [ ! -d "$orchestrator/.git" ]; then
err "orchestrator checkout not found at $orchestrator"
note "run 'just clone-orchestrator' (or 'just setup') first."
exit 1
fi
manifest="$orchestrator/scripts/$MANIFEST_FILE"
if [ ! -f "$manifest" ]; then
warn "orchestrator checkout at $orchestrator does not ship"
warn " scripts/$MANIFEST_FILE (expected in newer versions)."
note "falling back to the orchestrator README; open:"
note " $orchestrator/README.md"
exit 1
fi
step "next steps (source: orchestrator manifest)"
note "source: $manifest"
note "orchestrator: $orchestrator"
printf '\n' >&2
python3 - "$manifest" >&2 <<'PY'
import json, sys, textwrap
path = sys.argv[1]
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
if data.get("schema_version") != 1:
sys.stderr.write(
f"[err] unsupported setup-steps manifest schema in {path}: "
f"{data.get('schema_version')!r}\n"
)
sys.exit(2)
steps = data.get("steps", [])
total = len(steps)
for i, s in enumerate(steps, 1):
sid = s["id"]
title = s.get("title", sid)
desc = s.get("desc", "")
cmd = " ".join(s["cmd"])
opt = "" if "optional" in title.lower() else (" [optional]" if s.get("optional") else "")
sys.stderr.write(f" ({i}/{total}) {title}{opt}\n")
sys.stderr.write(f" id: {sid}\n")
if desc:
for line in textwrap.wrap(desc, width=76,
initial_indent=" ",
subsequent_indent=" "):
sys.stderr.write(line + "\n")
sys.stderr.write(f" run: {cmd}\n\n")
PY
if [ "$MODE" != "run" ]; then
# ``${arr:+...}`` on an empty array triggers set -u; build explicitly.
fwd_suffix=""
if [ "${#forward_args[@]}" -gt 0 ]; then
fwd_suffix=" ${forward_args[*]}"
fi
note "Execute via:"
note " just run-next-steps --manifest ${MANIFEST_FILE} --recipe ${RECIPE}${fwd_suffix}"
note "Execute inside the orchestrator:"
note " cd $orchestrator && just ${RECIPE}${fwd_suffix}"
exit 0
fi
step "exec just ${RECIPE} (cwd=$orchestrator)"
if ! command -v just >/dev/null 2>&1; then
die "just not on PATH; install via 'just doctor' fix hint"
fi
cd "$orchestrator"
exec just "${RECIPE}" "${forward_args[@]}"