life-manager/run
2026-03-09 21:41:49 -07:00

168 lines
9.2 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
API_DIR="$ROOT/codebase/apps/api"
# shellcheck source=/dev/null
source "${HOME}/Code/@packages/@ts/@cli/bash-templates/lib/colors.sh"
# Short aliases used throughout this script
C=$CYAN G=$GREEN Y=$YELLOW M=$MAGENTA B=$BOLD R=$NC D=$DIM
cmd_help() {
echo -e "${B}life-manager${R} — unified project runner\n"
echo -e "${C}${B}Dev Workflow${R}"
echo -e " ${G}dev${R} Start dev (API + web) ${D}turbo dev${R}"
echo -e " ${G}dev:all${R} Start all including showcase ${D}turbo dev (all)${R}"
echo -e " ${G}dev:showcase${R} Start showcase only ${D}vite on :5702${R}"
echo -e " ${G}build${R} Build all packages ${D}turbo build${R}"
echo -e " ${G}typecheck${R} Type-check everything ${D}turbo typecheck${R}"
echo -e " ${G}lint${R} Lint all packages ${D}eslint${R}"
echo ""
echo -e "${C}${B}Testing${R}"
echo -e " ${G}test${R} Unit tests ${D}turbo test${R}"
echo -e " ${G}test:e2e${R} Playwright E2E tests ${D}playwright test${R}"
echo -e " ${G}test:e2e:prod${R} E2E smoke tests vs production ${D}playwright → black${R}"
echo -e " ${G}test:all${R} Unit + E2E ${D}turbo test + playwright${R}"
echo ""
echo -e "${C}${B}Docker${R}"
echo -e " ${G}docker${R} Start Postgres + Redis ${D}docker compose up -d${R}"
echo -e " ${G}docker:stop${R} Stop Postgres + Redis ${D}docker compose down${R}"
echo -e " ${G}docker:status${R} Show container status ${D}docker compose ps${R}"
echo ""
echo -e "${C}${B}Infrastructure${R}"
echo -e " ${G}infra${R} Cross-host status dashboard ${D}apricot + black + plum${R}"
echo -e " ${G}infra status${R} ${Y}[host]${R} Detailed host status ${D}checks ports/services${R}"
echo -e " ${G}infra health${R} Quick one-liner per host ${D}green/red summary${R}"
echo -e " ${G}infra deploy${R} Build + deploy to black ${D}prod.sh release${R}"
echo -e " ${G}infra start|stop|restart${R} ${Y}[host]${R} Service control"
echo -e " ${G}infra logs${R} ${Y}[svc]${R} Follow black logs ${D}journalctl${R}"
echo ""
echo -e "${C}${B}Database${R}"
echo -e " ${G}db:migrate${R} Run dev migrations ${D}typeorm migration:run${R}"
echo -e " ${G}db:migrate:prod${R} Run prod migrations (+ backup) ${D}backup → migration:run${R}"
echo -e " ${G}db:backup${R} Backup dev database ${D}pg_dump → .project/backups/${R}"
echo -e " ${G}db:backup:prod${R} Backup prod database ${D}pg_dump → .project/backups/${R}"
echo -e " ${G}db:seed${R} Seed database ${D}pnpm seed${R}"
echo -e " ${G}db:reset${R} Drop + recreate dev database ${D}dropdb + createdb${R}"
echo -e " ${G}db:reseed${R} Reset + seed (fresh start) ${D}db:reset + db:seed${R}"
echo -e " ${G}db:generate${R} ${Y}<name>${R} Generate migration ${D}typeorm migration:generate${R}"
echo ""
echo -e "${C}${B}Life Manager CLI${R} ${D}(any unrecognized command passes through to the CLI)${R}"
echo -e " ${G}settings${R} ${Y}<cmd>${R} Manage settings ${D}list | get <key> | set <key> <val>${R}"
echo -e " ${G}reminders${R} ${Y}<cmd>${R} Manage reminders ${D}list | create | fire <id>${R}"
echo -e " ${G}services${R} ${Y}<cmd>${R} Service cluster status ${D}status${R}"
echo -e " ${G}meds${R} ${Y}<cmd>${R} Medication management ${D}list | due | log <id> | logs <id>${R}"
echo -e " ${G}tasks${R} ${Y}<cmd>${R} Task management ${D}list | create | update${R}"
echo -e " ${G}today${R} Today overview ${D}schedule + tasks + habits${R}"
echo -e " ${G}chat${R} ${Y}[msg]${R} AI chat session ${D}interactive or one-shot${R}"
echo ""
echo -e "${C}${B}Production${R}"
echo -e " ${G}prod:release${R} Full build + deploy ${D}scripts/prod.sh release${R}"
echo -e " ${G}prod:start${R} Start prod services ${D}scripts/prod.sh start${R}"
echo -e " ${G}prod:stop${R} Stop prod services ${D}scripts/prod.sh stop${R}"
echo -e " ${G}prod:restart${R} Restart prod services ${D}scripts/prod.sh restart${R}"
echo -e " ${G}prod:status${R} Show service status ${D}scripts/prod.sh status${R}"
echo -e " ${G}prod:logs${R} ${Y}[svc]${R} Follow prod logs ${D}scripts/prod.sh logs${R}"
echo ""
echo -e "${M}Usage:${R} ./run <command> [args...]"
}
case "${1:-help}" in
# Dev workflow
dev) cd "$ROOT" && pnpm dev ;;
dev:all) cd "$ROOT" && pnpm dev:all ;;
dev:showcase) cd "$ROOT" && pnpm dev:showcase ;;
build) cd "$ROOT" && pnpm build ;;
typecheck) cd "$ROOT" && pnpm typecheck ;;
lint) cd "$ROOT" && pnpm lint ;;
# Testing
test) cd "$ROOT" && pnpm test ;;
test:e2e) cd "$ROOT" && pnpm test:e2e ;;
test:e2e:prod) cd "$ROOT" && pnpm test:e2e:prod ;;
test:all) cd "$ROOT" && pnpm test:all ;;
# Docker (local containers)
docker) cd "$ROOT" && docker compose up -d ;;
docker:stop) cd "$ROOT" && docker compose down ;;
docker:status) cd "$ROOT" && docker compose ps ;;
# Database
db:migrate)
echo -e "${C}=== Running database migrations ===${R}"
cd "$API_DIR"
set -a; source "$ROOT/.env"; set +a
pnpm migration:run
;;
db:migrate:prod)
echo -e "${C}=== Running production database migrations ===${R}"
echo -e "${Y}Backing up production database first...${R}"
"$0" db:backup:prod
echo ""
cd "$API_DIR"
set -a; source "$ROOT/.env.production"; set +a
pnpm migration:run
;;
db:backup)
DUMP_DIR="$ROOT/.project/backups"
mkdir -p "$DUMP_DIR"
DUMP_FILE="$DUMP_DIR/life_manager_$(date +%Y%m%d_%H%M%S).sql.gz"
set -a; source "$ROOT/.env"; set +a
echo -e "${C}=== Backing up dev database → ${DUMP_FILE} ===${R}"
PGPASSWORD="$DATABASE_PASSWORD" pg_dump -h "${DATABASE_HOST:-localhost}" -p "${DATABASE_PORT:-25471}" -U "${DATABASE_USER:-lilith}" "${DATABASE_NAME:-life_manager}" | gzip > "$DUMP_FILE"
echo -e "${G}Backup complete:${R} $DUMP_FILE ($(du -h "$DUMP_FILE" | cut -f1))"
;;
db:backup:prod)
DUMP_DIR="$ROOT/.project/backups"
mkdir -p "$DUMP_DIR"
DUMP_FILE="$DUMP_DIR/life_manager_prod_$(date +%Y%m%d_%H%M%S).sql.gz"
set -a; source "$ROOT/.env.production"; set +a
echo -e "${C}=== Backing up production database → ${DUMP_FILE} ===${R}"
PGPASSWORD="$DATABASE_PASSWORD" pg_dump -h "${DATABASE_HOST:-localhost}" -p "${DATABASE_PORT:-25471}" -U "${DATABASE_USER:-lilith}" "${DATABASE_NAME:-life_manager_prod}" | gzip > "$DUMP_FILE"
echo -e "${G}Backup complete:${R} $DUMP_FILE ($(du -h "$DUMP_FILE" | cut -f1))"
;;
db:seed) cd "$API_DIR" && pnpm seed ;;
db:reset)
set -a; source "$ROOT/.env"; set +a
DB="${DATABASE_NAME:-life_manager}"
if [[ "$DB" == *prod* ]]; then
echo -e "${Y}Refusing to reset production database: ${DB}${R}"
exit 1
fi
echo -e "${Y}=== Dropping and recreating dev database: ${DB} ===${R}"
PGPASSWORD="$DATABASE_PASSWORD" psql -h "${DATABASE_HOST:-localhost}" -p "${DATABASE_PORT:-25471}" -U "${DATABASE_USER:-lilith}" -d postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$DB' AND pid <> pg_backend_pid();" > /dev/null 2>&1
PGPASSWORD="$DATABASE_PASSWORD" dropdb -h "${DATABASE_HOST:-localhost}" -p "${DATABASE_PORT:-25471}" -U "${DATABASE_USER:-lilith}" --if-exists "$DB"
PGPASSWORD="$DATABASE_PASSWORD" createdb -h "${DATABASE_HOST:-localhost}" -p "${DATABASE_PORT:-25471}" -U "${DATABASE_USER:-lilith}" "$DB"
PGPASSWORD="$DATABASE_PASSWORD" psql -h "${DATABASE_HOST:-localhost}" -p "${DATABASE_PORT:-25471}" -U "${DATABASE_USER:-lilith}" "$DB" -c 'CREATE EXTENSION IF NOT EXISTS pgcrypto;' > /dev/null
echo -e "${G}Database ${DB} reset.${R}"
;;
db:reseed)
echo -e "${C}=== Reset + Seed ===${R}"
"$0" db:reset
"$0" db:seed
;;
db:generate)
if [[ -z "${2:-}" ]]; then
echo -e "${Y}Usage:${R} ./run db:generate <migration-name>"
exit 1
fi
cd "$API_DIR"
set -a; source "$ROOT/.env"; set +a
pnpm migration:generate "src/migrations/$2"
;;
# Production (pass through to CLI for full --target support)
prod:release) shift; "$ROOT/scripts/prod.sh" release --target black "$@" ;;
prod:start) shift; "$ROOT/scripts/prod.sh" start --target black "$@" ;;
prod:stop) shift; "$ROOT/scripts/prod.sh" stop --target black "$@" ;;
prod:restart) shift; "$ROOT/scripts/prod.sh" restart --target black "$@" ;;
prod:status) shift; "$ROOT/scripts/prod.sh" status --target black "$@" ;;
prod:logs) shift; "$ROOT/scripts/prod.sh" logs "$@" --target black ;;
# Help
help|--help|-h) cmd_help ;;
# Everything else → life-manager CLI passthrough
*) set -a; source "$ROOT/.env"; set +a; cd "$ROOT/codebase/apps/cli" && node --import @swc-node/register/esm-register src/bin/lm.ts "$@" ;;
esac