name: CI on: push: branches: [main] pull_request: concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: APP_ENV: testing APP_KEY: base64:NXm/6jIyFcDGHoMKGc5QZuSaq0dRZFYPg1Isuy1fNvE= APP_LOCALE: pt_BR APP_FALLBACK_LOCALE: pt_BR APP_TIMEZONE: America/Sao_Paulo BCRYPT_ROUNDS: 4 CACHE_STORE: database DB_CONNECTION: pgsql DB_HOST: 127.0.0.1 DB_PORT: 5432 DB_DATABASE: amare_test DB_USERNAME: amare DB_PASSWORD: secret QUEUE_CONNECTION: database SESSION_DRIVER: database jobs: static: name: static runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: cp .env.example .env - uses: shivammathur/setup-php@v2 with: php-version: "8.4" extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_pgsql, bcmath, intl, sodium, gd coverage: none - uses: actions/cache@v5 with: path: ~/.composer/cache/files key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }} restore-keys: composer-${{ runner.os }}- - run: composer validate --strict - run: composer install --no-interaction --prefer-dist - run: composer pint:check - run: composer phpstan - run: composer audit # audit-level=high: package.json has no runtime `dependencies` today (npm # itself reports prod:1, 0 vulnerabilities), so this is currently a # vacuous forward guard rather than an active protection. `high` is # chosen deliberately over `low`/`moderate` so that once a real runtime # JS dependency is added, the gate flags exploitable issues without # becoming noisy on every transitive dev-only advisory. - run: npm audit --omit=dev --audit-level=high unit: name: unit runs-on: ubuntu-latest # Postgres is required here (not just in `feature`) because the # Domain/Application coverage gate below runs the Feature suite too: the # App\Application\Queries\Marketing\* classes are only exercised through # Feature (HTTP) tests today, so a Unit-only coverage run would undercount # them well under the 80% threshold. services: postgres: image: postgres:17 env: POSTGRES_DB: amare_test POSTGRES_USER: amare POSTGRES_PASSWORD: secret ports: - 5432:5432 options: >- --health-cmd "pg_isready -U amare -d amare_test" --health-interval 5s --health-timeout 5s --health-retries 10 steps: - uses: actions/checkout@v5 - run: cp .env.example .env - uses: shivammathur/setup-php@v2 with: php-version: "8.4" extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_pgsql, bcmath, intl, sodium, gd coverage: pcov - uses: actions/cache@v5 with: path: ~/.composer/cache/files key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }} restore-keys: composer-${{ runner.os }}- - uses: actions/cache@v5 with: path: ~/.npm key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} restore-keys: npm-${{ runner.os }}- - run: composer install --no-interaction --prefer-dist - run: npm ci - run: npm run build - run: composer test:unit - run: php artisan migrate --force # Domain/Application coverage gate (SPEC.md L2351, 80% minimum). Scoped # via phpunit.coverage.xml rather than editing the project-wide # phpunit.xml block, which stays covering all of app/ for # every other test/coverage invocation. app/Domain currently holds only # the DomainModule placeholder (zero executable lines), so in practice # this gates app/Application until Domain gains real logic. - run: composer test:coverage feature: name: feature runs-on: ubuntu-latest services: postgres: image: postgres:17 env: POSTGRES_DB: amare_test POSTGRES_USER: amare POSTGRES_PASSWORD: secret ports: - 5432:5432 options: >- --health-cmd "pg_isready -U amare -d amare_test" --health-interval 5s --health-timeout 5s --health-retries 10 steps: - uses: actions/checkout@v5 - run: cp .env.example .env - uses: shivammathur/setup-php@v2 with: php-version: "8.4" extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_pgsql, bcmath, intl, sodium, gd coverage: none - uses: actions/cache@v5 with: path: ~/.composer/cache/files key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }} restore-keys: composer-${{ runner.os }}- - uses: actions/cache@v5 with: path: ~/.npm key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} restore-keys: npm-${{ runner.os }}- - run: composer install --no-interaction --prefer-dist - run: npm ci - run: npm run build - run: php artisan migrate --force - run: composer test:feature browser: name: browser runs-on: ubuntu-latest services: postgres: image: postgres:17 env: POSTGRES_DB: amare_test POSTGRES_USER: amare POSTGRES_PASSWORD: secret ports: - 5432:5432 options: >- --health-cmd "pg_isready -U amare -d amare_test" --health-interval 5s --health-timeout 5s --health-retries 10 steps: - uses: actions/checkout@v5 - run: cp .env.example .env - uses: shivammathur/setup-php@v2 with: php-version: "8.4" extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_pgsql, bcmath, intl, sodium, gd coverage: none - uses: actions/cache@v5 with: path: ~/.composer/cache/files key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }} restore-keys: composer-${{ runner.os }}- - uses: actions/cache@v5 with: path: ~/.npm key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} restore-keys: npm-${{ runner.os }}- - run: composer install --no-interaction --prefer-dist - run: npm ci - run: npm run build - run: npx playwright install chromium --with-deps - run: php artisan migrate --force - run: php artisan storage:link - name: Build application image run: docker build -t amare-app:ci . - name: Run browser tests against FrankenPHP container run: | docker run -d --name amare-web \ -e APP_ENV=testing \ -e APP_KEY="${APP_KEY}" \ -e APP_URL=http://127.0.0.1:8000 \ -e APP_LOCALE=pt_BR \ -e APP_FALLBACK_LOCALE=pt_BR \ -e APP_TIMEZONE=America/Sao_Paulo \ -e DB_CONNECTION=pgsql \ -e DB_HOST=host.docker.internal \ -e DB_PORT=5432 \ -e DB_DATABASE=amare_test \ -e DB_USERNAME=amare \ -e DB_PASSWORD=secret \ -e SESSION_DRIVER=database \ -e CACHE_STORE=database \ -e QUEUE_CONNECTION=database \ --add-host=host.docker.internal:host-gateway \ -v "${GITHUB_WORKSPACE}/storage/app/public:/app/storage/app/public" \ -p 8000:8000 \ amare-app:ci for i in $(seq 1 30); do if curl -fsS http://127.0.0.1:8000/up; then break fi sleep 2 done curl -fsS http://127.0.0.1:8000/up ./vendor/bin/pest --testsuite=Browser - name: Collect failure diagnostics if: failure() run: | mkdir -p artifacts/browser docker logs amare-web > artifacts/browser/container.log 2>&1 || true cp -R storage/logs artifacts/browser/app-logs 2>/dev/null || true - name: Upload browser failure artifacts if: failure() uses: actions/upload-artifact@v4 with: name: browser-failure-artifacts path: artifacts/browser if-no-files-found: ignore container: name: container runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: Build production image run: docker build -t amare-app:ci . - name: Verify container healthcheck and storage link run: | docker run -d --name amare-health \ -e APP_ENV=production \ -e APP_KEY="${{ env.APP_KEY }}" \ -e APP_URL=http://127.0.0.1:8000 \ -e APP_DEBUG=false \ -e DB_CONNECTION=pgsql \ -e DB_HOST=127.0.0.1 \ -e DB_PORT=5432 \ -e DB_DATABASE=amare \ -e DB_USERNAME=amare \ -e DB_PASSWORD=secret \ -p 8000:8000 \ amare-app:ci for i in $(seq 1 30); do if curl -fsS http://127.0.0.1:8000/up; then docker exec amare-health test -L /app/public/storage exit 0 fi sleep 2 done docker logs amare-health exit 1