1
0

appctl 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. #!/usr/bin/env bash
  2. # appctl — convenience wrapper for the dev/prod compose split.
  3. #
  4. # Replaces the old Makefile. The dev compose chain layers
  5. # docker-compose.dev.yml on top of docker-compose.yml so source bind
  6. # mounts and the css-watcher sidecar only show up for `appctl dev …`.
  7. # Prod uses docker-compose.yml alone.
  8. set -euo pipefail
  9. # Resolve the repo root (parent of bin/) regardless of where the user
  10. # invokes us from, so cd into the right place even when called via the
  11. # top-level ./appctl symlink.
  12. SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
  13. REPO_ROOT="$(cd "$(dirname "$SCRIPT_PATH")/.." && pwd)"
  14. cd "$REPO_ROOT"
  15. # Exported so the css-watcher service can run as the host user via
  16. # `user: "${HOST_UID}:${HOST_GID}"` in docker-compose.dev.yml. Files
  17. # the watcher writes into bind-mounted host paths
  18. # (public/assets/css/app.css, public/assets/js/vendor/*) then land
  19. # with normal ownership.
  20. export HOST_UID="${HOST_UID:-$(id -u)}"
  21. export HOST_GID="${HOST_GID:-$(id -g)}"
  22. COMPOSE_PROD=(docker compose)
  23. COMPOSE_DEV=(docker compose -f docker-compose.yml -f docker-compose.dev.yml)
  24. usage() {
  25. cat <<'EOF'
  26. appctl — Sprint Planner dev/prod/test wrapper
  27. USAGE
  28. appctl <command> [subcommand]
  29. DEV
  30. appctl dev start start dev stack (app + css-watcher) in foreground
  31. appctl dev stop stop and remove dev containers
  32. appctl dev build rebuild dev images
  33. appctl dev shell bash into the running app container
  34. appctl dev logs tail logs from the dev stack
  35. PROD
  36. appctl prod start start prod stack detached
  37. appctl prod stop stop and remove prod containers
  38. appctl prod build rebuild prod images
  39. CHECKS (one-shot containers, no running stack required)
  40. appctl lint php -l on src/ + tests/
  41. appctl test phpunit
  42. appctl check lint + test (used by the /check Claude Code skill)
  43. OTHER
  44. appctl completion print the bash completion script to stdout
  45. appctl help show this message
  46. EOF
  47. }
  48. die() {
  49. printf 'appctl: %s\n' "$*" >&2
  50. exit 2
  51. }
  52. # --- bash completion auto-source on first run --------------------------
  53. #
  54. # We ship bin/appctl-completion.bash next to this script. On the very
  55. # first interactive invocation we offer to add a `source` line to the
  56. # user's ~/.bashrc — only once, gated by a marker file in $HOME so the
  57. # prompt never appears again, and only when stdin is a TTY (CI / piped
  58. # invocations are silent).
  59. maybe_offer_completion() {
  60. [[ -t 0 && -t 1 ]] || return 0
  61. [[ "${APPCTL_NO_COMPLETION_PROMPT:-}" != "1" ]] || return 0
  62. local marker="${HOME}/.config/appctl/completion-installed"
  63. [[ ! -e "$marker" ]] || return 0
  64. local completion_script="${REPO_ROOT}/bin/appctl-completion.bash"
  65. [[ -f "$completion_script" ]] || return 0
  66. local bashrc="${HOME}/.bashrc"
  67. if [[ -f "$bashrc" ]] && grep -Fq "appctl-completion.bash" "$bashrc"; then
  68. # Already wired up by a previous repo checkout; just write the
  69. # marker so we stop asking.
  70. mkdir -p "$(dirname "$marker")"
  71. : > "$marker"
  72. return 0
  73. fi
  74. printf '\n[appctl] bash completion is not yet installed.\n'
  75. printf ' Add `source %s` to %s? [y/N] ' \
  76. "$completion_script" "$bashrc"
  77. local reply
  78. read -r reply || reply=""
  79. case "$reply" in
  80. y|Y|yes|YES)
  81. mkdir -p "$(dirname "$marker")"
  82. {
  83. printf '\n# appctl bash completion (added by appctl on first run)\n'
  84. printf 'source %q\n' "$completion_script"
  85. } >> "$bashrc"
  86. : > "$marker"
  87. printf '[appctl] added. Open a new shell or `source %s` to activate.\n\n' "$bashrc"
  88. ;;
  89. *)
  90. mkdir -p "$(dirname "$marker")"
  91. : > "$marker"
  92. printf '[appctl] skipped. Re-run with APPCTL_NO_COMPLETION_PROMPT=1 to silence; delete %s to ask again.\n\n' "$marker"
  93. ;;
  94. esac
  95. }
  96. # --- subcommands -------------------------------------------------------
  97. cmd_dev() {
  98. local sub="${1:-}"
  99. case "$sub" in
  100. start) "${COMPOSE_DEV[@]}" up ;;
  101. stop) "${COMPOSE_DEV[@]}" down ;;
  102. build) "${COMPOSE_DEV[@]}" build ;;
  103. shell) "${COMPOSE_DEV[@]}" exec app bash ;;
  104. logs) "${COMPOSE_DEV[@]}" logs -f ;;
  105. ""|help|-h|--help) usage ;;
  106. *) die "unknown dev subcommand: $sub (try: start|stop|build|shell|logs)" ;;
  107. esac
  108. }
  109. cmd_prod() {
  110. local sub="${1:-}"
  111. case "$sub" in
  112. start) "${COMPOSE_PROD[@]}" up -d ;;
  113. stop) "${COMPOSE_PROD[@]}" down ;;
  114. build) "${COMPOSE_PROD[@]}" build ;;
  115. ""|help|-h|--help) usage ;;
  116. *) die "unknown prod subcommand: $sub (try: start|stop|build)" ;;
  117. esac
  118. }
  119. cmd_lint() {
  120. "${COMPOSE_DEV[@]}" --profile test run --rm tests \
  121. sh -c 'find src tests -name "*.php" -print0 | xargs -0 -n1 -P 4 php -l > /dev/null && echo "lint: OK"'
  122. }
  123. cmd_test() {
  124. "${COMPOSE_DEV[@]}" --profile test run --rm tests
  125. }
  126. cmd_check() {
  127. cmd_lint
  128. cmd_test
  129. }
  130. cmd_completion() {
  131. local completion_script="${REPO_ROOT}/bin/appctl-completion.bash"
  132. [[ -f "$completion_script" ]] || die "completion script missing: $completion_script"
  133. cat "$completion_script"
  134. }
  135. # --- dispatch ----------------------------------------------------------
  136. maybe_offer_completion
  137. cmd="${1:-help}"
  138. shift || true
  139. case "$cmd" in
  140. dev) cmd_dev "$@" ;;
  141. prod) cmd_prod "$@" ;;
  142. lint) cmd_lint ;;
  143. test) cmd_test ;;
  144. check) cmd_check ;;
  145. completion) cmd_completion ;;
  146. help|-h|--help|"") usage ;;
  147. *) die "unknown command: $cmd (try: appctl help)" ;;
  148. esac