| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- #!/usr/bin/env bash
- # appctl — convenience wrapper for the dev/prod compose split.
- #
- # Replaces the old Makefile. The dev compose chain layers
- # docker-compose.dev.yml on top of docker-compose.yml so source bind
- # mounts and the css-watcher sidecar only show up for `appctl dev …`.
- # Prod uses docker-compose.yml alone.
- set -euo pipefail
- # Resolve the repo root (parent of bin/) regardless of where the user
- # invokes us from, so cd into the right place even when called via the
- # top-level ./appctl symlink.
- SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
- REPO_ROOT="$(cd "$(dirname "$SCRIPT_PATH")/.." && pwd)"
- cd "$REPO_ROOT"
- # Exported so the css-watcher service can run as the host user via
- # `user: "${HOST_UID}:${HOST_GID}"` in docker-compose.dev.yml. Files
- # the watcher writes into bind-mounted host paths
- # (public/assets/css/app.css, public/assets/js/vendor/*) then land
- # with normal ownership.
- export HOST_UID="${HOST_UID:-$(id -u)}"
- export HOST_GID="${HOST_GID:-$(id -g)}"
- COMPOSE_PROD=(docker compose)
- COMPOSE_DEV=(docker compose -f docker-compose.yml -f docker-compose.dev.yml)
- usage() {
- cat <<'EOF'
- appctl — Sprint Planner dev/prod/test wrapper
- USAGE
- appctl <command> [subcommand]
- DEV
- appctl dev start start dev stack (app + css-watcher) in foreground
- appctl dev stop stop and remove dev containers
- appctl dev build rebuild dev images
- appctl dev shell bash into the running app container
- appctl dev logs tail logs from the dev stack
- PROD
- appctl prod start start prod stack detached
- appctl prod stop stop and remove prod containers
- appctl prod build rebuild prod images
- CHECKS (one-shot containers, no running stack required)
- appctl lint php -l on src/ + tests/
- appctl test phpunit
- appctl check lint + test (used by the /check Claude Code skill)
- OTHER
- appctl completion print the bash completion script to stdout
- appctl help show this message
- EOF
- }
- die() {
- printf 'appctl: %s\n' "$*" >&2
- exit 2
- }
- # --- bash completion auto-source on first run --------------------------
- #
- # We ship bin/appctl-completion.bash next to this script. On the very
- # first interactive invocation we offer to add a `source` line to the
- # user's ~/.bashrc — only once, gated by a marker file in $HOME so the
- # prompt never appears again, and only when stdin is a TTY (CI / piped
- # invocations are silent).
- maybe_offer_completion() {
- [[ -t 0 && -t 1 ]] || return 0
- [[ "${APPCTL_NO_COMPLETION_PROMPT:-}" != "1" ]] || return 0
- local marker="${HOME}/.config/appctl/completion-installed"
- [[ ! -e "$marker" ]] || return 0
- local completion_script="${REPO_ROOT}/bin/appctl-completion.bash"
- [[ -f "$completion_script" ]] || return 0
- local bashrc="${HOME}/.bashrc"
- if [[ -f "$bashrc" ]] && grep -Fq "appctl-completion.bash" "$bashrc"; then
- # Already wired up by a previous repo checkout; just write the
- # marker so we stop asking.
- mkdir -p "$(dirname "$marker")"
- : > "$marker"
- return 0
- fi
- printf '\n[appctl] bash completion is not yet installed.\n'
- printf ' Add `source %s` to %s? [y/N] ' \
- "$completion_script" "$bashrc"
- local reply
- read -r reply || reply=""
- case "$reply" in
- y|Y|yes|YES)
- mkdir -p "$(dirname "$marker")"
- {
- printf '\n# appctl bash completion (added by appctl on first run)\n'
- printf 'source %q\n' "$completion_script"
- } >> "$bashrc"
- : > "$marker"
- printf '[appctl] added. Open a new shell or `source %s` to activate.\n\n' "$bashrc"
- ;;
- *)
- mkdir -p "$(dirname "$marker")"
- : > "$marker"
- printf '[appctl] skipped. Re-run with APPCTL_NO_COMPLETION_PROMPT=1 to silence; delete %s to ask again.\n\n' "$marker"
- ;;
- esac
- }
- # --- subcommands -------------------------------------------------------
- cmd_dev() {
- local sub="${1:-}"
- case "$sub" in
- start) "${COMPOSE_DEV[@]}" up ;;
- stop) "${COMPOSE_DEV[@]}" down ;;
- build) "${COMPOSE_DEV[@]}" build ;;
- shell) "${COMPOSE_DEV[@]}" exec app bash ;;
- logs) "${COMPOSE_DEV[@]}" logs -f ;;
- ""|help|-h|--help) usage ;;
- *) die "unknown dev subcommand: $sub (try: start|stop|build|shell|logs)" ;;
- esac
- }
- cmd_prod() {
- local sub="${1:-}"
- case "$sub" in
- start) "${COMPOSE_PROD[@]}" up -d ;;
- stop) "${COMPOSE_PROD[@]}" down ;;
- build) "${COMPOSE_PROD[@]}" build ;;
- ""|help|-h|--help) usage ;;
- *) die "unknown prod subcommand: $sub (try: start|stop|build)" ;;
- esac
- }
- cmd_lint() {
- "${COMPOSE_DEV[@]}" --profile test run --rm tests \
- sh -c 'find src tests -name "*.php" -print0 | xargs -0 -n1 -P 4 php -l > /dev/null && echo "lint: OK"'
- }
- cmd_test() {
- "${COMPOSE_DEV[@]}" --profile test run --rm tests
- }
- cmd_check() {
- cmd_lint
- cmd_test
- }
- cmd_completion() {
- local completion_script="${REPO_ROOT}/bin/appctl-completion.bash"
- [[ -f "$completion_script" ]] || die "completion script missing: $completion_script"
- cat "$completion_script"
- }
- # --- dispatch ----------------------------------------------------------
- maybe_offer_completion
- cmd="${1:-help}"
- shift || true
- case "$cmd" in
- dev) cmd_dev "$@" ;;
- prod) cmd_prod "$@" ;;
- lint) cmd_lint ;;
- test) cmd_test ;;
- check) cmd_check ;;
- completion) cmd_completion ;;
- help|-h|--help|"") usage ;;
- *) die "unknown command: $cmd (try: appctl help)" ;;
- esac
|