| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- #!/usr/bin/env bash
- # Local CI runner. Runs lint / static analysis / tests for both subprojects,
- # builds the ui asset bundle, and verifies docker compose images build.
- #
- # Designed for a host with Docker installed but no PHP/Composer/Node toolchain:
- # every PHP and Node command runs inside an ephemeral container.
- #
- # Usage:
- # ./scripts/ci.sh
- #
- # Env vars:
- # DB_DRIVERS space-separated list, default "sqlite mysql".
- # api test stage runs once per driver. mysql is skipped
- # with a warning if no MySQL is reachable on the host.
- set -euo pipefail
- REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
- cd "$REPO_ROOT"
- DB_DRIVERS="${DB_DRIVERS:-sqlite mysql}"
- PHP_IMAGE="composer:2"
- NODE_IMAGE="node:20-alpine"
- UID_GID="$(id -u):$(id -g)"
- banner() {
- printf '\n\033[1;36m==> %s\033[0m\n' "$*"
- }
- note() {
- printf '\033[1;33m[note]\033[0m %s\n' "$*"
- }
- ok() {
- printf '\033[1;32m%s\033[0m\n' "$*"
- }
- fail() {
- printf '\033[1;31m[fail]\033[0m %s\n' "$*" >&2
- exit 1
- }
- # Run a command inside a one-off PHP/Composer container, mounted at /app.
- # Args: <subproject-dir> <env-args> <command...>
- run_php() {
- local dir="$1"; shift
- docker run --rm \
- -u "$UID_GID" \
- -v "$REPO_ROOT/$dir":/app \
- -v "$HOME/.composer-cache":/tmp \
- -w /app \
- -e COMPOSER_HOME=/tmp/composer \
- -e COMPOSER_CACHE_DIR=/tmp/composer-cache \
- -e XDG_CONFIG_HOME=/tmp \
- "$@"
- }
- # Run a command inside a one-off Node container.
- run_node() {
- local dir="$1"; shift
- docker run --rm \
- -u "$UID_GID" \
- -v "$REPO_ROOT/$dir":/app \
- -w /app \
- -e HOME=/tmp \
- "$NODE_IMAGE" "$@"
- }
- mkdir -p "$HOME/.composer-cache"
- # ---------- 1. api ----------
- banner "api: composer install"
- run_php api "$PHP_IMAGE" composer install --no-interaction --prefer-dist
- banner "api: phpstan"
- run_php api "$PHP_IMAGE" composer stan
- banner "api: php-cs-fixer (dry-run)"
- run_php api "$PHP_IMAGE" composer cs
- # Dependency vulnerability scan (SPEC §M14.9). Fails on any advisory.
- # Policy: when this fails an admin reviews and either patches or
- # accepts with a documented exception (see doc/security.md).
- banner "api: composer audit"
- run_php api "$PHP_IMAGE" composer audit --no-dev
- for driver in $DB_DRIVERS; do
- if [ "$driver" = "mysql" ]; then
- # No MySQL reachable from the host in M01; skip gracefully. Once
- # later milestones add real DB-touching tests, replace this with a
- # proper reachability probe.
- note "api: skipping phpunit for driver=mysql (no MySQL configured for local CI in M01)"
- continue
- fi
- banner "api: phpunit (DB_DRIVER=$driver)"
- run_php api -e "DB_DRIVER=$driver" "$PHP_IMAGE" composer test
- done
- # ---------- 2. ui ----------
- banner "ui: composer install"
- run_php ui "$PHP_IMAGE" composer install --no-interaction --prefer-dist
- banner "ui: phpstan"
- run_php ui "$PHP_IMAGE" composer stan
- banner "ui: php-cs-fixer (dry-run)"
- run_php ui "$PHP_IMAGE" composer cs
- banner "ui: composer audit"
- run_php ui "$PHP_IMAGE" composer audit --no-dev
- banner "ui: phpunit"
- run_php ui "$PHP_IMAGE" composer test
- # ---------- 3. ui frontend build ----------
- banner "ui: npm ci"
- if [ -f ui/package-lock.json ]; then
- run_node ui npm ci --no-audit --no-fund
- else
- note "ui/package-lock.json not present — using npm install to bootstrap"
- run_node ui npm install --no-audit --no-fund
- fi
- banner "ui: npm run build"
- run_node ui npm run build
- # Production-deps vuln scan (SPEC §M14.9). `--audit-level=high` fails
- # only on high/critical so noisy moderate advisories don't block CI;
- # they're expected to be reviewed during a routine bump pass.
- banner "ui: npm audit (production deps, high+critical)"
- run_node ui npm audit --omit=dev --audit-level=high
- if [ ! -f ui/public/assets/app.css ]; then
- fail "ui/public/assets/app.css was not produced by the tailwind build"
- fi
- # ---------- 4. docker compose build ----------
- banner "docker compose build"
- docker compose build
- ok "CI OK"
|