feat(deploy): HuggingFace Static Space for frontend (#34)
Browse files## Changes
- Add frontend/.env.production with backend API URL
- Update frontend/README.md with HF Spaces YAML metadata
- Add path-based CI filtering (dorny/paths-filter)
- Fix frontend-build conditional to handle skipped jobs
- Update spec 36 to document Vite 7 tsconfig pattern
## CI Optimization
- Backend jobs only run on src/, tests/, pyproject.toml changes
- Frontend jobs only run on frontend/** changes
- Push to main runs full CI (safety gate)
Co-authored-by: CodeRabbit <coderabbit@users.noreply.github.com>
.github/workflows/ci.yml
CHANGED
|
@@ -7,6 +7,11 @@ on:
|
|
| 7 |
branches: [main]
|
| 8 |
workflow_dispatch:
|
| 9 |
inputs:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
run_integration:
|
| 11 |
description: 'Run integration tests (requires HuggingFace download)'
|
| 12 |
required: false
|
|
@@ -14,7 +19,35 @@ on:
|
|
| 14 |
type: boolean
|
| 15 |
|
| 16 |
jobs:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
lint:
|
|
|
|
|
|
|
| 18 |
runs-on: ubuntu-latest
|
| 19 |
steps:
|
| 20 |
- uses: actions/checkout@v4
|
|
@@ -35,6 +68,8 @@ jobs:
|
|
| 35 |
run: uv run ruff format --check .
|
| 36 |
|
| 37 |
typecheck:
|
|
|
|
|
|
|
| 38 |
runs-on: ubuntu-latest
|
| 39 |
steps:
|
| 40 |
- uses: actions/checkout@v4
|
|
@@ -52,6 +87,8 @@ jobs:
|
|
| 52 |
run: uv run mypy src/
|
| 53 |
|
| 54 |
test:
|
|
|
|
|
|
|
| 55 |
runs-on: ubuntu-latest
|
| 56 |
steps:
|
| 57 |
- uses: actions/checkout@v4
|
|
@@ -107,8 +144,10 @@ jobs:
|
|
| 107 |
env:
|
| 108 |
HF_HOME: /tmp/hf_cache
|
| 109 |
|
| 110 |
-
# Frontend Jobs
|
| 111 |
frontend-lint:
|
|
|
|
|
|
|
| 112 |
runs-on: ubuntu-latest
|
| 113 |
defaults:
|
| 114 |
run:
|
|
@@ -126,6 +165,8 @@ jobs:
|
|
| 126 |
- run: npm run lint
|
| 127 |
|
| 128 |
frontend-typecheck:
|
|
|
|
|
|
|
| 129 |
runs-on: ubuntu-latest
|
| 130 |
defaults:
|
| 131 |
run:
|
|
@@ -143,6 +184,8 @@ jobs:
|
|
| 143 |
- run: npx tsc --noEmit
|
| 144 |
|
| 145 |
frontend-test:
|
|
|
|
|
|
|
| 146 |
runs-on: ubuntu-latest
|
| 147 |
defaults:
|
| 148 |
run:
|
|
@@ -167,6 +210,8 @@ jobs:
|
|
| 167 |
token: ${{ secrets.CODECOV_TOKEN }}
|
| 168 |
|
| 169 |
frontend-e2e:
|
|
|
|
|
|
|
| 170 |
runs-on: ubuntu-latest
|
| 171 |
defaults:
|
| 172 |
run:
|
|
@@ -193,8 +238,16 @@ jobs:
|
|
| 193 |
retention-days: 7
|
| 194 |
|
| 195 |
frontend-build:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
runs-on: ubuntu-latest
|
| 197 |
-
needs: [frontend-lint, frontend-typecheck, frontend-test]
|
| 198 |
defaults:
|
| 199 |
run:
|
| 200 |
working-directory: frontend
|
|
|
|
| 7 |
branches: [main]
|
| 8 |
workflow_dispatch:
|
| 9 |
inputs:
|
| 10 |
+
run_all:
|
| 11 |
+
description: 'Run all jobs regardless of path filters'
|
| 12 |
+
required: false
|
| 13 |
+
default: false
|
| 14 |
+
type: boolean
|
| 15 |
run_integration:
|
| 16 |
description: 'Run integration tests (requires HuggingFace download)'
|
| 17 |
required: false
|
|
|
|
| 19 |
type: boolean
|
| 20 |
|
| 21 |
jobs:
|
| 22 |
+
# Detect which paths changed to conditionally run jobs
|
| 23 |
+
changes:
|
| 24 |
+
runs-on: ubuntu-latest
|
| 25 |
+
outputs:
|
| 26 |
+
backend: ${{ steps.filter.outputs.backend }}
|
| 27 |
+
frontend: ${{ steps.filter.outputs.frontend }}
|
| 28 |
+
steps:
|
| 29 |
+
- uses: actions/checkout@v4
|
| 30 |
+
|
| 31 |
+
- uses: dorny/paths-filter@v3
|
| 32 |
+
id: filter
|
| 33 |
+
with:
|
| 34 |
+
filters: |
|
| 35 |
+
backend:
|
| 36 |
+
- 'src/**'
|
| 37 |
+
- 'tests/**'
|
| 38 |
+
- 'pyproject.toml'
|
| 39 |
+
- 'requirements.txt'
|
| 40 |
+
- 'Dockerfile'
|
| 41 |
+
- '.github/workflows/ci.yml'
|
| 42 |
+
frontend:
|
| 43 |
+
- 'frontend/**'
|
| 44 |
+
- '.github/workflows/ci.yml'
|
| 45 |
+
|
| 46 |
+
# Backend Jobs - run on backend changes, main push, or manual override
|
| 47 |
+
# Design: Push to main always runs full CI (safety gate), PRs use path filtering (efficiency)
|
| 48 |
lint:
|
| 49 |
+
needs: changes
|
| 50 |
+
if: needs.changes.outputs.backend == 'true' || github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.run_all)
|
| 51 |
runs-on: ubuntu-latest
|
| 52 |
steps:
|
| 53 |
- uses: actions/checkout@v4
|
|
|
|
| 68 |
run: uv run ruff format --check .
|
| 69 |
|
| 70 |
typecheck:
|
| 71 |
+
needs: changes
|
| 72 |
+
if: needs.changes.outputs.backend == 'true' || github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.run_all)
|
| 73 |
runs-on: ubuntu-latest
|
| 74 |
steps:
|
| 75 |
- uses: actions/checkout@v4
|
|
|
|
| 87 |
run: uv run mypy src/
|
| 88 |
|
| 89 |
test:
|
| 90 |
+
needs: changes
|
| 91 |
+
if: needs.changes.outputs.backend == 'true' || github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.run_all)
|
| 92 |
runs-on: ubuntu-latest
|
| 93 |
steps:
|
| 94 |
- uses: actions/checkout@v4
|
|
|
|
| 144 |
env:
|
| 145 |
HF_HOME: /tmp/hf_cache
|
| 146 |
|
| 147 |
+
# Frontend Jobs - run on frontend changes, main push, or manual override
|
| 148 |
frontend-lint:
|
| 149 |
+
needs: changes
|
| 150 |
+
if: needs.changes.outputs.frontend == 'true' || github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.run_all)
|
| 151 |
runs-on: ubuntu-latest
|
| 152 |
defaults:
|
| 153 |
run:
|
|
|
|
| 165 |
- run: npm run lint
|
| 166 |
|
| 167 |
frontend-typecheck:
|
| 168 |
+
needs: changes
|
| 169 |
+
if: needs.changes.outputs.frontend == 'true' || github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.run_all)
|
| 170 |
runs-on: ubuntu-latest
|
| 171 |
defaults:
|
| 172 |
run:
|
|
|
|
| 184 |
- run: npx tsc --noEmit
|
| 185 |
|
| 186 |
frontend-test:
|
| 187 |
+
needs: changes
|
| 188 |
+
if: needs.changes.outputs.frontend == 'true' || github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.run_all)
|
| 189 |
runs-on: ubuntu-latest
|
| 190 |
defaults:
|
| 191 |
run:
|
|
|
|
| 210 |
token: ${{ secrets.CODECOV_TOKEN }}
|
| 211 |
|
| 212 |
frontend-e2e:
|
| 213 |
+
needs: changes
|
| 214 |
+
if: needs.changes.outputs.frontend == 'true' || github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.run_all)
|
| 215 |
runs-on: ubuntu-latest
|
| 216 |
defaults:
|
| 217 |
run:
|
|
|
|
| 238 |
retention-days: 7
|
| 239 |
|
| 240 |
frontend-build:
|
| 241 |
+
needs: [changes, frontend-lint, frontend-typecheck, frontend-test]
|
| 242 |
+
# Build if: (1) frontend changed OR push to main OR manual run_all, AND (2) no prerequisite failures
|
| 243 |
+
# Note: Uses != 'failure' instead of == 'success' to handle skipped jobs gracefully
|
| 244 |
+
if: |
|
| 245 |
+
always() &&
|
| 246 |
+
(needs.changes.outputs.frontend == 'true' || github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.run_all)) &&
|
| 247 |
+
needs.frontend-lint.result != 'failure' &&
|
| 248 |
+
needs.frontend-typecheck.result != 'failure' &&
|
| 249 |
+
needs.frontend-test.result != 'failure'
|
| 250 |
runs-on: ubuntu-latest
|
|
|
|
| 251 |
defaults:
|
| 252 |
run:
|
| 253 |
working-directory: frontend
|
docs/specs/frontend/36-frontend-without-gradio-hf-spaces.md
CHANGED
|
@@ -207,28 +207,48 @@ export default defineConfig({
|
|
| 207 |
})
|
| 208 |
```
|
| 209 |
|
| 210 |
-
###
|
| 211 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
```json
|
| 213 |
{
|
| 214 |
"compilerOptions": {
|
| 215 |
-
"
|
|
|
|
| 216 |
"useDefineForClassFields": true,
|
| 217 |
-
"lib": ["
|
| 218 |
"module": "ESNext",
|
|
|
|
| 219 |
"skipLibCheck": true,
|
| 220 |
"moduleResolution": "bundler",
|
| 221 |
"allowImportingTsExtensions": true,
|
| 222 |
-
"
|
| 223 |
"moduleDetection": "force",
|
| 224 |
"noEmit": true,
|
| 225 |
"jsx": "react-jsx",
|
| 226 |
"strict": true,
|
| 227 |
"noUnusedLocals": true,
|
| 228 |
"noUnusedParameters": true,
|
| 229 |
-
"
|
|
|
|
|
|
|
| 230 |
},
|
| 231 |
-
"include": ["src"]
|
|
|
|
| 232 |
}
|
| 233 |
```
|
| 234 |
|
|
@@ -710,7 +730,7 @@ colorTo: purple
|
|
| 710 |
sdk: static
|
| 711 |
app_file: dist/index.html
|
| 712 |
app_build_command: npm run build
|
| 713 |
-
# CRITICAL: Vite
|
| 714 |
# Without this, the build will fail or produce warnings.
|
| 715 |
nodejs_version: "20"
|
| 716 |
pinned: false
|
|
|
|
| 207 |
})
|
| 208 |
```
|
| 209 |
|
| 210 |
+
### TypeScript Configuration (Vite 7 Project References Pattern)
|
| 211 |
|
| 212 |
+
Vite 7 uses a project references pattern for better separation of app, test, and build configs:
|
| 213 |
+
|
| 214 |
+
**tsconfig.json** (root - references only):
|
| 215 |
+
```json
|
| 216 |
+
{
|
| 217 |
+
"files": [],
|
| 218 |
+
"references": [
|
| 219 |
+
{ "path": "./tsconfig.app.json" },
|
| 220 |
+
{ "path": "./tsconfig.node.json" },
|
| 221 |
+
{ "path": "./tsconfig.test.json" }
|
| 222 |
+
]
|
| 223 |
+
}
|
| 224 |
+
```
|
| 225 |
+
|
| 226 |
+
**tsconfig.app.json** (application code):
|
| 227 |
```json
|
| 228 |
{
|
| 229 |
"compilerOptions": {
|
| 230 |
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
| 231 |
+
"target": "ES2022",
|
| 232 |
"useDefineForClassFields": true,
|
| 233 |
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
| 234 |
"module": "ESNext",
|
| 235 |
+
"types": ["vite/client"],
|
| 236 |
"skipLibCheck": true,
|
| 237 |
"moduleResolution": "bundler",
|
| 238 |
"allowImportingTsExtensions": true,
|
| 239 |
+
"verbatimModuleSyntax": true,
|
| 240 |
"moduleDetection": "force",
|
| 241 |
"noEmit": true,
|
| 242 |
"jsx": "react-jsx",
|
| 243 |
"strict": true,
|
| 244 |
"noUnusedLocals": true,
|
| 245 |
"noUnusedParameters": true,
|
| 246 |
+
"erasableSyntaxOnly": true,
|
| 247 |
+
"noFallthroughCasesInSwitch": true,
|
| 248 |
+
"noUncheckedSideEffectImports": true
|
| 249 |
},
|
| 250 |
+
"include": ["src"],
|
| 251 |
+
"exclude": ["src/test", "src/mocks", "src/**/*.test.tsx", "src/**/*.test.ts"]
|
| 252 |
}
|
| 253 |
```
|
| 254 |
|
|
|
|
| 730 |
sdk: static
|
| 731 |
app_file: dist/index.html
|
| 732 |
app_build_command: npm run build
|
| 733 |
+
# CRITICAL: Vite 7 requires Node.js >= 20. HF Spaces defaults to Node 18.
|
| 734 |
# Without this, the build will fail or produce warnings.
|
| 735 |
nodejs_version: "20"
|
| 736 |
pinned: false
|
frontend/.env.production
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Production environment - HuggingFace Spaces deployment
|
| 2 |
+
# Backend API running on Docker Space with T4 GPU
|
| 3 |
+
VITE_API_URL=https://vibecodermcswaggins-stroke-deepisles-demo.hf.space
|
frontend/README.md
CHANGED
|
@@ -6,8 +6,7 @@ colorTo: purple
|
|
| 6 |
sdk: static
|
| 7 |
app_file: dist/index.html
|
| 8 |
app_build_command: npm run build
|
| 9 |
-
# CRITICAL: Vite
|
| 10 |
-
# Without this, the build will fail or produce warnings.
|
| 11 |
nodejs_version: "20"
|
| 12 |
pinned: false
|
| 13 |
---
|
|
@@ -61,5 +60,20 @@ npm run build # Production build
|
|
| 61 |
Set `VITE_API_URL` to point to your backend:
|
| 62 |
|
| 63 |
```bash
|
|
|
|
| 64 |
VITE_API_URL=http://localhost:7860 npm run dev
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
```
|
|
|
|
| 6 |
sdk: static
|
| 7 |
app_file: dist/index.html
|
| 8 |
app_build_command: npm run build
|
| 9 |
+
# CRITICAL: Vite 7 requires Node.js >= 20. HF Spaces defaults to Node 18.
|
|
|
|
| 10 |
nodejs_version: "20"
|
| 11 |
pinned: false
|
| 12 |
---
|
|
|
|
| 60 |
Set `VITE_API_URL` to point to your backend:
|
| 61 |
|
| 62 |
```bash
|
| 63 |
+
# Local development (default)
|
| 64 |
VITE_API_URL=http://localhost:7860 npm run dev
|
| 65 |
+
|
| 66 |
+
# Production is configured in .env.production
|
| 67 |
+
# Points to: https://vibecodermcswaggins-stroke-deepisles-demo.hf.space
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
## Deployment
|
| 71 |
+
|
| 72 |
+
This frontend deploys as a **HuggingFace Static Space**. The backend API runs on a separate Docker Space with GPU.
|
| 73 |
+
|
| 74 |
+
```bash
|
| 75 |
+
# Build for production (uses .env.production)
|
| 76 |
+
npm run build
|
| 77 |
+
|
| 78 |
+
# The dist/ folder is deployed to HF Static Space
|
| 79 |
```
|