Skip to content

refactor(frontend): 移除 GSAP 并修复首页刷新闪烁 #388

refactor(frontend): 移除 GSAP 并修复首页刷新闪烁

refactor(frontend): 移除 GSAP 并修复首页刷新闪烁 #388

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
merge_group:
workflow_dispatch:
concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
defaults:
run:
shell: bash
jobs:
repository-check:
name: Repository rules
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
persist-credentials: false
- name: Repository configuration
run: |
set -euo pipefail
python3 - <<'PY'
import pathlib
import re
pattern = re.compile(r"^\s*-\s+uses:\s+([^\s#]+)", re.MULTILINE)
sha = re.compile(r"^[0-9a-f]{40}$")
invalid = []
for workflow in sorted(pathlib.Path('.github/workflows').glob('*.y*ml')):
for reference in pattern.findall(workflow.read_text(encoding='utf-8')):
if reference.startswith(('./', 'docker://')):
continue
_, separator, revision = reference.rpartition('@')
if not separator or sha.fullmatch(revision) is None:
invalid.append(f'{workflow}: {reference}')
if invalid:
raise SystemExit('GitHub Actions 必须固定到完整提交 SHA:\n - ' + '\n - '.join(invalid))
PY
trap 'rm -f .env .env.prod' EXIT
cp .env.example .env
cp .env.prod.example .env.prod
docker compose -f docker-compose-env.yml config --quiet
docker compose -f docker-compose.yml config --quiet
docker compose -f docker-compose-prod.yml config --quiet
backend-quality:
name: Backend quality
runs-on: ubuntu-24.04
timeout-minutes: 15
services:
postgres:
image: postgres:17.5-alpine
env:
POSTGRES_DB: video
POSTGRES_USER: video
POSTGRES_PASSWORD: video
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -q -U video -d video"
--health-interval 5s
--health-timeout 5s
--health-retries 20
env:
TEST_DATABASE_URL: postgresql+asyncpg://video:video@127.0.0.1:5432/video
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: "3.12"
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
with:
version: "0.11.32"
enable-cache: true
cache-dependency-glob: backend/uv.lock
- name: Backend quality gates
working-directory: backend
run: |
uv sync --frozen --dev
uv run --frozen ruff check app tests
uv run --frozen mypy --strict app
uv run --frozen pytest -q
frontend-quality:
name: Frontend quality
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: "24.15.0"
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Pin npm version
run: npm install --global npm@11.19.0
- name: Frontend quality gates
working-directory: frontend
run: |
npm ci
npm audit --omit=dev --audit-level=high
npm run lint
npm test
npm run build
runtime-smoke:
name: Runtime boundary
runs-on: ubuntu-24.04
timeout-minutes: 25
needs: [repository-check, backend-quality, frontend-quality]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: Build and verify runtime topology
run: |
set -Eeuo pipefail
compose=(docker compose --project-name video-server-ci -f docker-compose-env.yml -f docker-compose.yml)
started=0
cleanup() {
status=$?
trap - EXIT
if ((status != 0 && started == 1)); then
"${compose[@]}" ps --all || true
"${compose[@]}" logs --no-color --tail=200 || true
fi
if ((started == 1)); then
"${compose[@]}" down --volumes --remove-orphans || true
fi
rm -f .env
exit "$status"
}
trap cleanup EXIT
cp .env.example .env
sed -i 's/^ANALYSIS_ENABLED=.*/ANALYSIS_ENABLED=false/' .env
conflicts=()
while IFS= read -r container_name; do
if docker inspect "$container_name" >/dev/null 2>&1; then
conflicts+=("$container_name")
fi
done < <("${compose[@]}" config --format json | python3 -c 'import json,sys; print("\n".join(sorted(service["container_name"] for service in json.load(sys.stdin)["services"].values())))')
if ((${#conflicts[@]} > 0)); then
printf '运行边界烟测拒绝复用已有同名容器:%s\n' "${conflicts[*]}" >&2
exit 2
fi
docker build --target runtime --tag video-server:local .
started=1
"${compose[@]}" up --detach --wait --wait-timeout 240 postgres rabbitmq valkey minio
"${compose[@]}" run --rm --no-deps database-init
"${compose[@]}" run --rm --no-deps rabbitmq-init
"${compose[@]}" run --rm --no-deps minio-init
"${compose[@]}" up --detach --wait --wait-timeout 240 --no-build api frontend outbox worker-download provider-canary worker-report media-runner
python3 - <<'PY'
import json
import urllib.request
def request(origin: str, path: str):
with urllib.request.urlopen(f'{origin}{path}', timeout=10) as response:
return response.status, response.headers.get_content_type(), response.read()
api_origin = 'http://127.0.0.1:8111'
frontend_origin = 'http://127.0.0.1:8101'
status, content_type, body = request(api_origin, '/health/live')
assert status == 200 and json.loads(body) == {'status': 'ok'}
status, content_type, body = request(api_origin, '/health/ready')
assert status == 200 and json.loads(body) == {'status': 'ok', 'service': 'api'}
status, content_type, body = request(api_origin, '/openapi.json')
schema = json.loads(body)
assert status == 200 and content_type == 'application/json' and schema.get('paths')
status, content_type, _ = request(frontend_origin, '/')
assert status == 200 and content_type == 'text/html'
print(f'运行时烟测通过:{len(schema["paths"])} 个 OpenAPI paths')
PY
"${compose[@]}" run --rm --no-deps database-init
required-ci:
name: Required CI
if: always()
runs-on: ubuntu-24.04
timeout-minutes: 5
needs: [repository-check, backend-quality, frontend-quality, runtime-smoke]
steps:
- name: Require every boundary
env:
REPOSITORY_RESULT: ${{ needs.repository-check.result }}
BACKEND_RESULT: ${{ needs.backend-quality.result }}
FRONTEND_RESULT: ${{ needs.frontend-quality.result }}
RUNTIME_RESULT: ${{ needs.runtime-smoke.result }}
run: |
test "$REPOSITORY_RESULT" = success
test "$BACKEND_RESULT" = success
test "$FRONTEND_RESULT" = success
test "$RUNTIME_RESULT" = success