#!/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 ` inside the orchestrator. # # Manifest + recipe selection: # --manifest NAME json file under /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 /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: /scripts/. 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[@]}"