feat: #5 add user data schema and FTS triggers #39
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI/CD | |
| on: | |
| # No branch filter here on purpose. We stack PRs (a branch PR based on | |
| # another branch), and a `branches: [main]` filter silently skips CI for | |
| # every stacked PR. That is how this was found: #34 targets | |
| # feat/1-repo-tooling and got no run at all. | |
| pull_request: | |
| push: | |
| branches: [main] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| quality: | |
| name: Format, lint, typecheck, test, build | |
| # Deliberately unpinned beyond the platform: quality is a pure function of | |
| # the checked-out tree, so it can run on any self-hosted runner we add | |
| # later without changing this file. Only `deploy` is machine-specific. | |
| runs-on: [self-hosted, linux, x64] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: 10.33.0 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| cache: pnpm | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Check formatting | |
| run: pnpm format:check | |
| - name: Lint | |
| run: pnpm lint | |
| - name: Typecheck | |
| run: pnpm typecheck | |
| - name: Test | |
| run: pnpm test | |
| - name: Build | |
| run: pnpm build | |
| # Bundle budget, one of our bærekraft measures. The budget is deliberately | |
| # loose today and tightens as the app grows; the point is that CI fails | |
| # before a dependency quietly doubles our payload. | |
| - name: Enforce client bundle budget | |
| run: | | |
| set -euo pipefail | |
| MAX_BYTES=512000 | |
| shopt -s nullglob | |
| found=0 | |
| for f in client/dist/assets/*.js; do | |
| found=1 | |
| size=$(stat -c%s "$f") | |
| echo "$f = ${size} bytes (budget ${MAX_BYTES})" | |
| if [ "$size" -gt "$MAX_BYTES" ]; then | |
| echo "::error::Bundle budget exceeded: $f is ${size} bytes (max ${MAX_BYTES})." | |
| exit 1 | |
| fi | |
| done | |
| if [ "$found" -eq 0 ]; then | |
| echo "::error::No client JS bundle found in client/dist/assets/." | |
| exit 1 | |
| fi | |
| deploy: | |
| name: Deploy to the group VM | |
| needs: quality | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| # Pinned to the `project2` label on purpose. This job writes to | |
| # /var/www/html/project2 and restarts crate-api, both of which only exist | |
| # on it2810-19. If a second runner is ever registered to this repo from a | |
| # different machine, this must not land there. | |
| runs-on: [self-hosted, linux, x64, project2] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: 10.33.0 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| cache: pnpm | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build client | |
| run: pnpm build | |
| - name: Publish to Apache | |
| run: | | |
| set -euo pipefail | |
| find /var/www/html/project2 -mindepth 1 -delete | |
| cp -r client/dist/. /var/www/html/project2/ | |
| chmod -R a+rX /var/www/html/project2 | |
| curl -fsS -o /dev/null -w 'GET /project2/ : %{http_code}\n' http://localhost/project2/ | |
| # Restarting the GraphQL API is M2 work. Until `server/` exists in this | |
| # repository there is nothing to deploy, and restarting the unit would | |
| # just bring back whatever was last installed on the VM by hand. | |
| # | |
| # Two things here are deliberately not what the first version did: | |
| # | |
| # - The unit check is `[ -f ... ]`, not `systemctl list-unit-files | grep`. | |
| # `grep -q` exits on the first match and closes the pipe, `systemctl` | |
| # gets SIGPIPE and dies, and with `pipefail` the whole pipeline reports | |
| # failure. So the `if` took the else branch and skipped a service that | |
| # was definitely there. The log showed "Failed to print table: Broken | |
| # pipe". Reported by rachelks. | |
| # | |
| # - The restart needs `sudo`, and the runner has no TTY. That needs a | |
| # NOPASSWD rule scoped to exactly these commands. Without it the step | |
| # dies with "sudo: A terminal is required to authenticate". | |
| - name: Restart the GraphQL API if the server is deployed | |
| run: | | |
| set -euo pipefail | |
| if [ ! -f /etc/systemd/system/crate-api.service ]; then | |
| echo "crate-api.service is not installed. Skipping API restart." | |
| exit 0 | |
| fi | |
| if [ ! -d server ]; then | |
| echo "No server/ in this repository yet (arrives in M2). Skipping API restart." | |
| exit 0 | |
| fi | |
| if [ ! -f server/dist/index.js ]; then | |
| echo "::error::server/ exists but server/dist/index.js does not. Was 'pnpm build' run?" | |
| exit 1 | |
| fi | |
| sudo /usr/bin/systemctl restart crate-api | |
| # `Type=simple` makes systemd report active as soon as the process is | |
| # spawned, well before Node has bound the port. Measured on the VM, | |
| # the API answers again after about 1850 ms, so a fixed `sleep 2` | |
| # leaves a margin of roughly 120 ms. That was enough to return 503 | |
| # through the Apache proxy on at least one run. Poll instead. | |
| printf 'Waiting for the API' | |
| for _ in $(seq 1 30); do | |
| code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 2 \ | |
| -X POST http://localhost/graphql \ | |
| -H 'content-type: application/json' \ | |
| -d '{"query":"{ __typename }"}' 2>/dev/null || echo 000) | |
| if [ "$code" = "200" ]; then | |
| echo | |
| echo "POST /graphql : 200" | |
| exit 0 | |
| fi | |
| printf '.' | |
| sleep 1 | |
| done | |
| echo | |
| echo "::error::crate-api restarted but /graphql never returned 200 (last code ${code})." | |
| exit 1 |