# CluPilot CI — runs on Gitea Actions (GitHub-Actions syntax, own runner). # # Deliberately not mirrored to GitHub: this repository describes how our # infrastructure is provisioned, and the same workflow runs at home. If we ever # move, this file goes along unchanged. name: tests on: push: branches: [main, 'feat/**'] pull_request: jobs: pest: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.2.2 - name: Set up PHP # Pinned: the floating v2 tag now requires node24, which the runner # matching Gitea 1.20 cannot execute. 2.34.1 is the newest that still # declares node20. uses: shivammathur/setup-php@2.34.1 with: php-version: '8.4' extensions: mbstring, pdo_sqlite, sodium, redis, bcmath, gd, zip coverage: none - name: Install PHP dependencies # Source clones, not dist archives: dist downloads go through GitHub's # API, which throttles anonymous callers and left a half-installed # vendor/ behind — the tests then failed with 500s that had nothing to # do with the code. Cloning is slower and does not need anyone's quota. # Swap back to --prefer-dist once a GitHub token is configured. run: composer install --no-interaction --prefer-source --no-progress - name: Prepare environment run: | cp .env.example .env php artisan key:generate - name: Tests # phpunit.xml pins its own sqlite/array drivers, so no services needed. run: ./vendor/bin/pest --colors=always assets: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.2.2 - uses: actions/setup-node@v4.1.0 with: node-version: '22' - name: Install JS dependencies run: npm ci --no-fund --no-audit # A build failure here is what used to surface as "the design is broken" # only after deploying — catch it before it ships. - name: Build assets run: npm run build release: # Only a green run produces the tag the console offers as an update. needs: [pest, assets] if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.2.2 with: fetch-depth: 0 - name: Tag this commit as tested run: | tag="tested-$(date -u +%Y%m%d-%H%M)-$(git rev-parse --short HEAD)" git tag "$tag" git push origin "$tag" # A release is cut by editing VERSION and merging it — nothing else. The # tag is created here, only after the suite is green, and only if it does # not already exist. Never moved: servers are pinned to these, and a tag # that changes underneath them means two machines claiming one version. - name: Tag the release when VERSION changed run: | version="$(tr -d ' \n\r' < VERSION)" # Anchored, because a `case` glob does not anchor: `[0-9]*.[0-9]*` # happily accepts 1x.2y.3garbage and would tag it. printf '%s' "$version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$' \ || { echo "VERSION is not MAJOR.MINOR.PATCH: '$version'" >&2; exit 1; } # Only the commit that RAISED the version is the release. Without # this, any later green push finds the tag missing and claims it — # and the tag would then name code the bump never described. Runs # overlap and finish out of order; that is enough for it to happen. previous="$(git show 'HEAD^:VERSION' 2>/dev/null | tr -d ' \n\r' || true)" if [ "$previous" = "$version" ]; then echo "::notice::VERSION is unchanged ($version) — nothing to release." exit 0 fi tag="v${version}" git fetch --tags --force origin if existing="$(git rev-parse -q --verify "refs/tags/${tag}^{commit}")"; then # Already released. The invariant is that the tag points at the # commit VERSION was raised in — if a later commit still carries # that number, that is fine, but the tag must not be re-pointed. if [ "$existing" != "$(git rev-parse HEAD)" ]; then echo "::notice::${tag} already exists at ${existing}; leaving it alone." fi exit 0 fi git config user.name "CluPilot CI" git config user.email "ci@clupilot.local" git tag -a "$tag" -m "CluPilot ${version}" git push origin "$tag" echo "::notice::Released ${tag}"