Spaces:
Runtime error
build(deploy): pipeline Astro (build+inline) + deploy_prod rsconnect + .rscignore
Browse files- scripts/build.ps1: compila Astro -> scripts/inline_js.py (inline JS + rutas relativas,
Regla de Oro) -> copia a backend/static. Reemplaza build_ui.ps1.
- scripts/inline_js.py: post-proceso del build para sub-paths de ShinyApps.
- scripts/deploy_prod.ps1: build + uv export requirements.txt + traduce .rscignore a
--exclude + rsconnect deploy shiny . --entrypoint backend.main:app (-Name/-AppId
parametrizables; app-id pendiente).
- .rscignore: exclusiones del bundle; **excluye .env** (BYOK, sin secretos en prod).
- dev.ps1: gateway (:8000) + Astro dev (:4321 hot-reload) en ventanas separadas.
- pyproject: rsconnect-python (dev). .gitignore: requirements.txt (generado).
- ejecucion.md §5: despliegue Agro-Stack documentado.
- Verificado: build.ps1 e inline_js.py corren OK; rsconnect importable.
- .gitignore +2 -0
- .rscignore +61 -0
- docs/ejecucion.md +22 -16
- pyproject.toml +1 -0
- scripts/build.ps1 +33 -0
- scripts/build_ui.ps1 +0 -17
- scripts/deploy_prod.ps1 +62 -0
- scripts/dev.ps1 +11 -5
- scripts/inline_js.py +121 -0
- uv.lock +71 -0
|
@@ -39,6 +39,8 @@ frontend/pnpm-debug.log*
|
|
| 39 |
backend/static/
|
| 40 |
backend/static/*
|
| 41 |
!backend/static/.gitkeep
|
|
|
|
|
|
|
| 42 |
|
| 43 |
# --- Data & SQLite ---
|
| 44 |
data/*
|
|
|
|
| 39 |
backend/static/
|
| 40 |
backend/static/*
|
| 41 |
!backend/static/.gitkeep
|
| 42 |
+
# requirements.txt se genera en el deploy (uv export); la fuente es pyproject + uv.lock
|
| 43 |
+
requirements.txt
|
| 44 |
|
| 45 |
# --- Data & SQLite ---
|
| 46 |
data/*
|
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Exclusiones para el deploy a ShinyApps.io.
|
| 2 |
+
#
|
| 3 |
+
# IMPORTANTE: rsconnect-python NO lee este archivo por sí solo. Lo consume
|
| 4 |
+
# `scripts/deploy_prod.ps1`, que convierte cada línea en un flag `--exclude` de
|
| 5 |
+
# `rsconnect deploy shiny .`. Nombrar una carpeta basta (rsconnect la expande).
|
| 6 |
+
#
|
| 7 |
+
# Lo que SÍ va en el bundle: backend/ (incl. backend/static = UI Astro compilada),
|
| 8 |
+
# pyproject.toml, uv.lock, requirements.txt y .env.example.
|
| 9 |
+
|
| 10 |
+
# --- Entornos / dependencias (pesados) ---
|
| 11 |
+
.venv
|
| 12 |
+
node_modules
|
| 13 |
+
frontend/node_modules
|
| 14 |
+
|
| 15 |
+
# --- Caches de herramientas ---
|
| 16 |
+
.hypothesis
|
| 17 |
+
.pytest_cache
|
| 18 |
+
.mypy_cache
|
| 19 |
+
.ruff_cache
|
| 20 |
+
__pycache__
|
| 21 |
+
.git
|
| 22 |
+
.astro
|
| 23 |
+
|
| 24 |
+
# --- Fuente del frontend (lo compilado vive en backend/static) ---
|
| 25 |
+
frontend
|
| 26 |
+
|
| 27 |
+
# --- Desarrollo / no necesarios en runtime ---
|
| 28 |
+
tests
|
| 29 |
+
scripts
|
| 30 |
+
docs
|
| 31 |
+
notebooks
|
| 32 |
+
scratch
|
| 33 |
+
supabase
|
| 34 |
+
models
|
| 35 |
+
sample_data
|
| 36 |
+
.playwright-mcp
|
| 37 |
+
.claude
|
| 38 |
+
.agents
|
| 39 |
+
.vscode
|
| 40 |
+
.idea
|
| 41 |
+
.github
|
| 42 |
+
|
| 43 |
+
# --- SECRETOS: el modelo es BYOK (el usuario pone sus llaves por sesión).
|
| 44 |
+
# NO subir .env al bundle. ---
|
| 45 |
+
.env
|
| 46 |
+
.env.local
|
| 47 |
+
|
| 48 |
+
# --- Archivos sueltos ---
|
| 49 |
+
*.png
|
| 50 |
+
*.jpg
|
| 51 |
+
*.jpeg
|
| 52 |
+
*.gif
|
| 53 |
+
*.webp
|
| 54 |
+
*.csv
|
| 55 |
+
*.xlsx
|
| 56 |
+
*.db
|
| 57 |
+
*.sqlite
|
| 58 |
+
*.onnx
|
| 59 |
+
*.pt
|
| 60 |
+
*.log
|
| 61 |
+
*.tmp
|
|
@@ -61,7 +61,7 @@ uv sync
|
|
| 61 |
|
| 62 |
La UI es **Astro + Tailwind** (Node/pnpm). Compílala a estático con:
|
| 63 |
```powershell
|
| 64 |
-
.\scripts\
|
| 65 |
```
|
| 66 |
El gateway sirve ese build en `/`. Para hot-reload usa `pnpm dev` (ver §3.2).
|
| 67 |
|
|
@@ -86,7 +86,7 @@ cp .env.example .env
|
|
| 86 |
|
| 87 |
```powershell
|
| 88 |
# 1) Compilar la UI Astro -> backend/static (una vez, o tras cambios de UI)
|
| 89 |
-
.\scripts\
|
| 90 |
# 2) Levantar el gateway (sirve la UI en / y la API en /api)
|
| 91 |
.\scripts\run_backend.ps1
|
| 92 |
```
|
|
@@ -168,29 +168,35 @@ docker compose up --build # gateway en :8000 (sirve API + UI Astro compilada)
|
|
| 168 |
> - Verifica `uv run ruff check .` y `uv run python -m pytest` en verde antes de desplegar.
|
| 169 |
> - No se versionan secretos: configúralos en el panel de cada plataforma.
|
| 170 |
|
| 171 |
-
### 5.2
|
| 172 |
|
| 173 |
-
```
|
| 174 |
-
|
|
|
|
|
|
|
| 175 |
uv run rsconnect add --account <cuenta> --name <cuenta> --token <TOKEN> --secret <SECRET>
|
| 176 |
```
|
| 177 |
|
| 178 |
-
### 5.3 Ejecución del Despliegue
|
| 179 |
|
| 180 |
-
```
|
| 181 |
-
#
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
# Conecta el repo en el panel de Render o usa la API con RENDER_API_KEY.
|
| 186 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
|
| 188 |
### 5.4 Verificación Post-Despliegue
|
| 189 |
|
| 190 |
-
- [ ] URL pública
|
| 191 |
-
- [ ] `https://<
|
| 192 |
-
- [ ]
|
| 193 |
-
- [ ]
|
| 194 |
|
| 195 |
---
|
| 196 |
|
|
|
|
| 61 |
|
| 62 |
La UI es **Astro + Tailwind** (Node/pnpm). Compílala a estático con:
|
| 63 |
```powershell
|
| 64 |
+
.\scripts\build.ps1 # = pnpm build + inline_js.py + copia a backend/static
|
| 65 |
```
|
| 66 |
El gateway sirve ese build en `/`. Para hot-reload usa `pnpm dev` (ver §3.2).
|
| 67 |
|
|
|
|
| 86 |
|
| 87 |
```powershell
|
| 88 |
# 1) Compilar la UI Astro -> backend/static (una vez, o tras cambios de UI)
|
| 89 |
+
.\scripts\build.ps1
|
| 90 |
# 2) Levantar el gateway (sirve la UI en / y la API en /api)
|
| 91 |
.\scripts\run_backend.ps1
|
| 92 |
```
|
|
|
|
| 168 |
> - Verifica `uv run ruff check .` y `uv run python -m pytest` en verde antes de desplegar.
|
| 169 |
> - No se versionan secretos: configúralos en el panel de cada plataforma.
|
| 170 |
|
| 171 |
+
### 5.2 Modelo de despliegue (Agro-Stack)
|
| 172 |
|
| 173 |
+
Un solo servicio: el **gateway FastAPI** (`backend.main:app`) sirve la **UI Astro compilada** en `/`, la **API** en `/api` y (opcional) el Shiny legacy en `/shiny`. Se despliega a **ShinyApps.io** con `rsconnect` (como app ASGI), aplicando la **Regla de Oro** (JS inline + rutas relativas vía `scripts/inline_js.py`).
|
| 174 |
+
|
| 175 |
+
Registrar el token (una vez):
|
| 176 |
+
```powershell
|
| 177 |
uv run rsconnect add --account <cuenta> --name <cuenta> --token <TOKEN> --secret <SECRET>
|
| 178 |
```
|
| 179 |
|
| 180 |
+
### 5.3 Ejecución del Despliegue (script)
|
| 181 |
|
| 182 |
+
```powershell
|
| 183 |
+
# Primer deploy (crea la app en ShinyApps.io):
|
| 184 |
+
.\scripts\deploy_prod.ps1 -Name <cuenta>
|
| 185 |
+
# Redeploy (cuando ya exista el app-id):
|
| 186 |
+
.\scripts\deploy_prod.ps1 -Name <cuenta> -AppId <id>
|
|
|
|
| 187 |
```
|
| 188 |
+
`deploy_prod.ps1` hace: (0) compila la UI (`build.ps1`), (1) genera `requirements.txt` (`uv export`), (2) traduce `.rscignore` a flags `--exclude`, (3) `rsconnect deploy shiny . --entrypoint backend.main:app`.
|
| 189 |
+
|
| 190 |
+
> **BYOK / seguridad:** el `.env` **NO** viaja en el bundle (está en `.rscignore`); en producción las llaves las pone el usuario por sesión (cabeceras `X-User-*`).
|
| 191 |
+
> **Aún no hay app-id** para AgroVisión: el primer deploy con `-Name` lo crea; luego reutiliza el id con `-AppId` para redeploy.
|
| 192 |
+
> **Alternativa:** backend en **Render** con `backend/Dockerfile` (etapa Node compila Astro y el gateway sirve `/`).
|
| 193 |
|
| 194 |
### 5.4 Verificación Post-Despliegue
|
| 195 |
|
| 196 |
+
- [ ] URL pública accesible vía HTTPS; `/` carga la UI Astro.
|
| 197 |
+
- [ ] `https://<app>/api/status` responde `200`.
|
| 198 |
+
- [ ] Recargar (F5) no rompe la app: la SPA usa **hash-routing** + **rutas relativas** (Regla de Oro), así que el slug dinámico de ShinyApps no produce 404.
|
| 199 |
+
- [ ] Las credenciales se ingresan en la pestaña *Credenciales* (BYOK; no hay secretos en el bundle).
|
| 200 |
|
| 201 |
---
|
| 202 |
|
|
@@ -34,6 +34,7 @@ dev = [
|
|
| 34 |
"pytest>=8",
|
| 35 |
"ruff>=0.6",
|
| 36 |
"playwright>=1.45",
|
|
|
|
| 37 |
]
|
| 38 |
|
| 39 |
[tool.uv]
|
|
|
|
| 34 |
"pytest>=8",
|
| 35 |
"ruff>=0.6",
|
| 36 |
"playwright>=1.45",
|
| 37 |
+
"rsconnect-python>=1.28", # deploy a ShinyApps.io (scripts/deploy_prod.ps1)
|
| 38 |
]
|
| 39 |
|
| 40 |
[tool.uv]
|
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# scripts/build.ps1 — Pipeline de build de la UI: Astro -> inline -> backend/static
|
| 2 |
+
# (lo que sirve el gateway FastAPI en /). Uso: .\scripts\build.ps1
|
| 3 |
+
$ErrorActionPreference = "Stop"
|
| 4 |
+
$root = Split-Path -Parent $PSScriptRoot
|
| 5 |
+
Set-Location $root
|
| 6 |
+
# venv fuera de OneDrive (para uv) — ver docs/ejecucion.md
|
| 7 |
+
$env:UV_PROJECT_ENVIRONMENT = "$env:LOCALAPPDATA\agrovision-venv"
|
| 8 |
+
|
| 9 |
+
Write-Host "=== Build AgroVisión UI (Astro -> backend/static) ===" -ForegroundColor Cyan
|
| 10 |
+
|
| 11 |
+
# 1) Compilar Astro
|
| 12 |
+
Write-Host "`n[1/3] Compilando Astro (pnpm build)..." -ForegroundColor Yellow
|
| 13 |
+
Push-Location "$root\frontend"
|
| 14 |
+
pnpm install
|
| 15 |
+
pnpm build
|
| 16 |
+
$buildOk = ($LASTEXITCODE -eq 0)
|
| 17 |
+
Pop-Location
|
| 18 |
+
if (-not $buildOk) { Write-Error "pnpm build falló"; exit 1 }
|
| 19 |
+
|
| 20 |
+
# 2) Post-procesar: inline de JS y rutas relativas (Regla de Oro para ShinyApps)
|
| 21 |
+
Write-Host "`n[2/3] Post-procesando index.html (inline_js.py)..." -ForegroundColor Yellow
|
| 22 |
+
uv run python scripts/inline_js.py
|
| 23 |
+
if ($LASTEXITCODE -ne 0) { Write-Error "inline_js.py falló"; exit 1 }
|
| 24 |
+
|
| 25 |
+
# 3) Copiar el build a backend/static (preservando .gitkeep)
|
| 26 |
+
Write-Host "`n[3/3] Copiando frontend\dist -> backend\static..." -ForegroundColor Yellow
|
| 27 |
+
$static = "$root\backend\static"
|
| 28 |
+
New-Item -ItemType Directory -Force -Path $static | Out-Null
|
| 29 |
+
Get-ChildItem $static -Exclude ".gitkeep" -Force | Remove-Item -Recurse -Force
|
| 30 |
+
Copy-Item -Path "$root\frontend\dist\*" -Destination $static -Recurse -Force
|
| 31 |
+
if (-not (Test-Path "$static\.gitkeep")) { New-Item -ItemType File "$static\.gitkeep" | Out-Null }
|
| 32 |
+
|
| 33 |
+
Write-Host "`n=== Build completado. Sirve con: .\scripts\run_backend.ps1 -> http://127.0.0.1:8000/ ===" -ForegroundColor Green
|
|
@@ -1,17 +0,0 @@
|
|
| 1 |
-
# Compila la UI Astro y la copia a backend/static (servida por el gateway FastAPI en /).
|
| 2 |
-
# Uso: .\scripts\build_ui.ps1
|
| 3 |
-
Set-Location (Split-Path -Parent $PSScriptRoot)
|
| 4 |
-
|
| 5 |
-
Push-Location frontend
|
| 6 |
-
pnpm install
|
| 7 |
-
pnpm build
|
| 8 |
-
Pop-Location
|
| 9 |
-
|
| 10 |
-
$static = "backend/static"
|
| 11 |
-
New-Item -ItemType Directory -Force -Path $static | Out-Null
|
| 12 |
-
# Limpia el estático anterior conservando .gitkeep
|
| 13 |
-
Get-ChildItem $static -Exclude ".gitkeep" -Force | Remove-Item -Recurse -Force
|
| 14 |
-
Copy-Item -Recurse -Force "frontend/dist/*" "$static/"
|
| 15 |
-
if (-not (Test-Path "$static/.gitkeep")) { New-Item -ItemType File "$static/.gitkeep" | Out-Null }
|
| 16 |
-
|
| 17 |
-
Write-Host "UI Astro compilada -> backend/static (sirve el gateway en http://127.0.0.1:8000/)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# scripts/deploy_prod.ps1 — Despliegue de AgroVisión a ShinyApps.io (Agro-Stack).
|
| 2 |
+
#
|
| 3 |
+
# El entrypoint es el gateway FastAPI (backend.main:app), que sirve la UI Astro
|
| 4 |
+
# compilada en / y la API en /api. El bundle es el repo (`.`) MENOS las exclusiones
|
| 5 |
+
# de `.rscignore` (rsconnect-python no lee .rscignore solo: aquí se traducen a --exclude).
|
| 6 |
+
#
|
| 7 |
+
# BYOK: el `.env` (secretos) NO viaja en el bundle (está en .rscignore); en producción
|
| 8 |
+
# las credenciales las pone el usuario por sesión (cabeceras X-User-*).
|
| 9 |
+
#
|
| 10 |
+
# Aún no hay app-id para esta app: pásalo cuando exista para hacer redeploy.
|
| 11 |
+
# Primer deploy: .\scripts\deploy_prod.ps1 -Name <tu_cuenta>
|
| 12 |
+
# Redeploy: .\scripts\deploy_prod.ps1 -Name <tu_cuenta> -AppId <id>
|
| 13 |
+
# Registrar token (una vez):
|
| 14 |
+
# uv run rsconnect add --account <cuenta> --name <cuenta> --token <TOKEN> --secret <SECRET>
|
| 15 |
+
param(
|
| 16 |
+
[string]$Name = "<TU_CUENTA_SHINYAPPS>",
|
| 17 |
+
[string]$AppId = "" # vacío = primer deploy (crea la app); luego usa el id para redeploy
|
| 18 |
+
)
|
| 19 |
+
$ErrorActionPreference = "Stop"
|
| 20 |
+
$root = Split-Path -Parent $PSScriptRoot
|
| 21 |
+
Set-Location $root
|
| 22 |
+
$env:UV_PROJECT_ENVIRONMENT = "$env:LOCALAPPDATA\agrovision-venv"
|
| 23 |
+
|
| 24 |
+
Write-Host "=== Deploy AgroVisión -> ShinyApps.io ===" -ForegroundColor Cyan
|
| 25 |
+
|
| 26 |
+
# 0) Compilar la UI (Astro -> backend/static)
|
| 27 |
+
Write-Host "`n[0/3] Compilando la UI..." -ForegroundColor Yellow
|
| 28 |
+
& "$PSScriptRoot\build.ps1"
|
| 29 |
+
|
| 30 |
+
# 1) requirements.txt para ShinyApps (no usa uv; instala desde requirements.txt)
|
| 31 |
+
Write-Host "`n[1/3] Generando requirements.txt (uv export)..." -ForegroundColor Yellow
|
| 32 |
+
uv export --no-dev --no-hashes --no-emit-project -o requirements.txt
|
| 33 |
+
if ($LASTEXITCODE -ne 0) { Write-Error "uv export falló"; exit 1 }
|
| 34 |
+
|
| 35 |
+
# 2) Construir los --exclude desde .rscignore (ignora comentarios y vacíos)
|
| 36 |
+
$patterns = @()
|
| 37 |
+
if (Test-Path ".rscignore") {
|
| 38 |
+
$patterns = Get-Content ".rscignore" |
|
| 39 |
+
ForEach-Object { $_.Trim() } |
|
| 40 |
+
Where-Object { $_ -and (-not $_.StartsWith("#")) } |
|
| 41 |
+
Select-Object -Unique
|
| 42 |
+
} else {
|
| 43 |
+
Write-Warning ".rscignore no encontrado: el bundle incluiría TODO el repo."
|
| 44 |
+
}
|
| 45 |
+
$excludeArgs = @()
|
| 46 |
+
foreach ($p in $patterns) { $excludeArgs += "--exclude"; $excludeArgs += $p }
|
| 47 |
+
Write-Host ("[2/3] {0} patrones de exclusión cargados desde .rscignore" -f $patterns.Count)
|
| 48 |
+
|
| 49 |
+
if ($Name -eq "<TU_CUENTA_SHINYAPPS>") {
|
| 50 |
+
Write-Warning "Falta tu cuenta de ShinyApps. Uso: .\scripts\deploy_prod.ps1 -Name <cuenta> [-AppId <id>]"
|
| 51 |
+
Write-Warning "Y registra el token primero (ver cabecera de este script)."
|
| 52 |
+
exit 1
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
# 3) Desplegar (entrypoint = gateway FastAPI que sirve la UI Astro + /api + /shiny)
|
| 56 |
+
Write-Host "`n[3/3] Desplegando (entrypoint backend.main:app)..." -ForegroundColor Yellow
|
| 57 |
+
$appIdArgs = @()
|
| 58 |
+
if ($AppId) { $appIdArgs = @("--app-id", $AppId) }
|
| 59 |
+
& uv run python -c "from rsconnect.main import cli; cli()" deploy shiny . `
|
| 60 |
+
--entrypoint backend.main:app --name $Name @appIdArgs @excludeArgs
|
| 61 |
+
|
| 62 |
+
Write-Host "`n=== Despliegue completado ===" -ForegroundColor Green
|
|
@@ -1,8 +1,14 @@
|
|
| 1 |
-
# Arranca
|
| 2 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
Start-Process powershell -ArgumentList "-NoExit", "-File", "`"$PSScriptRoot\run_backend.ps1`""
|
| 4 |
Start-Sleep -Seconds 2
|
| 5 |
-
Start-Process powershell -ArgumentList "-NoExit", "-
|
| 6 |
|
| 7 |
-
Write-Host "
|
| 8 |
-
Write-Host "UI
|
|
|
|
|
|
|
|
|
| 1 |
+
# Arranca el stack de DESARROLLO en ventanas PowerShell separadas (local, sin Docker):
|
| 2 |
+
# - Gateway FastAPI (:8000) — API en /api (+ UI compilada en / si existe backend/static).
|
| 3 |
+
# - Astro dev server (:4321) — UI con hot-reload; proxea /api -> :8000.
|
| 4 |
+
# Uso: .\scripts\dev.ps1
|
| 5 |
+
$root = Split-Path -Parent $PSScriptRoot
|
| 6 |
+
|
| 7 |
Start-Process powershell -ArgumentList "-NoExit", "-File", "`"$PSScriptRoot\run_backend.ps1`""
|
| 8 |
Start-Sleep -Seconds 2
|
| 9 |
+
Start-Process powershell -ArgumentList "-NoExit", "-Command", "Set-Location '$root\frontend'; pnpm dev"
|
| 10 |
|
| 11 |
+
Write-Host "API: http://127.0.0.1:8000/api/status"
|
| 12 |
+
Write-Host "UI (Astro, dev): http://localhost:4321/ (hot-reload)"
|
| 13 |
+
Write-Host "UI (compilada): http://127.0.0.1:8000/ (tras .\scripts\build.ps1)"
|
| 14 |
+
Write-Host "UI Shiny (legacy): .\scripts\run_ui.ps1 -> http://127.0.0.1:8001"
|
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Archivo: inline_js.py
|
| 3 |
+
Fecha de modificación: 04/06/2026
|
| 4 |
+
Autor: Equipo AgroVisión
|
| 5 |
+
|
| 6 |
+
Descripción:
|
| 7 |
+
Post-procesa el HTML del build de Astro para que funcione bajo **sub-paths dinámicos**
|
| 8 |
+
(como los slugs `/_w_xxxx/` de ShinyApps.io). Inyecta inline los scripts que Astro
|
| 9 |
+
hubiera emitido en `/_astro/*.js` y normaliza las rutas de assets (favicon, JS) de
|
| 10 |
+
**absolutas a relativas**, ya que el host sirve la app desde una ruta distinta de la raíz.
|
| 11 |
+
Es la implementación de la "Regla de Oro" de `docs/doc_guia/plan_replication.md`.
|
| 12 |
+
|
| 13 |
+
Nota: nuestra UI ya usa scripts `is:inline` y `inlineStylesheets: 'always'`, por lo que
|
| 14 |
+
normalmente no hay `/_astro/*.js` externos; este script es idempotente y a prueba de
|
| 15 |
+
futuro (si se añade una isla/componente que genere JS externo, queda inlineado).
|
| 16 |
+
|
| 17 |
+
Acciones Principales:
|
| 18 |
+
- Inyecta el contenido de los scripts `/_astro/*.js` directamente en el HTML.
|
| 19 |
+
- Convierte las rutas absolutas de favicon a relativas.
|
| 20 |
+
- Normaliza cualquier referencia absoluta restante a `/_astro/`.
|
| 21 |
+
|
| 22 |
+
Estructura Interna:
|
| 23 |
+
- `inline_scripts`: inyecta el código JS externo inline.
|
| 24 |
+
- `fix_favicon_paths`: relativiza las rutas de iconos.
|
| 25 |
+
- `fix_absolute_asset_refs`: relativiza las rutas absolutas restantes.
|
| 26 |
+
- `main`: orquesta el post-proceso sobre `frontend/dist/index.html`.
|
| 27 |
+
|
| 28 |
+
Entradas / Dependencias:
|
| 29 |
+
- Biblioteca estándar (`re`, `pathlib`).
|
| 30 |
+
|
| 31 |
+
Salidas / Efectos:
|
| 32 |
+
- Reescribe `frontend/dist/index.html` in place.
|
| 33 |
+
|
| 34 |
+
Ejecución:
|
| 35 |
+
uv run python scripts/inline_js.py
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
from __future__ import annotations
|
| 39 |
+
|
| 40 |
+
import re
|
| 41 |
+
import sys
|
| 42 |
+
from pathlib import Path
|
| 43 |
+
|
| 44 |
+
DIST = Path("frontend/dist")
|
| 45 |
+
HTML_FILE = DIST / "index.html"
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def inline_scripts(html: str) -> str:
|
| 49 |
+
"""
|
| 50 |
+
Reemplaza cada `<script ... src="/_astro/...">` por su contenido inline.
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
html (str): HTML de entrada.
|
| 54 |
+
|
| 55 |
+
Returns:
|
| 56 |
+
str: HTML con los scripts de Astro embebidos inline.
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
def _replace(match: re.Match) -> str: # type: ignore[type-arg]
|
| 60 |
+
src = match.group(1)
|
| 61 |
+
rel = src.lstrip("/") # la URL parte de "/"; el archivo es relativo a DIST
|
| 62 |
+
js_path = DIST / rel
|
| 63 |
+
if js_path.exists():
|
| 64 |
+
content = js_path.read_text(encoding="utf-8")
|
| 65 |
+
return f'<script type="module">{content}</script>'
|
| 66 |
+
print(f" [WARN] JS no encontrado: {js_path}", file=sys.stderr)
|
| 67 |
+
return match.group(0)
|
| 68 |
+
|
| 69 |
+
pattern = (
|
| 70 |
+
r'<script\s[^>]*type=["\']module["\'][^>]*'
|
| 71 |
+
r'src=["\'](\/_astro\/[^"\']+)["\'][^>]*>\s*</script>'
|
| 72 |
+
)
|
| 73 |
+
return re.sub(pattern, _replace, html)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def fix_favicon_paths(html: str) -> str:
|
| 77 |
+
"""Convierte rutas absolutas `/favicon.*` a relativas `./favicon.*`."""
|
| 78 |
+
html = re.sub(r'href="\/favicon\.', 'href="./favicon.', html)
|
| 79 |
+
html = re.sub(r"href='\/favicon\.", "href='./favicon.", html)
|
| 80 |
+
return html
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def fix_absolute_asset_refs(html: str) -> str:
|
| 84 |
+
"""Convierte cualquier referencia absoluta restante a `/_astro/` en relativa."""
|
| 85 |
+
html = html.replace('href="/_astro/', 'href="./_astro/')
|
| 86 |
+
html = html.replace("href='/_astro/", "href='./_astro/")
|
| 87 |
+
html = html.replace('src="/_astro/', 'src="./_astro/')
|
| 88 |
+
html = html.replace("src='/_astro/", "src='./_astro/")
|
| 89 |
+
return html
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def main() -> None:
|
| 93 |
+
"""
|
| 94 |
+
Aplica el post-proceso sobre `frontend/dist/index.html` in place.
|
| 95 |
+
|
| 96 |
+
Raises:
|
| 97 |
+
SystemExit: si no existe el HTML del build (código 1).
|
| 98 |
+
"""
|
| 99 |
+
if not HTML_FILE.exists():
|
| 100 |
+
print(f"[ERROR] No se encontró {HTML_FILE}. Ejecuta 'pnpm build' (en frontend/) primero.")
|
| 101 |
+
sys.exit(1)
|
| 102 |
+
|
| 103 |
+
html = HTML_FILE.read_text(encoding="utf-8")
|
| 104 |
+
original_len = len(html)
|
| 105 |
+
|
| 106 |
+
html = inline_scripts(html)
|
| 107 |
+
html = fix_favicon_paths(html)
|
| 108 |
+
html = fix_absolute_asset_refs(html)
|
| 109 |
+
|
| 110 |
+
HTML_FILE.write_text(html, encoding="utf-8")
|
| 111 |
+
|
| 112 |
+
remaining = re.findall(r'["\']/_astro/', html)
|
| 113 |
+
if remaining:
|
| 114 |
+
print(f"[WARN] Aún quedan {len(remaining)} referencias a /_astro/ sin convertir.")
|
| 115 |
+
else:
|
| 116 |
+
print(f"[OK] inline_js.py completado ({original_len} -> {len(html)} bytes).")
|
| 117 |
+
print(" No quedan referencias absolutas a /_astro/ en index.html.")
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
if __name__ == "__main__":
|
| 121 |
+
main()
|
|
@@ -38,6 +38,7 @@ dependencies = [
|
|
| 38 |
dev = [
|
| 39 |
{ name = "playwright" },
|
| 40 |
{ name = "pytest" },
|
|
|
|
| 41 |
{ name = "ruff" },
|
| 42 |
]
|
| 43 |
|
|
@@ -70,6 +71,7 @@ requires-dist = [
|
|
| 70 |
dev = [
|
| 71 |
{ name = "playwright", specifier = ">=1.45" },
|
| 72 |
{ name = "pytest", specifier = ">=8" },
|
|
|
|
| 73 |
{ name = "ruff", specifier = ">=0.6" },
|
| 74 |
]
|
| 75 |
|
|
@@ -1568,6 +1570,15 @@ wheels = [
|
|
| 1568 |
{ url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" },
|
| 1569 |
]
|
| 1570 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1571 |
[[package]]
|
| 1572 |
name = "platformdirs"
|
| 1573 |
version = "4.10.0"
|
|
@@ -1867,6 +1878,15 @@ wheels = [
|
|
| 1867 |
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
| 1868 |
]
|
| 1869 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1870 |
[[package]]
|
| 1871 |
name = "pyparsing"
|
| 1872 |
version = "3.3.2"
|
|
@@ -2017,6 +2037,22 @@ wheels = [
|
|
| 2017 |
{ url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
|
| 2018 |
]
|
| 2019 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2020 |
[[package]]
|
| 2021 |
name = "ruff"
|
| 2022 |
version = "0.15.15"
|
|
@@ -2113,6 +2149,15 @@ wheels = [
|
|
| 2113 |
{ url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" },
|
| 2114 |
]
|
| 2115 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2116 |
[[package]]
|
| 2117 |
name = "setuptools"
|
| 2118 |
version = "82.0.1"
|
|
@@ -2443,6 +2488,32 @@ wheels = [
|
|
| 2443 |
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
| 2444 |
]
|
| 2445 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2446 |
[[package]]
|
| 2447 |
name = "uvicorn"
|
| 2448 |
version = "0.48.0"
|
|
|
|
| 38 |
dev = [
|
| 39 |
{ name = "playwright" },
|
| 40 |
{ name = "pytest" },
|
| 41 |
+
{ name = "rsconnect-python" },
|
| 42 |
{ name = "ruff" },
|
| 43 |
]
|
| 44 |
|
|
|
|
| 71 |
dev = [
|
| 72 |
{ name = "playwright", specifier = ">=1.45" },
|
| 73 |
{ name = "pytest", specifier = ">=8" },
|
| 74 |
+
{ name = "rsconnect-python", specifier = ">=1.28" },
|
| 75 |
{ name = "ruff", specifier = ">=0.6" },
|
| 76 |
]
|
| 77 |
|
|
|
|
| 1570 |
{ url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" },
|
| 1571 |
]
|
| 1572 |
|
| 1573 |
+
[[package]]
|
| 1574 |
+
name = "pip"
|
| 1575 |
+
version = "26.1.2"
|
| 1576 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1577 |
+
sdist = { url = "https://files.pythonhosted.org/packages/01/91/47e7d486260f618783899587af63ccf7980fb60245c3e63dd4571c6b57ad/pip-26.1.2.tar.gz", hash = "sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605", size = 1840799, upload-time = "2026-05-31T17:33:58.56Z" }
|
| 1578 |
+
wheels = [
|
| 1579 |
+
{ url = "https://files.pythonhosted.org/packages/5d/95/6b5cb3461ea5673ba0995989746db58eb18b91b54dbf331e72f569540946/pip-26.1.2-py3-none-any.whl", hash = "sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab", size = 1813144, upload-time = "2026-05-31T17:33:56.772Z" },
|
| 1580 |
+
]
|
| 1581 |
+
|
| 1582 |
[[package]]
|
| 1583 |
name = "platformdirs"
|
| 1584 |
version = "4.10.0"
|
|
|
|
| 1878 |
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
| 1879 |
]
|
| 1880 |
|
| 1881 |
+
[[package]]
|
| 1882 |
+
name = "pyjwt"
|
| 1883 |
+
version = "2.13.0"
|
| 1884 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1885 |
+
sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" }
|
| 1886 |
+
wheels = [
|
| 1887 |
+
{ url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" },
|
| 1888 |
+
]
|
| 1889 |
+
|
| 1890 |
[[package]]
|
| 1891 |
name = "pyparsing"
|
| 1892 |
version = "3.3.2"
|
|
|
|
| 2037 |
{ url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
|
| 2038 |
]
|
| 2039 |
|
| 2040 |
+
[[package]]
|
| 2041 |
+
name = "rsconnect-python"
|
| 2042 |
+
version = "1.29.0"
|
| 2043 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2044 |
+
dependencies = [
|
| 2045 |
+
{ name = "click" },
|
| 2046 |
+
{ name = "pip" },
|
| 2047 |
+
{ name = "pyjwt" },
|
| 2048 |
+
{ name = "semver" },
|
| 2049 |
+
{ name = "typing-extensions" },
|
| 2050 |
+
{ name = "uv" },
|
| 2051 |
+
]
|
| 2052 |
+
wheels = [
|
| 2053 |
+
{ url = "https://files.pythonhosted.org/packages/4b/2d/e1c992e5f63421cdc01a9398a67c62c886b711085d16c01abee7335354ca/rsconnect_python-1.29.0-py2.py3-none-any.whl", hash = "sha256:187960581cd5f277409b94d01d30e47e731d89eb28bb62b74aea551b921f7f6d", size = 116926, upload-time = "2026-04-29T19:10:42.708Z" },
|
| 2054 |
+
]
|
| 2055 |
+
|
| 2056 |
[[package]]
|
| 2057 |
name = "ruff"
|
| 2058 |
version = "0.15.15"
|
|
|
|
| 2149 |
{ url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" },
|
| 2150 |
]
|
| 2151 |
|
| 2152 |
+
[[package]]
|
| 2153 |
+
name = "semver"
|
| 2154 |
+
version = "3.0.4"
|
| 2155 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2156 |
+
sdist = { url = "https://files.pythonhosted.org/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730, upload-time = "2025-01-24T13:19:27.617Z" }
|
| 2157 |
+
wheels = [
|
| 2158 |
+
{ url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" },
|
| 2159 |
+
]
|
| 2160 |
+
|
| 2161 |
[[package]]
|
| 2162 |
name = "setuptools"
|
| 2163 |
version = "82.0.1"
|
|
|
|
| 2488 |
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
| 2489 |
]
|
| 2490 |
|
| 2491 |
+
[[package]]
|
| 2492 |
+
name = "uv"
|
| 2493 |
+
version = "0.11.19"
|
| 2494 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2495 |
+
sdist = { url = "https://files.pythonhosted.org/packages/67/f0/6254502aebfdc0a9df6069269a126dd58252ac29d2d6cdf4777cea3e90b5/uv-0.11.19.tar.gz", hash = "sha256:f56f5bf853626a30423052d7ee00bf5cc940a08347d6ee7ede96862d084054a5", size = 4213580, upload-time = "2026-06-03T22:37:15.976Z" }
|
| 2496 |
+
wheels = [
|
| 2497 |
+
{ url = "https://files.pythonhosted.org/packages/1a/73/be32c2f6ba30fa9d8b3baceb478107cc23722d4aaab87145a332e4985185/uv-0.11.19-py3-none-linux_armv6l.whl", hash = "sha256:c729f56ffef9b945053412c839695e8a0b13758aa15b7763e95a7dd539a6f522", size = 23620003, upload-time = "2026-06-03T22:37:53.017Z" },
|
| 2498 |
+
{ url = "https://files.pythonhosted.org/packages/fd/ed/3aefe4a4ca4ac9204c6745670dbe12f4add69194d40f5abd1c7bd45ba9af/uv-0.11.19-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a98495b9dd67287d8c1a0786f98cb037a50f0ee6c3d648572edaa7137aabc277", size = 23183211, upload-time = "2026-06-03T22:37:20.699Z" },
|
| 2499 |
+
{ url = "https://files.pythonhosted.org/packages/5b/eb/5d1469f9e709d56066f292978711fbf1f805b7fb46f901d3c1f260fd9908/uv-0.11.19-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fdd881cd6d80782afcf8c1d446dd15a42985167fd812b763d38ba1e4a8d944d", size = 21754003, upload-time = "2026-06-03T22:37:05.027Z" },
|
| 2500 |
+
{ url = "https://files.pythonhosted.org/packages/7b/93/109b5ee6678f54492f94fdef74149643eaa1f2f4716906a2a10816b31247/uv-0.11.19-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:7222f45b5541551057bfc2e3021f113800704f665c119fdf3ea700c6c4859b21", size = 23518832, upload-time = "2026-06-03T22:37:28.794Z" },
|
| 2501 |
+
{ url = "https://files.pythonhosted.org/packages/08/0c/8c59bbcf78e94ca9994256920efa99d1c4dc9d0b966eb62ebba075585a16/uv-0.11.19-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:2e0e0b8ad59ec56f1440d6e4313b64a1d8119275dcec73d19eef33c43f99428c", size = 23163128, upload-time = "2026-06-03T22:37:23.226Z" },
|
| 2502 |
+
{ url = "https://files.pythonhosted.org/packages/89/d6/69caf9e6f11c84b5fb92df190b46fbecb7dc6645ae891c6ed66d7aaaa310/uv-0.11.19-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4aa17ffd719daf37b7a6265efd3ee4922a8ddaabaf0406d2b28c7e5ce2f20ff", size = 23164395, upload-time = "2026-06-03T22:37:18.11Z" },
|
| 2503 |
+
{ url = "https://files.pythonhosted.org/packages/d6/83/0c2242b77c51ac33a0ddd8b06790429a0b8b9623974c9594ab2b0070ec47/uv-0.11.19-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32d7988c0dfb6f90941f201c871a4478e96e4f2a32bdb2256d62a78ee20593fc", size = 24541708, upload-time = "2026-06-03T22:37:08.093Z" },
|
| 2504 |
+
{ url = "https://files.pythonhosted.org/packages/54/10/b1404fc52c0eddc3655f57a8b76e79dcf8dd02568382272f17e2fa68c4bb/uv-0.11.19-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d663bacb97e2e8412d1c26eace28c7ebbde9d6f5d7d78760fafd114d693817f", size = 25575501, upload-time = "2026-06-03T22:37:47.526Z" },
|
| 2505 |
+
{ url = "https://files.pythonhosted.org/packages/7c/17/4cda5994195ba9ce1f6971d40d5f2ceec58e2a79030d9052b3bf322557b1/uv-0.11.19-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:574f5dd4f31666661ea6386d3b91c5f0e8b84a8cae98ebba447c4674f2e6a4c7", size = 24827200, upload-time = "2026-06-03T22:37:34.039Z" },
|
| 2506 |
+
{ url = "https://files.pythonhosted.org/packages/5a/74/2bd8b51e1d76210fd424ae55ec3f34ded5a10eeff3dd38aeb03c816a0af2/uv-0.11.19-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:731d9fab8db5d41590af64236d03f8069c8da665fd0f9493b85985f19c86cd90", size = 24872664, upload-time = "2026-06-03T22:37:11.301Z" },
|
| 2507 |
+
{ url = "https://files.pythonhosted.org/packages/06/b1/44b0764f656bbdd0728118610a63f2feddd9cbe450f974d80c5bb56aad34/uv-0.11.19-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:301fd78309fc545c2cec2bfcc61a6bbdde876856c6d2041502737cf44085c178", size = 23617890, upload-time = "2026-06-03T22:37:44.796Z" },
|
| 2508 |
+
{ url = "https://files.pythonhosted.org/packages/d2/25/312fa33cd4c34e7618f86cad0c9fdb312d8fef2e7fc61944c1a2f1bf1256/uv-0.11.19-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:62b0b35a51d3034ff30ecd0f381e9bbc20d5b335754f54b098da29424d551ceb", size = 24267220, upload-time = "2026-06-03T22:37:39.425Z" },
|
| 2509 |
+
{ url = "https://files.pythonhosted.org/packages/8d/25/13856aeff9e14c98ee3e1ceae4d209301cbdeabde93abcd758433601dc82/uv-0.11.19-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:65e932720daed1af1f720a0ff5f9b33ee5f7ad97488dcceceb85154fc1323b82", size = 24376177, upload-time = "2026-06-03T22:37:50.276Z" },
|
| 2510 |
+
{ url = "https://files.pythonhosted.org/packages/45/7d/590b3ab420e03504cf658d2981e1fcb4af60f3858d42da1d4d8740141dd9/uv-0.11.19-py3-none-musllinux_1_1_i686.whl", hash = "sha256:8f90b6687a480d154595aa619fb836a9a20d00ce37293db8099aad924f2b18f9", size = 23808336, upload-time = "2026-06-03T22:37:26.086Z" },
|
| 2511 |
+
{ url = "https://files.pythonhosted.org/packages/9e/8e/40acebd4ea419c870930580623e8367e23d810a0ecb8cc2f44d852a27293/uv-0.11.19-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:28b0d612a766eb25756dbaa315433b726e93affa467d29a2682cc317547952ba", size = 25080747, upload-time = "2026-06-03T22:37:13.886Z" },
|
| 2512 |
+
{ url = "https://files.pythonhosted.org/packages/9c/d3/4037b2acb2bb73b1a3ee47a1d23864ecc503f5840387afd29f621d4fd2ec/uv-0.11.19-py3-none-win32.whl", hash = "sha256:aa6a7e8d07b33ad22f4732848ebb1d9486503973c248d6e632c06ce4339fe347", size = 22459533, upload-time = "2026-06-03T22:37:36.741Z" },
|
| 2513 |
+
{ url = "https://files.pythonhosted.org/packages/d4/43/f374fad7ad94e4a8c47cf09f00d803c76c6cc7f225668c41f4e2fb5de000/uv-0.11.19-py3-none-win_amd64.whl", hash = "sha256:480fc34a8d0967af6a90b3f99a6e5687cd5c6e29528de96bec04d6e305a59363", size = 25143888, upload-time = "2026-06-03T22:37:42.169Z" },
|
| 2514 |
+
{ url = "https://files.pythonhosted.org/packages/18/98/d2db53ae036528b0a9407529ef175ee200b01f626c9c160978784c8af870/uv-0.11.19-py3-none-win_arm64.whl", hash = "sha256:50e4d4796ca1a6da359a4f723a0fea86640c381d3ff4fa759a41badd7cb52dee", size = 23601290, upload-time = "2026-06-03T22:37:31.393Z" },
|
| 2515 |
+
]
|
| 2516 |
+
|
| 2517 |
[[package]]
|
| 2518 |
name = "uvicorn"
|
| 2519 |
version = "0.48.0"
|