Skip to content

158 polish visual consistency and mobile browsing #78

158 polish visual consistency and mobile browsing

158 polish visual consistency and mobile browsing #78

Workflow file for this run

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."