Refactor: Standardize country names in filters and sightings #74
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: | |
| pull_request: | |
| branches: [main] | |
| push: | |
| branches: [main] | |
| # Cancel outdated pull request runs to save runner time and energy. Runs on | |
| # main are never cancelled, so a deploy is not stopped halfway; a newer | |
| # merge waits for the running one instead. | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| # Apache serves this folder at /project2 (see docs/ci-cd.md). | |
| WEB_DIR: /var/www/html/project2 | |
| # The backend runs from here, outside the runner's work folder, which is | |
| # wiped by the next checkout. | |
| APP_DIR: /srv/ufound-it | |
| SERVICE: ufound-it-backend | |
| jobs: | |
| # The job keeps its name since branch protection requires a check named | |
| # "checks". Pull requests only run this job. | |
| checks: | |
| runs-on: self-hosted | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # No `cache: npm`. The runner is our own VM, so npm's download cache | |
| # (~/.npm) is still on disk from the last run. Saving it to GitHub's | |
| # cache too would only upload and download it again every run. | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| # npm ci installs exactly what package-lock.json says, so CI matches | |
| # what we run locally. --prefer-offline takes packages from ~/.npm | |
| # without asking the registry first. The lock file's hashes still | |
| # check them. | |
| - run: npm ci --prefer-offline | |
| # Checkout deletes ignored files and npm ci deletes node_modules, so | |
| # the caches live in the runner's home folder instead. They compare | |
| # file contents, since checkout changes modification times, and only | |
| # changed files are checked again. | |
| - name: Lint | |
| run: npm run lint --workspaces -- --cache --cache-strategy content --cache-location ~/.cache/ufound-it-ci/eslint/ | |
| - name: Check formatting | |
| run: npm run format:check -- --cache --cache-strategy content --cache-location ~/.cache/ufound-it-ci/prettier | |
| - run: npm test | |
| - run: npm run build | |
| # Chromium only, the one browser the tests use. It stays in the | |
| # runner's ~/.cache/ms-playwright between runs, so it is only | |
| # downloaded again when Playwright is upgraded. Its system libraries | |
| # are installed once by hand, since the runner has no sudo (see | |
| # docs/ci-cd.md). | |
| - name: Install Playwright's Chromium | |
| run: npx -w frontend playwright install chromium | |
| - name: End-to-end tests | |
| id: e2e | |
| run: npm run test:e2e | |
| # The report shows each failed test with a screenshot, and a trace | |
| # for retried ones. Only uploaded when the e2e tests fail, and deleted | |
| # after a week. upload-artifact@v4 does not work on GitHub Enterprise | |
| # Server, which git.ntnu.no is, so this uses v3. | |
| - name: Upload the e2e report | |
| if: failure() && steps.e2e.outcome == 'failure' | |
| uses: actions/upload-artifact@v3 | |
| with: | |
| name: playwright-report | |
| path: frontend/playwright-report/ | |
| retention-days: 7 | |
| # Hands the build that passed every check to the deploy job, so it is | |
| # not installed and built a second time. Only on main, since pull | |
| # requests are not deployed, and deleted after a day. | |
| - name: Upload the build for deploy | |
| if: github.event_name == 'push' | |
| uses: actions/upload-artifact@v3 | |
| with: | |
| name: build | |
| path: | | |
| frontend/dist/ | |
| backend/dist/ | |
| package.json | |
| package-lock.json | |
| frontend/package.json | |
| backend/package.json | |
| retention-days: 1 | |
| # A separate job so it can belong to the "production" environment, which | |
| # makes GitHub list each deploy, which commit is live, and the site URL. | |
| # It deploys the files that `checks` built and tested, so it needs no | |
| # checkout, install or build of its own. | |
| deploy: | |
| needs: checks | |
| if: github.event_name == 'push' | |
| runs-on: self-hosted | |
| environment: | |
| name: production | |
| url: http://it2810-25.idi.ntnu.no/project2/ | |
| steps: | |
| # The runner's temp folder is emptied before every job, so no files | |
| # from an earlier build can end up on the site. | |
| - uses: actions/download-artifact@v3 | |
| with: | |
| name: build | |
| path: ${{ runner.temp }}/build | |
| # Still needed: for npm ci in the app folder, and the service runs this | |
| # Node.js binary. | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - name: Deploy frontend | |
| working-directory: ${{ runner.temp }}/build | |
| run: | | |
| find "$WEB_DIR" -mindepth 1 -delete | |
| cp -r frontend/dist/. "$WEB_DIR"/ | |
| - name: Deploy backend | |
| working-directory: ${{ runner.temp }}/build | |
| run: | | |
| # npm ci needs the workspace layout, so both package.json files are | |
| # copied even though only the backend runs here. backend/.env is | |
| # created once by hand on the VM and is left alone. | |
| mkdir -p "$APP_DIR/backend" "$APP_DIR/frontend" | |
| cp package.json package-lock.json "$APP_DIR"/ | |
| cp backend/package.json "$APP_DIR/backend/" | |
| cp frontend/package.json "$APP_DIR/frontend/" | |
| rm -rf "$APP_DIR/backend/dist" | |
| cp -r backend/dist "$APP_DIR/backend/" | |
| (cd "$APP_DIR" && npm ci --prefer-offline --omit=dev --workspace backend) | |
| # Node is only installed in the runner's tool cache, so the service | |
| # gets its own copy of the same version CI tested with. Renaming | |
| # over the old binary works while the service is still running it. | |
| cp "$(command -v node)" "$APP_DIR/node.new" | |
| mv -f "$APP_DIR/node.new" "$APP_DIR/node" | |
| sudo -n systemctl restart "$SERVICE" | |
| - name: Check the deployed site | |
| run: | | |
| graphql() { | |
| curl -fsS http://localhost/project2/graphql \ | |
| -H 'content-type: application/json' \ | |
| --data "{\"query\":\"$1\"}" | |
| } | |
| # The backend needs a moment to start, so retry before failing. | |
| for attempt in 1 2 3 4 5 6 7 8 9 10; do | |
| if graphql '{ health }' | grep -q '"ok"'; then | |
| break | |
| fi | |
| if [ "$attempt" = 10 ]; then | |
| echo "The backend did not answer through Apache." >&2 | |
| sudo -n systemctl status "$SERVICE" --no-pager || true | |
| exit 1 | |
| fi | |
| sleep 2 | |
| done | |
| # health does not touch the database, so a wrong password in .env | |
| # or a stopped MySQL only shows up in a real query. | |
| result=$(graphql '{ sightings(first: 1) { totalCount } }') | |
| if ! echo "$result" | grep -q '"totalCount"'; then | |
| echo "The backend is up but cannot read sightings: $result" >&2 | |
| exit 1 | |
| fi | |
| curl -fsS -o /dev/null http://localhost/project2/ | |
| echo "Deployed and responding." |