feat(infrastructure): add frontend, deploy to cloudflare

This commit is contained in:
2025-10-06 18:05:32 +02:00
parent 37f4d44caf
commit 9c4144f5c4
19 changed files with 4007 additions and 5 deletions

View File

@@ -20,6 +20,15 @@ jobs:
pr_number: ${{ github.event.pull_request.number }}
secrets: inherit
frontend:
if: github.event.action != 'closed'
name: Frontend - Build and Deploy to Cloudflare Pages (PR)
uses: ./.github/workflows/frontend-pages.yml
with:
mode: pr
pr_number: ${{ github.event.pull_request.number }}
secrets: inherit
deploy:
if: github.event.action != 'closed'
name: Helm upgrade/install (PR preview)
@@ -27,7 +36,7 @@ jobs:
concurrency:
group: pr-${{ github.event.pull_request.number }}
cancel-in-progress: false
needs: [build]
needs: [build, frontend]
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -76,10 +85,11 @@ jobs:
--set-string rabbitmq.password="$RABBITMQ_PASSWORD" \
--set-string database.password="$DB_PASSWORD"
- name: Post preview URL as PR comment
- name: Post preview URLs as PR comment
uses: actions/github-script@v7
env:
DEV_BASE_DOMAIN: ${{ secrets.BASE_DOMAIN }}
FRONTEND_URL: ${{ needs.frontend.outputs.deployed_url }}
with:
script: |
const pr = context.payload.pull_request;
@@ -87,10 +97,11 @@ jobs:
const prNumber = pr.number;
const domainBase = process.env.DEV_BASE_DOMAIN;
if (!domainBase) { core.setFailed('DEV_BASE_DOMAIN is required'); return; }
const domain = `pr-${prNumber}.${domainBase}`;
const url = `https://${domain}`;
const backendDomain = `pr-${prNumber}.${domainBase}`;
const backendUrl = `https://${backendDomain}`;
const frontendUrl = process.env.FRONTEND_URL || '(not available)';
const marker = '<!-- preview-link -->';
const body = `${marker}\nPreview environment is running: ${url}\n`;
const body = `${marker}\nPreview environment is running\n- Frontend: ${frontendUrl}\n- Backend: ${backendUrl}\n`;
const { owner, repo } = context.repo;
const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number: prNumber, per_page: 100 });
const existing = comments.find(c => c.body && c.body.includes(marker));

View File

@@ -28,6 +28,13 @@ jobs:
context: 7project/backend
secrets: inherit
frontend:
name: Frontend - Build and Deploy to Cloudflare Pages (prod)
uses: ./.github/workflows/frontend-pages.yml
with:
mode: prod
secrets: inherit
deploy:
name: Helm upgrade/install (prod)
runs-on: vhs

166
.github/workflows/frontend-pages.yml vendored Normal file
View File

@@ -0,0 +1,166 @@
name: Frontend - Build and Deploy to Cloudflare Pages
on:
workflow_call:
inputs:
mode:
description: "Build mode: 'prod' or 'pr'"
required: true
type: string
pr_number:
description: 'PR number (required when mode=pr)'
required: false
type: string
project_name:
description: 'Cloudflare Pages project name (overrides default)'
required: false
type: string
secrets:
CLOUDFLARE_API_TOKEN:
required: true
CLOUDFLARE_ACCOUNT_ID:
required: true
outputs:
deployed_url:
description: 'URL of deployed frontend'
value: ${{ jobs.deploy.outputs.deployed_url }}
push:
branches: [ "main" ]
paths:
- '7project/frontend/**'
- '.github/workflows/frontend-pages.yml'
pull_request:
branches: [ "main" ]
paths:
- '7project/frontend/**'
- '.github/workflows/frontend-pages.yml'
workflow_dispatch:
inputs:
project_name:
description: 'Cloudflare Pages project name (overrides default)'
required: false
type: string
# Required repository secrets:
# CLOUDFLARE_API_TOKEN - API token with Pages:Edit (or Account:Workers Scripts:Edit) permissions
# CLOUDFLARE_ACCOUNT_ID - Your Cloudflare account ID
# Optional repository variables:
# CF_PAGES_PROJECT_NAME - Default Cloudflare Pages project name
# PROD_DOMAIN - App domain for prod releases (e.g., api.example.com or https://api.example.com)
# BACKEND_URL_PR_TEMPLATE - Template for PR backend URL. Use {PR} placeholder for PR number (e.g., https://api-pr-{PR}.example.com)
jobs:
build:
name: Build frontend
runs-on: ubuntu-latest
defaults:
run:
working-directory: 7project/frontend
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: 7project/frontend/package-lock.json
- name: Install dependencies
run: npm ci
- name: Compute backend URL for Vite
id: be
env:
EVENT_NAME: ${{ github.event_name }}
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
PR_TEMPLATE: ${{ vars.BACKEND_URL_PR_TEMPLATE }}
PROD_DOMAIN: ${{ vars.PROD_DOMAIN }}
MODE: ${{ inputs.mode }}
run: |
set -euo pipefail
URL=""
if [ -n "${PROD_DOMAIN:-}" ]; then
if echo "$PROD_DOMAIN" | grep -Eiq '^https?://'; then
URL="$PROD_DOMAIN"
else
URL="https://${PROD_DOMAIN}"
fi
fi
if [ "${MODE:-}" = "pr" ] || [ "${EVENT_NAME}" = "pull_request" ]; then
if [ -n "${PR_TEMPLATE:-}" ] && [ -n "${PR_NUMBER:-}" ] ; then
URL="${PR_TEMPLATE//\{PR\}/${PR_NUMBER}}"
fi
fi
echo "Using backend URL: ${URL:-<empty>}"
echo "VITE_BACKEND_URL=${URL}" >> $GITHUB_ENV
- name: Build
run: npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: frontend-dist
path: 7project/frontend/dist
deploy:
name: Deploy to Cloudflare Pages
needs: build
runs-on: ubuntu-latest
outputs:
deployed_url: ${{ steps.out.outputs.deployed_url }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: frontend-dist
path: dist
- name: Determine project name and branch
id: pname
env:
INPUT_MODE: ${{ inputs.mode }}
INPUT_PR: ${{ inputs.pr_number }}
run: |
set -euo pipefail
# Prefer manual input, then repo variable, fallback to repo-name
INPUT_NAME='${{ inputs.project_name }}'
VAR_NAME='${{ vars.CF_PAGES_PROJECT_NAME }}'
if [ -n "$INPUT_NAME" ]; then PNAME="$INPUT_NAME";
elif [ -n "$VAR_NAME" ]; then PNAME="$VAR_NAME";
else PNAME="${GITHUB_REPOSITORY##*/}-frontend"; fi
# Determine branch for Pages
if [ "${INPUT_MODE}" = "pr" ]; then
if [ -z "${INPUT_PR}" ]; then echo "pr_number is required when mode=pr"; exit 1; fi
PBRANCH="pr-${INPUT_PR}"
else
PBRANCH="main"
fi
echo "project_name=$PNAME" >> $GITHUB_OUTPUT
echo "branch=$PBRANCH" >> $GITHUB_OUTPUT
- name: Deploy using Cloudflare Wrangler
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=${{ steps.pname.outputs.project_name }} --branch=${{ steps.pname.outputs.branch }}
- name: Compute deployed URL
id: out
env:
PNAME: ${{ steps.pname.outputs.project_name }}
PBRANCH: ${{ steps.pname.outputs.branch }}
run: |
set -euo pipefail
if [ "$PBRANCH" = "main" ]; then
URL="https://${PNAME}.pages.dev"
else
URL="https://${PBRANCH}.${PNAME}.pages.dev"
fi
echo "deployed_url=$URL" >> $GITHUB_OUTPUT