Replace the stub dev-env with the real, working implementation that grew
in the reference machine config — de-identified for the public scaffold.
Nix layer:
- options.nix: full project schema (url/upstream/fork/category/
worktreeRoot/worktrees{branch,path,remote}/isClone/deployFlakeInput),
deploy.targets, github.forkUser, writeDirenvHints. Drops the
forgejo-URL block + deploy-flake auto-derivation (incoherent in a
scaffold that uses explicit per-project urls).
- lib.nix: mkProject + worktreePath/bareRepoPath/projectRemotes,
generalized to the explicit-url model (origin falls back to upstream).
- config.nix: renders /etc/dev-env/{config.sh,projects.json,
tmux-sessions.json}, installs helpers via writeShellScriptBin, loads
shell functions into interactive shells, wires the git pre-commit hook.
Scripts (config-driven, read /etc/dev-env at runtime):
- bootstrap.sh, nav.sh, worktree.sh, pr-helpers.sh, rebase.sh,
status.sh, deploy.sh, regtest.sh, tmux-launch.sh.
- Stripped aiolabs/forgejo/bitspire/lamassu/webapp hardcoding; the
github-fork remote is renamed 'fork' to match git.remotes vocabulary.
- Removes the dev.sh stub (the matured impl uses discrete commands +
shell functions, not a unified 'dev' CLI).
presets/example.nix: a worked, generic project list replacing the
identity-specific aiolabs preset. tests/smoke.nix + flake checks
exercise the schema; 'nix flake check' is green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
145 lines
4.7 KiB
Bash
145 lines
4.7 KiB
Bash
#!/usr/bin/env bash
|
|
# dev-status: show divergence and dirty state of every dev-env worktree
|
|
#
|
|
# Reads /etc/dev-env/projects.json and walks every declared worktree,
|
|
# reporting dirty state and ahead/behind counts vs origin (and upstream,
|
|
# for projects that declare one).
|
|
|
|
set -euo pipefail
|
|
|
|
if [[ -r /etc/dev-env/config.sh ]]; then
|
|
# shellcheck disable=SC1091
|
|
source /etc/dev-env/config.sh
|
|
fi
|
|
|
|
PROJECTS_JSON="${DEVENV_PROJECTS_JSON:-/etc/dev-env/projects.json}"
|
|
|
|
if [[ ! -r "$PROJECTS_JSON" ]]; then
|
|
echo "projects.json not found at $PROJECTS_JSON" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v jq >/dev/null; then
|
|
echo "jq required" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m'
|
|
|
|
issues=()
|
|
|
|
header() {
|
|
echo ""
|
|
echo -e "${BOLD}${CYAN}═══════════════════════════════════════════════════════${NC}"
|
|
echo -e "${BOLD}${CYAN} $1${NC}"
|
|
echo -e "${BOLD}${CYAN}═══════════════════════════════════════════════════════${NC}"
|
|
}
|
|
|
|
check_one() {
|
|
local label="$1" path="$2" has_upstream="${3:-false}"
|
|
|
|
if [[ ! -d "$path/.git" ]] && [[ ! -f "$path/.git" ]]; then
|
|
printf " ${YELLOW}⚠${NC} %s: not present\n" "$label"
|
|
issues+=("$label: not present")
|
|
return
|
|
fi
|
|
|
|
git -C "$path" fetch --all --quiet 2>/dev/null || true
|
|
|
|
local branch icons="" status_str=""
|
|
branch="$(git -C "$path" branch --show-current 2>/dev/null || echo '?')"
|
|
|
|
[[ -n "$(git -C "$path" status --porcelain)" ]] && {
|
|
icons+="${YELLOW}●${NC} "
|
|
status_str+="dirty "
|
|
}
|
|
|
|
local behind ahead
|
|
behind=$(git -C "$path" rev-list --count "HEAD..origin/$branch" 2>/dev/null || echo 0)
|
|
ahead=$(git -C "$path" rev-list --count "origin/$branch..HEAD" 2>/dev/null || echo 0)
|
|
(( behind > 0 )) && icons+="${RED}↓${NC}$behind "
|
|
(( ahead > 0 )) && icons+="${GREEN}↑${NC}$ahead "
|
|
|
|
if [[ "$has_upstream" == "true" ]] && git -C "$path" remote get-url upstream &>/dev/null; then
|
|
local ub="main"
|
|
git -C "$path" rev-parse upstream/main &>/dev/null || ub="master"
|
|
local ub_behind
|
|
ub_behind=$(git -C "$path" rev-list --count "HEAD..upstream/$ub" 2>/dev/null || echo 0)
|
|
if (( ub_behind > 0 )); then
|
|
icons+="${CYAN}⇣${NC}$ub_behind "
|
|
issues+=("$label: $ub_behind behind upstream/$ub")
|
|
fi
|
|
fi
|
|
|
|
[[ -z "$icons" ]] && icons="${GREEN}✓${NC}"
|
|
printf " %-45s %s (%s)\n" "$label" "$icons" "$branch"
|
|
}
|
|
|
|
header "Development Environment Status"
|
|
echo -e " ${BLUE}Date:${NC} $(date '+%Y-%m-%d %H:%M')"
|
|
echo -e " ${BLUE}Host:${NC} $(hostname)"
|
|
echo -e " ${BLUE}Root:${NC} ${DEV_ROOT:-?}"
|
|
|
|
# Walk every project and worktree from the JSON.
|
|
mapfile -t PROJECTS < <(jq -r 'keys[]' "$PROJECTS_JSON")
|
|
|
|
for proj in "${PROJECTS[@]}"; do
|
|
echo ""
|
|
echo -e "${BLUE}─── $proj ───${NC}"
|
|
|
|
is_clone="$(jq -r --arg p "$proj" '.[$p].isClone' "$PROJECTS_JSON")"
|
|
has_upstream_decl="$(jq -r --arg p "$proj" '.[$p].remotes.upstream != null' "$PROJECTS_JSON")"
|
|
|
|
if [[ "$is_clone" == "true" ]]; then
|
|
clone_path="$(jq -r --arg p "$proj" '.[$p].clonePath' "$PROJECTS_JSON")"
|
|
check_one "$proj" "$clone_path" "$has_upstream_decl"
|
|
else
|
|
while IFS=$'\t' read -r wt_name wt_path; do
|
|
[[ -z "$wt_name" || "$wt_path" == "null" ]] && continue
|
|
check_one "$proj/$wt_name" "$wt_path" "$has_upstream_decl"
|
|
done < <(jq -r --arg p "$proj" '
|
|
.[$p].worktrees
|
|
| to_entries[]
|
|
| "\(.key)\t\(.value.path)"
|
|
' "$PROJECTS_JSON")
|
|
fi
|
|
done
|
|
|
|
# Docker
|
|
echo ""
|
|
echo -e "${BLUE}─── Docker ───${NC}"
|
|
if command -v docker &>/dev/null; then
|
|
running="$(docker ps --format '{{.Names}}' 2>/dev/null | wc -l)"
|
|
echo -e " ${BLUE}Containers running:${NC} $running"
|
|
fi
|
|
|
|
header "Summary"
|
|
if (( ${#issues[@]} == 0 )); then
|
|
echo -e " ${GREEN}✓ All worktrees are clean and in sync!${NC}"
|
|
else
|
|
echo -e " ${YELLOW}Issues found:${NC}"
|
|
for issue in "${issues[@]}"; do
|
|
echo -e " ${YELLOW}•${NC} $issue"
|
|
done
|
|
fi
|
|
|
|
cat <<EOF
|
|
|
|
${BLUE}Legend:${NC}
|
|
${GREEN}✓${NC} clean ${YELLOW}●${NC} dirty ${GREEN}↑${NC} ahead origin
|
|
${RED}↓${NC} behind origin ${CYAN}⇣${NC} behind upstream
|
|
|
|
${BLUE}Quick actions:${NC}
|
|
wts sync all worktrees with origin
|
|
wtu <repo> fetch upstream + show divergence
|
|
rebase status which forks need rebasing onto upstream
|
|
dev-env-bootstrap materialize missing worktrees
|
|
|
|
EOF
|