1
0

ci.sh 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. #!/usr/bin/env bash
  2. # Local CI runner. Runs lint / static analysis / tests for both subprojects,
  3. # builds the ui asset bundle, and verifies docker compose images build.
  4. #
  5. # Designed for a host with Docker installed but no PHP/Composer/Node toolchain:
  6. # every PHP and Node command runs inside an ephemeral container.
  7. #
  8. # Usage:
  9. # ./scripts/ci.sh
  10. #
  11. # Env vars:
  12. # DB_DRIVERS space-separated list, default "sqlite mysql".
  13. # api test stage runs once per driver. mysql is skipped
  14. # with a warning if no MySQL is reachable on the host.
  15. set -euo pipefail
  16. REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
  17. cd "$REPO_ROOT"
  18. DB_DRIVERS="${DB_DRIVERS:-sqlite mysql}"
  19. PHP_IMAGE="composer:2"
  20. NODE_IMAGE="node:20-alpine"
  21. UID_GID="$(id -u):$(id -g)"
  22. banner() {
  23. printf '\n\033[1;36m==> %s\033[0m\n' "$*"
  24. }
  25. note() {
  26. printf '\033[1;33m[note]\033[0m %s\n' "$*"
  27. }
  28. ok() {
  29. printf '\033[1;32m%s\033[0m\n' "$*"
  30. }
  31. fail() {
  32. printf '\033[1;31m[fail]\033[0m %s\n' "$*" >&2
  33. exit 1
  34. }
  35. # Run a command inside a one-off PHP/Composer container, mounted at /app.
  36. # Args: <subproject-dir> <env-args> <command...>
  37. run_php() {
  38. local dir="$1"; shift
  39. docker run --rm \
  40. -u "$UID_GID" \
  41. -v "$REPO_ROOT/$dir":/app \
  42. -v "$HOME/.composer-cache":/tmp \
  43. -w /app \
  44. -e COMPOSER_HOME=/tmp/composer \
  45. -e COMPOSER_CACHE_DIR=/tmp/composer-cache \
  46. -e XDG_CONFIG_HOME=/tmp \
  47. "$@"
  48. }
  49. # Run a command inside a one-off Node container.
  50. run_node() {
  51. local dir="$1"; shift
  52. docker run --rm \
  53. -u "$UID_GID" \
  54. -v "$REPO_ROOT/$dir":/app \
  55. -w /app \
  56. -e HOME=/tmp \
  57. "$NODE_IMAGE" "$@"
  58. }
  59. mkdir -p "$HOME/.composer-cache"
  60. # ---------- 1. api ----------
  61. banner "api: composer install"
  62. run_php api "$PHP_IMAGE" composer install --no-interaction --prefer-dist
  63. banner "api: phpstan"
  64. run_php api "$PHP_IMAGE" composer stan
  65. banner "api: php-cs-fixer (dry-run)"
  66. run_php api "$PHP_IMAGE" composer cs
  67. # Dependency vulnerability scan (SPEC §M14.9). Fails on any advisory.
  68. # Policy: when this fails an admin reviews and either patches or
  69. # accepts with a documented exception (see doc/security.md).
  70. banner "api: composer audit"
  71. run_php api "$PHP_IMAGE" composer audit --no-dev
  72. for driver in $DB_DRIVERS; do
  73. if [ "$driver" = "mysql" ]; then
  74. # No MySQL reachable from the host in M01; skip gracefully. Once
  75. # later milestones add real DB-touching tests, replace this with a
  76. # proper reachability probe.
  77. note "api: skipping phpunit for driver=mysql (no MySQL configured for local CI in M01)"
  78. continue
  79. fi
  80. banner "api: phpunit (DB_DRIVER=$driver)"
  81. run_php api -e "DB_DRIVER=$driver" "$PHP_IMAGE" composer test
  82. done
  83. # ---------- 2. ui ----------
  84. banner "ui: composer install"
  85. run_php ui "$PHP_IMAGE" composer install --no-interaction --prefer-dist
  86. banner "ui: phpstan"
  87. run_php ui "$PHP_IMAGE" composer stan
  88. banner "ui: php-cs-fixer (dry-run)"
  89. run_php ui "$PHP_IMAGE" composer cs
  90. banner "ui: composer audit"
  91. run_php ui "$PHP_IMAGE" composer audit --no-dev
  92. banner "ui: phpunit"
  93. run_php ui "$PHP_IMAGE" composer test
  94. # ---------- 3. ui frontend build ----------
  95. banner "ui: npm ci"
  96. if [ -f ui/package-lock.json ]; then
  97. run_node ui npm ci --no-audit --no-fund
  98. else
  99. note "ui/package-lock.json not present — using npm install to bootstrap"
  100. run_node ui npm install --no-audit --no-fund
  101. fi
  102. banner "ui: npm run build"
  103. run_node ui npm run build
  104. # Production-deps vuln scan (SPEC §M14.9). `--audit-level=high` fails
  105. # only on high/critical so noisy moderate advisories don't block CI;
  106. # they're expected to be reviewed during a routine bump pass.
  107. banner "ui: npm audit (production deps, high+critical)"
  108. run_node ui npm audit --omit=dev --audit-level=high
  109. if [ ! -f ui/public/assets/app.css ]; then
  110. fail "ui/public/assets/app.css was not produced by the tailwind build"
  111. fi
  112. # ---------- 4. docker compose build ----------
  113. banner "docker compose build"
  114. docker compose build
  115. ok "CI OK"