name: tests on: push: branches: - develop - main paths-ignore: # Files no code reads and no test covers. Deliberately NOT listed: # CHANGELOG.md, which ReleaseNotes parses and ReleaseNotesTest # covers, and docs/, whose only two tracked files are served by # ApiDocsController and OpenApiController. A malformed edit to # either is exactly the thing that must not skip the suite. # # Repeated verbatim under pull_request: GitHub Actions does not # support YAML anchors. - 'README.md' - 'CONTRIBUTING.md' - 'SECURITY.md' - 'LICENSING.md' - 'CLA-ENTITY.md' - 'CLA-INDIVIDUAL.md' - 'INSTALL.md' - 'UPDATE.md' - 'DOCKER.md' - 'MIGRATING-FROM-V1.md' - 'docker/production/dockerhub-overview.md' - '.github/screenshots/**' pull_request: branches: - develop - main paths-ignore: - 'README.md' - 'CONTRIBUTING.md' - 'SECURITY.md' - 'LICENSING.md' - 'CLA-ENTITY.md' - 'CLA-INDIVIDUAL.md' - 'INSTALL.md' - 'UPDATE.md' - 'DOCKER.md' - 'MIGRATING-FROM-V1.md' - 'docker/production/dockerhub-overview.md' - '.github/screenshots/**' # A second push supersedes the first — the later run covers a superset of # what the earlier one was checking, so finishing both buys nothing and # costs a runner. # # Keyed on the ref, so `main` and a branch never cancel each other. A push # to a branch that also has a pull request open produces two events with # two different refs, which is why they do not fight either. # # The tradeoff worth naming: on `main` this means an intermediate commit # can end up with no run of its own when two pushes land together. That is # accepted here — what is being verified is the state of the branch, and # the run that survives is the one that includes both commits. If a commit # ever needs its own green tick (a bisect, a release audit), push it alone. concurrency: group: tests-${{ github.ref }} cancel-in-progress: true jobs: ci: runs-on: ubuntu-latest # This job has no MySQL/db service — it's SQLite-only (see # phpunit.xml) — but .env.example's DB_CONNECTION=mysql/DB_HOST=db is # for the local docker-compose stack. Bare artisan calls that run # outside phpunit.xml's env (composer's package:discover, # key:generate) would otherwise try to reach a "db" host that # doesn't exist here. Same story for CACHE_STORE: every process # boot reads a mail-config cache entry (PlatformServiceProvider:: # boot() -> MailConfigApplier::apply()), which needs a store that # works without a migrated schema this early. env: CACHE_STORE: array DB_CONNECTION: sqlite # config/database.php's sqlite connection reuses DB_DATABASE for # the file path — .env.example's DB_DATABASE=projectsend (the # MySQL database name) would otherwise make sqlite look for a # file literally named "projectsend". DB_DATABASE: database/database.sqlite steps: - name: Checkout uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: 8.4 tools: composer:v2 coverage: xdebug - name: Setup Node uses: actions/setup-node@v4 with: node-version: '22' cache: 'npm' # community-modules is the community edition's companion package, # resolved from its public GitHub repository (the vcs entry in # composer.json). A plain clone installs it with no local packages/ # checkout — cloud-modules is not required at all. COMPOSER_AUTH just # hands composer the runner's token so the GitHub API calls it makes # to fetch the package are not subject to the anonymous rate limit. # composer install's post-autoload-dump script boots the app # (package:discover), which needs the sqlite file to already exist # even before .env is copied — config/database.php defaults to it. - name: Create SQLite Database run: touch database/database.sqlite - name: Install PHP Dependencies env: COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GITHUB_TOKEN }}"}}' run: composer install --no-interaction --prefer-dist --optimize-autoloader - name: Install Node Dependencies run: npm ci # tsconfig.json maps the "ziggy-js" import to vendor/tightenco/ziggy, # so this needs PHP deps installed first. - name: Typecheck Frontend run: npm run types - name: Build Assets run: npm run build - name: Copy Environment File run: cp .env.example .env - name: Generate Application Key run: php artisan key:generate - name: Static Analysis run: ./vendor/bin/phpstan analyse --no-progress # `--parallel` rather than a shorter suite. One process took 191s of # this job's 4m30s; the same 1763 tests across the runner's cores # take about a third of that, with nothing skipped. paratest is # already a dev dependency (via Pest), so this needs no new install. # # `:memory:` explicitly: parallel testing gives each process its own # database, and an in-memory one per process is what the suite is # verified against locally. The job-level DB_DATABASE above is a file # path, which parallel workers would have to create and migrate # individually — a difference in behaviour with nothing to gain. - name: Tests run: ./vendor/bin/pest --parallel env: DB_DATABASE: ':memory:'