255 lines
6.7 KiB
YAML
255 lines
6.7 KiB
YAML
name: CI
|
|
|
|
on:
|
|
pull_request:
|
|
paths-ignore:
|
|
- '**/*.md'
|
|
- 'docs/**'
|
|
- '.github/ISSUE_TEMPLATE/**'
|
|
- '.github/pull_request_template.md'
|
|
- 'AGENTS.md'
|
|
- 'CODE_OF_CONDUCT.md'
|
|
- 'CONTRIBUTING.md'
|
|
- 'LICENSE'
|
|
- 'SECURITY.md'
|
|
- 'SUPPORT.md'
|
|
push:
|
|
branches:
|
|
- main
|
|
paths-ignore:
|
|
- '**/*.md'
|
|
- 'docs/**'
|
|
- '.github/ISSUE_TEMPLATE/**'
|
|
- '.github/pull_request_template.md'
|
|
- 'AGENTS.md'
|
|
- 'CODE_OF_CONDUCT.md'
|
|
- 'CONTRIBUTING.md'
|
|
- 'LICENSE'
|
|
- 'SECURITY.md'
|
|
- 'SUPPORT.md'
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
env:
|
|
COMPOSE_FILES: -f docker-compose.yml -f docker-compose.build.yml
|
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
|
|
jobs:
|
|
web:
|
|
name: Web Checks
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Set up Bun
|
|
uses: oven-sh/setup-bun@v2
|
|
|
|
- name: Install dependencies
|
|
run: bun install --frozen-lockfile
|
|
|
|
- name: Check Prettier formatting
|
|
run: bun run format:check:prettier
|
|
|
|
- name: Lint web app
|
|
run: bun run lint:web
|
|
|
|
- name: Type-check web app
|
|
run: bun run typecheck:web
|
|
|
|
- name: Run web unit tests with coverage
|
|
run: bun run test:web:coverage
|
|
|
|
- name: Normalize web coverage paths for Codecov
|
|
run: |
|
|
python - <<'PY'
|
|
from pathlib import Path
|
|
|
|
source = Path('apps/web/coverage/lcov.info')
|
|
target = Path('apps/web/coverage/lcov.codecov.info')
|
|
|
|
normalized_lines = []
|
|
for line in source.read_text(encoding='utf-8').splitlines():
|
|
if line.startswith('SF:'):
|
|
path = line[3:].replace('\\', '/')
|
|
marker = '/apps/web/'
|
|
if marker in path:
|
|
path = 'apps/web/' + path.split(marker, 1)[1]
|
|
elif not path.startswith('apps/web/'):
|
|
path = 'apps/web/' + path.lstrip('./')
|
|
line = f'SF:{path}'
|
|
normalized_lines.append(line)
|
|
|
|
target.write_text('\n'.join(normalized_lines) + '\n', encoding='utf-8')
|
|
PY
|
|
|
|
- name: Preview web coverage source paths
|
|
shell: bash
|
|
run: |
|
|
grep '^SF:' apps/web/coverage/lcov.codecov.info | head -n 20
|
|
|
|
- name: Upload web coverage artifact
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: web-coverage
|
|
path: |
|
|
apps/web/coverage/lcov.info
|
|
apps/web/coverage/lcov.codecov.info
|
|
if-no-files-found: error
|
|
|
|
- name: Upload web coverage to Codecov
|
|
if: ${{ env.CODECOV_TOKEN != '' }}
|
|
uses: codecov/codecov-action@v5
|
|
with:
|
|
files: apps/web/coverage/lcov.codecov.info
|
|
flags: frontend
|
|
disable_search: true
|
|
token: ${{ env.CODECOV_TOKEN }}
|
|
fail_ci_if_error: false
|
|
|
|
- name: Build web app
|
|
run: bun run build:web
|
|
|
|
api:
|
|
name: API Checks
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
|
|
defaults:
|
|
run:
|
|
working-directory: apps/api
|
|
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@v6
|
|
with:
|
|
go-version-file: apps/api/go.mod
|
|
cache-dependency-path: apps/api/go.sum
|
|
|
|
- name: Check gofmt formatting
|
|
shell: bash
|
|
run: |
|
|
unformatted="$(gofmt -l .)"
|
|
if [ -n "$unformatted" ]; then
|
|
echo "The following Go files are not gofmt-formatted:"
|
|
echo "$unformatted"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Run API tests with coverage
|
|
run: go test ./... -coverprofile=coverage.out -covermode=atomic
|
|
|
|
- name: Print API coverage summary
|
|
run: go tool cover -func=coverage.out
|
|
|
|
- name: Rewrite API coverage paths for Codecov
|
|
shell: bash
|
|
run: |
|
|
sed 's#^github.com/xdrop/monorepo/#apps/api/#' coverage.out > coverage.codecov.out
|
|
|
|
- name: Upload API coverage artifact
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: api-coverage
|
|
path: |
|
|
apps/api/coverage.out
|
|
apps/api/coverage.codecov.out
|
|
if-no-files-found: error
|
|
|
|
- name: Upload API coverage to Codecov
|
|
if: ${{ env.CODECOV_TOKEN != '' }}
|
|
uses: codecov/codecov-action@v5
|
|
with:
|
|
files: apps/api/coverage.codecov.out
|
|
flags: backend
|
|
disable_search: true
|
|
token: ${{ env.CODECOV_TOKEN }}
|
|
fail_ci_if_error: false
|
|
|
|
- name: Build API binary
|
|
run: go build -o /tmp/xdrop-api ./cmd/api
|
|
|
|
e2e:
|
|
name: Compose Smoke And E2E
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 35
|
|
needs:
|
|
- web
|
|
- api
|
|
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Set up Bun
|
|
uses: oven-sh/setup-bun@v2
|
|
|
|
- name: Install dependencies
|
|
run: bun install --frozen-lockfile
|
|
|
|
- name: Install Playwright browser
|
|
run: bunx playwright install --with-deps chromium
|
|
|
|
- name: Start Docker stack
|
|
run: docker compose ${{ env.COMPOSE_FILES }} up -d --build
|
|
|
|
- name: Wait for Xdrop endpoints
|
|
shell: bash
|
|
run: |
|
|
for _ in {1..60}; do
|
|
if curl -fsS http://localhost:8080/healthz >/dev/null && curl -fsS http://localhost:8080/ >/dev/null; then
|
|
exit 0
|
|
fi
|
|
sleep 5
|
|
done
|
|
echo "Xdrop endpoints did not become ready in time" >&2
|
|
exit 1
|
|
|
|
- name: Run browser E2E suite
|
|
run: bun run test:e2e
|
|
|
|
- name: Capture Docker logs
|
|
if: always()
|
|
run: docker compose ${{ env.COMPOSE_FILES }} logs --no-color > compose-logs.txt
|
|
|
|
- name: Upload Playwright report
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: playwright-report
|
|
path: playwright-report
|
|
if-no-files-found: ignore
|
|
|
|
- name: Upload Playwright test results
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: playwright-test-results
|
|
path: test-results
|
|
if-no-files-found: ignore
|
|
|
|
- name: Upload Docker logs
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: compose-logs
|
|
path: compose-logs.txt
|
|
if-no-files-found: ignore
|
|
|
|
- name: Shut down Docker stack
|
|
if: always()
|
|
run: docker compose ${{ env.COMPOSE_FILES }} down -v
|