feat: add bash infra functions — Gitea, Android SDK, Mantine, Capacitor

Nuevas funciones bash: gestión Gitea (create_repo, list_repos, add_collaborator,
push_directory), install_android_sdk, install_mantine, frontend_doctor.
Pipelines: capacitor_build_apk y gitea_init_app.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 23:47:10 +02:00
parent 817ec9fc36
commit 01042bc23c
18 changed files with 1466 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
---
name: frontend_doctor
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "frontend_doctor(project_dir: string) -> diagnostics_stdout"
description: "Diagnostica la salud de un proyecto frontend Mantine. Verifica Node, React, Mantine, PostCSS, TypeScript, vite.config y detecta residuos de shadcn/@base-ui. Imprime tabla de checks con exit code 0/1."
tags: [frontend, mantine, doctor, diagnostics, health, validation]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: project_dir
desc: "directorio del proyecto frontend con package.json"
output: "tabla de checks con ✓/✗ por cada validación y resumen final"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/frontend_doctor.sh"
---
## Ejemplo
```bash
# Diagnosticar un proyecto
bash frontend_doctor.sh ./apps/rapid_dashboards/frontend
# Output:
# === Frontend Doctor: ./apps/rapid_dashboards/frontend ===
#
# ✓ Node >= 18 22.12.0
# ✓ Package manager detected pnpm
# ✓ node_modules present
# ✓ @mantine/core 7.17.0
# ✓ @mantine/hooks
# ✓ @mantine/charts
# ✓ React >= 18 19.2.4
# ✓ postcss.config present
# ✓ TypeScript >= 5 6.0.2
# ✓ vite.config present
# ✓ No shadcn residual
# ✓ No @base-ui residual
#
# Resultado: todo OK
```
## Notas
Checks informativos, no modifica nada. Util para validar que un proyecto esta correctamente configurado despues de instalar Mantine o migrar desde shadcn. Exit code 0 si todo OK, 1 si hay problemas.
+150
View File
@@ -0,0 +1,150 @@
# frontend_doctor
# ----------------
# Diagnostica la salud de un proyecto frontend Mantine.
# Verifica dependencias, configuracion y versiones.
# Imprime tabla de checks y retorna exit code 0 (ok) o 1 (fallos).
#
# USO (sourced):
# source frontend_doctor.sh
# frontend_doctor /path/to/frontend
#
# USO (directo):
# bash frontend_doctor.sh /path/to/frontend
frontend_doctor() {
local project_dir="$1"
local failures=0
if [ -z "$project_dir" ]; then
echo "frontend_doctor: se requiere project_dir" >&2
return 1
fi
if [ ! -f "$project_dir/package.json" ]; then
echo "frontend_doctor: no existe package.json en $project_dir" >&2
return 1
fi
echo "=== Frontend Doctor: $project_dir ==="
echo ""
# Helper: check y reportar
_check() {
local label="$1"
local ok="$2"
local detail="$3"
if [ "$ok" = "1" ]; then
printf " ✓ %-35s %s\n" "$label" "$detail"
else
printf " ✗ %-35s %s\n" "$label" "$detail"
((failures++))
fi
}
# 1. Node >= 18
local node_ver=""
local node_ok=0
if command -v node &>/dev/null; then
node_ver=$(node -v 2>/dev/null | sed 's/v//')
local node_major=$(echo "$node_ver" | cut -d. -f1)
[ "$node_major" -ge 18 ] 2>/dev/null && node_ok=1
fi
_check "Node >= 18" "$node_ok" "${node_ver:-not found}"
# 2. Package manager
local pm_ok=0
local pm_name="none"
if [ -f "$project_dir/pnpm-lock.yaml" ]; then
pm_name="pnpm"; pm_ok=1
elif [ -f "$project_dir/yarn.lock" ]; then
pm_name="yarn"; pm_ok=1
elif [ -f "$project_dir/package-lock.json" ]; then
pm_name="npm"; pm_ok=1
fi
_check "Package manager detected" "$pm_ok" "$pm_name"
# 3. node_modules existe
local nm_ok=0
[ -d "$project_dir/node_modules" ] && nm_ok=1
_check "node_modules present" "$nm_ok" ""
# 4. @mantine/core instalado
local mantine_ok=0
local mantine_ver=""
if [ -f "$project_dir/node_modules/@mantine/core/package.json" ]; then
mantine_ver=$(node -e "console.log(require('$project_dir/node_modules/@mantine/core/package.json').version)" 2>/dev/null)
mantine_ok=1
fi
_check "@mantine/core" "$mantine_ok" "${mantine_ver:-not installed}"
# 5. @mantine/hooks
local hooks_ok=0
[ -d "$project_dir/node_modules/@mantine/hooks" ] && hooks_ok=1
_check "@mantine/hooks" "$hooks_ok" ""
# 6. @mantine/charts
local charts_ok=0
[ -d "$project_dir/node_modules/@mantine/charts" ] && charts_ok=1
_check "@mantine/charts" "$charts_ok" ""
# 7. React >= 18
local react_ok=0
local react_ver=""
if [ -f "$project_dir/node_modules/react/package.json" ]; then
react_ver=$(node -e "console.log(require('$project_dir/node_modules/react/package.json').version)" 2>/dev/null)
local react_major=$(echo "$react_ver" | cut -d. -f1)
[ "$react_major" -ge 18 ] 2>/dev/null && react_ok=1
fi
_check "React >= 18" "$react_ok" "${react_ver:-not found}"
# 8. postcss.config presente
local postcss_ok=0
if [ -f "$project_dir/postcss.config.cjs" ] || [ -f "$project_dir/postcss.config.js" ] || [ -f "$project_dir/postcss.config.mjs" ]; then
postcss_ok=1
fi
_check "postcss.config present" "$postcss_ok" ""
# 9. TypeScript >= 5
local ts_ok=0
local ts_ver=""
if [ -f "$project_dir/node_modules/typescript/package.json" ]; then
ts_ver=$(node -e "console.log(require('$project_dir/node_modules/typescript/package.json').version)" 2>/dev/null)
local ts_major=$(echo "$ts_ver" | cut -d. -f1)
[ "$ts_major" -ge 5 ] 2>/dev/null && ts_ok=1
fi
_check "TypeScript >= 5" "$ts_ok" "${ts_ver:-not found}"
# 10. vite.config presente
local vite_ok=0
if [ -f "$project_dir/vite.config.ts" ] || [ -f "$project_dir/vite.config.js" ]; then
vite_ok=1
fi
_check "vite.config present" "$vite_ok" ""
# 11. Shadcn residual (warning)
local shadcn_clean=1
if [ -f "$project_dir/components.json" ]; then
shadcn_clean=0
fi
_check "No shadcn residual" "$shadcn_clean" "$([ "$shadcn_clean" = "0" ] && echo 'components.json found')"
# 12. @base-ui residual (warning)
local baseui_clean=1
if [ -d "$project_dir/node_modules/@base-ui" ]; then
baseui_clean=0
fi
_check "No @base-ui residual" "$baseui_clean" "$([ "$baseui_clean" = "0" ] && echo '@base-ui still installed')"
echo ""
if [ "$failures" -eq 0 ]; then
echo " Resultado: todo OK"
return 0
else
echo " Resultado: $failures problema(s) encontrado(s)"
return 1
fi
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
frontend_doctor "$@"
fi
@@ -0,0 +1,53 @@
---
name: gitea_add_collaborator
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "gitea_add_collaborator(owner: string, repo: string, username: string, permission: string) -> void"
description: "Añade un colaborador a un repositorio Gitea con el nivel de permisos indicado. Silencioso si el colaborador ya existe (422)."
tags: [gitea, git, collaborator, permission, repo, api, infra]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: owner
desc: "usuario u organización propietaria del repositorio"
- name: repo
desc: "nombre del repositorio"
- name: username
desc: "nombre de usuario del colaborador a añadir"
- name: permission
desc: "nivel de permisos: 'read', 'write' o 'admin' (default: admin)"
output: "vacío — efectos observables a través de la API de Gitea"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/gitea_add_collaborator.sh"
---
## Ejemplo
```bash
source bash/functions/infra/gitea_add_collaborator.sh
export GITEA_URL="https://git.example.com"
export GITEA_TOKEN="$(pass agentes/dataforge-token)"
# Añadir colaborador con permiso admin (default)
gitea_add_collaborator "myorg" "my-app" "egutierrez"
# Añadir colaborador con permiso de solo lectura
gitea_add_collaborator "myorg" "my-app" "reviewer" "read"
```
## Notas
- Requiere `GITEA_URL` y `GITEA_TOKEN` seteadas.
- Un 422 de la API indica que el usuario ya es colaborador — se trata como éxito silencioso.
- La función no produce salida a stdout; los mensajes informativos van a stderr.
- Nivel `admin` da acceso completo al repo incluyendo settings.
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# gitea_add_collaborator — Añade un colaborador a un repositorio Gitea
gitea_add_collaborator() {
local owner="$1"
local repo="$2"
local username="$3"
local permission="${4:-admin}"
if [[ -z "${GITEA_URL:-}" ]]; then
echo "gitea_add_collaborator: GITEA_URL no está seteada" >&2
return 1
fi
if [[ -z "${GITEA_TOKEN:-}" ]]; then
echo "gitea_add_collaborator: GITEA_TOKEN no está seteado" >&2
return 1
fi
if [[ -z "$owner" || -z "$repo" || -z "$username" ]]; then
echo "gitea_add_collaborator: se requieren owner, repo y username" >&2
return 1
fi
local payload
payload=$(printf '{"permission":"%s"}' "$permission")
echo "gitea_add_collaborator: añadiendo '$username' a '$owner/$repo' con permiso '$permission'..." >&2
local response http_code
response=$(curl -s -w "\n%{http_code}" \
-X PUT \
-H "Content-Type: application/json" \
-H "Authorization: token ${GITEA_TOKEN}" \
-d "$payload" \
"${GITEA_URL}/api/v1/repos/${owner}/${repo}/collaborators/${username}")
http_code=$(echo "$response" | tail -n1)
local body
body=$(echo "$response" | head -n -1)
if [[ "$http_code" == "204" || "$http_code" == "200" ]]; then
echo "gitea_add_collaborator: '$username' añadido a '$owner/$repo'" >&2
return 0
fi
if [[ "$http_code" == "422" ]]; then
echo "gitea_add_collaborator: '$username' ya es colaborador de '$owner/$repo' (silencioso)" >&2
return 0
fi
echo "gitea_add_collaborator: error (HTTP ${http_code}): ${body}" >&2
return 1
}
+56
View File
@@ -0,0 +1,56 @@
---
name: gitea_create_repo
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "gitea_create_repo(owner: string, name: string, private: string, description: string) -> string"
description: "Crea un repositorio en Gitea para un owner. Intenta crearlo en org primero; si el owner no es una org (404/422), lo crea en el usuario autenticado. No falla fatalmente si el repo ya existe (409)."
tags: [gitea, git, repo, create, infra, api]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: owner
desc: "usuario u organización propietaria del repo"
- name: name
desc: "nombre del repositorio a crear"
- name: private
desc: "si el repo es privado, 'true' o 'false' (default: false)"
- name: description
desc: "descripción del repositorio (opcional)"
output: "JSON del repositorio creado según la API de Gitea"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/gitea_create_repo.sh"
---
## Ejemplo
```bash
source bash/functions/infra/gitea_create_repo.sh
export GITEA_URL="https://git.example.com"
export GITEA_TOKEN="$(pass agentes/dataforge-token)"
# Crear repo público en org o usuario
repo_json=$(gitea_create_repo "myorg" "my-app")
# Crear repo privado con descripción
repo_json=$(gitea_create_repo "myorg" "my-app" "true" "Mi aplicación principal")
# Extraer la URL del clon
clone_url=$(echo "$repo_json" | jq -r '.clone_url')
```
## Notas
- Requiere variables de entorno `GITEA_URL` y `GITEA_TOKEN` seteadas antes de invocar.
- El fallback org → usuario ocurre con HTTP 404 o 422 en el endpoint de orgs.
- Un 409 se reporta a stderr pero la función retorna 0 — el repo ya existe es una condición aceptable para idempotencia.
- Los mensajes informativos van a stderr; el JSON de respuesta va a stdout.
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
# gitea_create_repo — Crea un repositorio en Gitea para un owner (org o usuario)
gitea_create_repo() {
local owner="$1"
local name="$2"
local private="${3:-false}"
local description="${4:-}"
if [[ -z "${GITEA_URL:-}" ]]; then
echo "gitea_create_repo: GITEA_URL no está seteada" >&2
return 1
fi
if [[ -z "${GITEA_TOKEN:-}" ]]; then
echo "gitea_create_repo: GITEA_TOKEN no está seteado" >&2
return 1
fi
if [[ -z "$owner" || -z "$name" ]]; then
echo "gitea_create_repo: se requieren owner y name" >&2
return 1
fi
local payload
payload=$(printf '{"name":"%s","private":%s,"description":"%s","auto_init":false}' \
"$name" "$private" "$description")
echo "gitea_create_repo: intentando crear '$owner/$name' en org..." >&2
local response http_code
response=$(curl -s -w "\n%{http_code}" \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: token ${GITEA_TOKEN}" \
-d "$payload" \
"${GITEA_URL}/api/v1/orgs/${owner}/repos")
http_code=$(echo "$response" | tail -n1)
local body
body=$(echo "$response" | head -n -1)
if [[ "$http_code" == "201" ]]; then
echo "gitea_create_repo: repo '$owner/$name' creado en org" >&2
echo "$body"
return 0
fi
if [[ "$http_code" == "409" ]]; then
echo "gitea_create_repo: repo '$owner/$name' ya existe (409)" >&2
echo "$body"
return 0
fi
if [[ "$http_code" == "404" || "$http_code" == "422" ]]; then
echo "gitea_create_repo: org no encontrada (${http_code}), intentando en usuario..." >&2
response=$(curl -s -w "\n%{http_code}" \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: token ${GITEA_TOKEN}" \
-d "$payload" \
"${GITEA_URL}/api/v1/user/repos")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n -1)
if [[ "$http_code" == "201" ]]; then
echo "gitea_create_repo: repo '$owner/$name' creado en usuario" >&2
echo "$body"
return 0
fi
if [[ "$http_code" == "409" ]]; then
echo "gitea_create_repo: repo '$owner/$name' ya existe (409)" >&2
echo "$body"
return 0
fi
echo "gitea_create_repo: error al crear en usuario (HTTP ${http_code}): ${body}" >&2
return 1
fi
echo "gitea_create_repo: error inesperado (HTTP ${http_code}): ${body}" >&2
return 1
}
+51
View File
@@ -0,0 +1,51 @@
---
name: gitea_list_repos
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "gitea_list_repos(owner: string) -> string"
description: "Lista repositorios de un owner en Gitea. Intenta listar como org primero; si falla, lista como usuario. Imprime una línea por repo en formato name<TAB>html_url<TAB>description."
tags: [gitea, git, repo, list, org, user, api, infra]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: owner
desc: "nombre del usuario u organización cuyos repos se listan"
output: "una línea por repositorio con columnas separadas por tabulador: name, html_url, description"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/gitea_list_repos.sh"
---
## Ejemplo
```bash
source bash/functions/infra/gitea_list_repos.sh
export GITEA_URL="https://git.example.com"
export GITEA_TOKEN="$(pass agentes/dataforge-token)"
# Listar todos los repos de una org
gitea_list_repos "myorg"
# my-app https://git.example.com/myorg/my-app Mi aplicación principal
# infra https://git.example.com/myorg/infra
# Iterar sobre los repos
while IFS=$'\t' read -r name url desc; do
echo "Repo: $name$url"
done < <(gitea_list_repos "myorg")
```
## Notas
- Requiere `GITEA_URL` y `GITEA_TOKEN` seteadas.
- Usa `jq` si está disponible; fallback con grep/sed en caso contrario.
- El límite es 50 repos por página. Para owners con más de 50 repos habría que implementar paginación.
- Los mensajes informativos van a stderr; los datos tabulados van a stdout.
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
# gitea_list_repos — Lista repositorios de un owner (org o usuario) en Gitea
gitea_list_repos() {
local owner="$1"
if [[ -z "${GITEA_URL:-}" ]]; then
echo "gitea_list_repos: GITEA_URL no está seteada" >&2
return 1
fi
if [[ -z "${GITEA_TOKEN:-}" ]]; then
echo "gitea_list_repos: GITEA_TOKEN no está seteado" >&2
return 1
fi
if [[ -z "$owner" ]]; then
echo "gitea_list_repos: se requiere owner" >&2
return 1
fi
echo "gitea_list_repos: listando repos de '$owner' (intentando org)..." >&2
local response http_code body
response=$(curl -s -w "\n%{http_code}" \
-H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_URL}/api/v1/orgs/${owner}/repos?limit=50")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n -1)
if [[ "$http_code" != "200" ]]; then
echo "gitea_list_repos: org no encontrada (${http_code}), intentando usuario..." >&2
response=$(curl -s -w "\n%{http_code}" \
-H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_URL}/api/v1/users/${owner}/repos?limit=50")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n -1)
if [[ "$http_code" != "200" ]]; then
echo "gitea_list_repos: error listando repos de usuario (HTTP ${http_code}): ${body}" >&2
return 1
fi
fi
# Formatear salida como name\thtml_url\tdescription
if command -v jq &>/dev/null; then
echo "$body" | jq -r '.[] | [.name, .html_url, (.description // "")] | @tsv'
else
# Fallback sin jq: extraer campos básicos con grep/sed
echo "$body" | grep -o '"name":"[^"]*"\|"html_url":"[^"]*"\|"description":"[^"]*"' \
| paste - - - | sed 's/"name":"//;s/"html_url":"//;s/"description":"//;s/"//g'
fi
}
@@ -0,0 +1,55 @@
---
name: gitea_push_directory
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "gitea_push_directory(directory: string, owner: string, repo: string, branch: string) -> void"
description: "Inicializa git en un directorio local y lo sube a un repositorio Gitea existente. Si el directorio ya tiene .git, actualiza el remote y pushea cambios pendientes. Protege registry.db añadiéndolo al .gitignore antes del commit."
tags: [gitea, git, push, directory, sync, infra, repo]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: directory
desc: "ruta absoluta o relativa al directorio local a subir"
- name: owner
desc: "usuario u organización propietaria del repositorio Gitea destino"
- name: repo
desc: "nombre del repositorio Gitea destino (debe existir previamente)"
- name: branch
desc: "rama en la que hacer push (default: main)"
output: "vacío — efectos observables en el repositorio Gitea destino"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/gitea_push_directory.sh"
---
## Ejemplo
```bash
source bash/functions/infra/gitea_push_directory.sh
export GITEA_URL="https://git.example.com"
export GITEA_TOKEN="$(pass agentes/dataforge-token)"
# Subir directorio a repo existente
gitea_push_directory "/home/lucas/myproject" "myorg" "my-app"
# Subir a rama específica
gitea_push_directory "/home/lucas/myproject" "myorg" "my-app" "develop"
```
## Notas
- Requiere `GITEA_URL` y `GITEA_TOKEN` seteadas.
- El token se embebe en la URL del remote para autenticación (nunca se imprime a stdout/stderr, se enmascara con ***).
- Si `registry.db` existe en el directorio, se añade automáticamente al `.gitignore` local.
- Si el `.git` ya existe con un remote diferente, se redirige al repo indicado sin perder el historial local.
- Usa `--force-with-lease` para el primer push y fallback a push normal (para repos vacíos recién creados).
- El commit se firma con `agent@fn-registry` si no hay configuración git en el entorno.
@@ -0,0 +1,95 @@
#!/usr/bin/env bash
# gitea_push_directory — Inicializa git en un directorio y lo sube a un repo Gitea existente
gitea_push_directory() {
local directory="$1"
local owner="$2"
local repo="$3"
local branch="${4:-main}"
if [[ -z "${GITEA_URL:-}" ]]; then
echo "gitea_push_directory: GITEA_URL no está seteada" >&2
return 1
fi
if [[ -z "${GITEA_TOKEN:-}" ]]; then
echo "gitea_push_directory: GITEA_TOKEN no está seteado" >&2
return 1
fi
if [[ -z "$directory" || -z "$owner" || -z "$repo" ]]; then
echo "gitea_push_directory: se requieren directory, owner y repo" >&2
return 1
fi
if [[ ! -d "$directory" ]]; then
echo "gitea_push_directory: directorio '$directory' no existe" >&2
return 1
fi
# Construir URL con credenciales embebidas para autenticación
local gitea_host
gitea_host=$(echo "$GITEA_URL" | sed 's|https\?://||')
local remote_url="https://${GITEA_TOKEN}@${gitea_host}/${owner}/${repo}.git"
local display_url="https://***@${gitea_host}/${owner}/${repo}.git"
echo "gitea_push_directory: procesando '$directory' → '$owner/$repo' (rama: $branch)..." >&2
# Añadir registry.db al .gitignore local si existe en el directorio
if [[ -f "$directory/registry.db" ]]; then
echo "gitea_push_directory: añadiendo registry.db al .gitignore..." >&2
if [[ ! -f "$directory/.gitignore" ]] || ! grep -qxF "registry.db" "$directory/.gitignore"; then
echo "registry.db" >> "$directory/.gitignore"
fi
fi
# Gestionar estado del repositorio git
if [[ -d "$directory/.git" ]]; then
local existing_remote
existing_remote=$(git -C "$directory" remote get-url origin 2>/dev/null || echo "")
if [[ -z "$existing_remote" ]]; then
echo "gitea_push_directory: añadiendo remote origin..." >&2
git -C "$directory" remote add origin "$remote_url"
else
# Comparar remote sin token para detectar si apunta al mismo repo
local clean_existing
clean_existing=$(echo "$existing_remote" | sed 's|https://[^@]*@||;s|https://||')
local clean_target="${gitea_host}/${owner}/${repo}.git"
if [[ "$clean_existing" != "$clean_target" ]]; then
echo "gitea_push_directory: remote apunta a otro destino ('$clean_existing'), actualizando..." >&2
git -C "$directory" remote set-url origin "$remote_url"
else
echo "gitea_push_directory: remote ya apunta al destino correcto, actualizando token..." >&2
git -C "$directory" remote set-url origin "$remote_url"
fi
fi
else
echo "gitea_push_directory: inicializando nuevo repositorio git..." >&2
git -C "$directory" init
git -C "$directory" remote add origin "$remote_url"
fi
# Configurar rama por defecto
git -C "$directory" checkout -B "$branch" 2>/dev/null || true
# Añadir y commitear cambios si los hay
git -C "$directory" add -A
local status
status=$(git -C "$directory" status --porcelain)
if [[ -n "$status" ]]; then
echo "gitea_push_directory: commiteando cambios..." >&2
git -C "$directory" -c user.email="agent@fn-registry" -c user.name="fn-registry agent" \
commit -m "chore: sync from fn-registry agent"
else
echo "gitea_push_directory: sin cambios pendientes, solo haciendo push..." >&2
fi
echo "gitea_push_directory: haciendo push a $display_url..." >&2
git -C "$directory" push --set-upstream origin "$branch" --force-with-lease 2>&1 \
| sed "s|${GITEA_TOKEN}|***|g" >&2 \
|| git -C "$directory" push --set-upstream origin "$branch" 2>&1 \
| sed "s|${GITEA_TOKEN}|***|g" >&2
echo "gitea_push_directory: push completado a '$owner/$repo' rama '$branch'" >&2
}
@@ -0,0 +1,63 @@
---
name: install_android_sdk
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "install_android_sdk() -> void"
description: "Descarga e instala Android SDK command-line tools y JDK 17 localmente (sin root/sudo) en $ANDROID_SDK_DIR (default: $HOME/android-sdk). Idempotente: detecta instalacion existente y sale sin hacer nada. Genera env.sh con JAVA_HOME, ANDROID_HOME y PATH listos para hacer source."
tags: [android, sdk, jdk, java, install, infra, mobile]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params: []
output: "sin salida estructurada; imprime progreso y resumen final con rutas de instalacion"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/install_android_sdk.sh"
---
## Ejemplo
```bash
# Instalacion en directorio por defecto ($HOME/android-sdk)
source install_android_sdk.sh
# Instalacion en directorio personalizado
ANDROID_SDK_DIR=/opt/android source install_android_sdk.sh
# Si ya esta instalado:
# Android SDK ya instalado en: /home/user/android-sdk
# Instalacion completa imprime:
# Descargando JDK 17...
# JDK 17 instalado: /home/user/android-sdk/jdk-17/jdk-17.0.x+y
# Descargando Android cmdline-tools...
# cmdline-tools instalados
# Aceptando licencias de Android SDK...
# Instalando platform-tools, platforms;android-34, build-tools;34.0.0...
#
# Android SDK instalado en: /home/user/android-sdk
# JDK 17: /home/user/android-sdk/jdk-17/jdk-17.0.x+y
# Para activar: source /home/user/android-sdk/env.sh
# Activar entorno en sesion actual
source ~/android-sdk/env.sh
```
## Notas
Requiere `curl` y `unzip` (disponibles en la mayoria de distros Linux). No requiere root ni sudo.
El JDK se descarga desde Adoptium (Eclipse Temurin) via su API oficial. La URL de cmdline-tools apunta a la version 11076708 (2024). Si Google actualiza la version, cambiar la URL con el nuevo numero de build.
La reorganizacion del zip es necesaria porque Google distribuye cmdline-tools con estructura `cmdline-tools/bin/...` pero sdkmanager espera estar en `cmdline-tools/latest/bin/sdkmanager` para que Android Studio y otras herramientas lo detecten correctamente.
El archivo `env.sh` generado en `$ANDROID_SDK_DIR/env.sh` contiene las variables de entorno necesarias (`JAVA_HOME`, `ANDROID_HOME`, `ANDROID_SDK_ROOT`, `PATH`) y puede hacerse source desde `.bashrc`, `.zshrc` o desde scripts de CI.
Paquetes instalados: `platform-tools` (adb, fastboot), `platforms;android-34` (API 34), `build-tools;34.0.0`.
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env bash
# install_android_sdk — Descarga e instala Android SDK command-line tools y JDK 17
# localmente (sin root/sudo) en $ANDROID_SDK_DIR (default: $HOME/android-sdk).
set -euo pipefail
install_android_sdk() {
local sdk_dir="${ANDROID_SDK_DIR:-$HOME/android-sdk}"
local tmp_dir
tmp_dir="$(mktemp -d)"
# Limpia temporales al salir
trap 'rm -rf "$tmp_dir"' EXIT
# 1. Verifica si ya está instalado
if [[ -f "$sdk_dir/cmdline-tools/latest/bin/sdkmanager" ]]; then
if JAVA_HOME="$(ls -d "$sdk_dir"/jdk-17/jdk-17* 2>/dev/null | head -1)" \
"$sdk_dir/cmdline-tools/latest/bin/sdkmanager" --version &>/dev/null; then
echo "Android SDK ya instalado en: $sdk_dir"
return 0
fi
fi
mkdir -p "$sdk_dir"
# 2. Descarga JDK 17 si no existe
local jdk_dir
jdk_dir="$(ls -d "$sdk_dir"/jdk-17/jdk-17* 2>/dev/null | head -1 || true)"
if [[ -z "$jdk_dir" ]]; then
echo "Descargando JDK 17..."
local jdk_tar="$tmp_dir/jdk17.tar.gz"
local jdk_url="https://api.adoptium.net/v3/binary/latest/17/ga/linux/x64/jdk/hotspot/normal/eclipse"
if ! curl -fL --progress-bar -o "$jdk_tar" "$jdk_url"; then
echo "ERROR: fallo al descargar JDK 17 desde $jdk_url" >&2
return 1
fi
mkdir -p "$sdk_dir/jdk-17"
echo "Extrayendo JDK 17..."
if ! tar -xzf "$jdk_tar" -C "$sdk_dir/jdk-17"; then
echo "ERROR: fallo al extraer JDK 17" >&2
return 1
fi
jdk_dir="$(ls -d "$sdk_dir"/jdk-17/jdk-17* 2>/dev/null | head -1 || true)"
if [[ -z "$jdk_dir" ]]; then
echo "ERROR: no se encontro directorio jdk-17* tras la extraccion" >&2
return 1
fi
if ! JAVA_HOME="$jdk_dir" "$jdk_dir/bin/java" -version &>/dev/null; then
echo "ERROR: java -version fallo tras instalar JDK" >&2
return 1
fi
echo "JDK 17 instalado: $jdk_dir"
else
echo "JDK 17 ya presente: $jdk_dir"
fi
export JAVA_HOME="$jdk_dir"
# 3. Descarga Android cmdline-tools si no existen
if [[ ! -f "$sdk_dir/cmdline-tools/latest/bin/sdkmanager" ]]; then
echo "Descargando Android cmdline-tools..."
local tools_zip="$tmp_dir/cmdline-tools.zip"
local tools_url="https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip"
if ! curl -fL --progress-bar -o "$tools_zip" "$tools_url"; then
echo "ERROR: fallo al descargar Android cmdline-tools desde $tools_url" >&2
return 1
fi
local tools_tmp="$tmp_dir/cmdline-tools-extracted"
mkdir -p "$tools_tmp"
echo "Extrayendo cmdline-tools..."
if ! unzip -q "$tools_zip" -d "$tools_tmp"; then
echo "ERROR: fallo al extraer cmdline-tools" >&2
return 1
fi
# La estructura del zip es cmdline-tools/bin/..., reorganizar a cmdline-tools/latest/
mkdir -p "$sdk_dir/cmdline-tools"
if [[ -d "$tools_tmp/cmdline-tools" ]]; then
mv "$tools_tmp/cmdline-tools" "$sdk_dir/cmdline-tools/latest"
else
echo "ERROR: estructura inesperada en el zip de cmdline-tools" >&2
return 1
fi
if [[ ! -f "$sdk_dir/cmdline-tools/latest/bin/sdkmanager" ]]; then
echo "ERROR: sdkmanager no encontrado tras extraer cmdline-tools" >&2
return 1
fi
echo "cmdline-tools instalados"
else
echo "cmdline-tools ya presentes"
fi
local sdkmanager="$sdk_dir/cmdline-tools/latest/bin/sdkmanager"
export ANDROID_HOME="$sdk_dir"
export ANDROID_SDK_ROOT="$sdk_dir"
export PATH="$JAVA_HOME/bin:$sdk_dir/cmdline-tools/latest/bin:$sdk_dir/platform-tools:$PATH"
# 4. Acepta licencias e instala paquetes necesarios
echo "Aceptando licencias de Android SDK..."
if ! yes | "$sdkmanager" --licenses; then
echo "ERROR: fallo al aceptar licencias de Android SDK" >&2
return 1
fi
echo "Instalando platform-tools, platforms;android-34, build-tools;34.0.0..."
if ! "$sdkmanager" "platform-tools" "platforms;android-34" "build-tools;34.0.0"; then
echo "ERROR: fallo al instalar paquetes de Android SDK" >&2
return 1
fi
# 5. Genera archivo de entorno
local env_file="$sdk_dir/env.sh"
cat > "$env_file" <<EOF
export JAVA_HOME="$JAVA_HOME"
export ANDROID_HOME="$sdk_dir"
export ANDROID_SDK_ROOT="$sdk_dir"
export PATH="\$JAVA_HOME/bin:$sdk_dir/cmdline-tools/latest/bin:$sdk_dir/platform-tools:\$PATH"
EOF
# 6. Resumen final
echo ""
echo "Android SDK instalado en: $sdk_dir"
echo "JDK 17: $JAVA_HOME"
echo "Para activar: source $sdk_dir/env.sh"
}
install_android_sdk
+40
View File
@@ -0,0 +1,40 @@
---
name: install_mantine
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "install_mantine(project_dir: string) -> void"
description: "Instala Mantine UI con todas sus dependencias (@mantine/core, hooks, charts, notifications, form) y PostCSS en un proyecto frontend. Detecta package manager por lockfile. Genera postcss.config.cjs si no existe. Idempotente."
tags: [mantine, frontend, install, react, ui, postcss]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: project_dir
desc: "directorio del proyecto frontend con package.json"
output: "sin salida; muestra progreso de instalación"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/install_mantine.sh"
---
## Ejemplo
```bash
# Instalar Mantine en un proyecto con pnpm
source install_mantine.sh
install_mantine ./apps/rapid_dashboards/frontend
# Uso directo
bash install_mantine.sh ./frontend
```
## Notas
Detecta el package manager por lockfile: pnpm-lock.yaml → pnpm, yarn.lock → yarn, package-lock.json → npm. Instala las dependencias core de Mantine v7+ y el stack PostCSS necesario. Si postcss.config.cjs ya existe no lo sobreescribe.
+97
View File
@@ -0,0 +1,97 @@
# install_mantine
# ---------------
# Instala dependencias de Mantine UI en un proyecto frontend.
# Detecta package manager por lockfile (pnpm > yarn > npm).
# Genera postcss.config.cjs si no existe.
# Idempotente: no reinstala si ya estan presentes.
#
# USO (sourced):
# source install_mantine.sh
# install_mantine /path/to/frontend
#
# USO (directo):
# bash install_mantine.sh /path/to/frontend
install_mantine() {
local project_dir="$1"
if [ -z "$project_dir" ]; then
echo "install_mantine: se requiere project_dir" >&2
return 1
fi
if [ ! -f "$project_dir/package.json" ]; then
echo "install_mantine: no existe package.json en $project_dir" >&2
return 1
fi
# Detectar package manager
local pm="npm"
local add_cmd="install"
local add_dev_flag="--save-dev"
if [ -f "$project_dir/pnpm-lock.yaml" ] || [ -f "$project_dir/pnpm-workspace.yaml" ]; then
pm="pnpm"
add_cmd="add"
add_dev_flag="-D"
elif [ -f "$project_dir/yarn.lock" ]; then
pm="yarn"
add_cmd="add"
add_dev_flag="--dev"
elif [ -f "$project_dir/package-lock.json" ]; then
pm="npm"
add_cmd="install"
add_dev_flag="--save-dev"
fi
echo "Detectado package manager: $pm"
# Dependencias runtime
local runtime_deps="@mantine/core @mantine/hooks @mantine/charts @mantine/notifications @mantine/form"
echo "Instalando dependencias Mantine..."
(cd "$project_dir" && $pm $add_cmd $runtime_deps 2>&1)
if [ $? -ne 0 ]; then
echo "install_mantine: fallo instalando dependencias runtime" >&2
return 1
fi
# Dependencias PostCSS (dev)
local dev_deps="postcss postcss-preset-mantine postcss-simple-vars"
echo "Instalando dependencias PostCSS..."
(cd "$project_dir" && $pm $add_cmd $add_dev_flag $dev_deps 2>&1)
if [ $? -ne 0 ]; then
echo "install_mantine: fallo instalando dependencias PostCSS" >&2
return 1
fi
# Generar postcss.config.cjs si no existe
if [ ! -f "$project_dir/postcss.config.cjs" ]; then
echo "Generando postcss.config.cjs..."
cat > "$project_dir/postcss.config.cjs" << 'POSTCSS'
module.exports = {
plugins: {
'postcss-preset-mantine': {},
'postcss-simple-vars': {
variables: {
'mantine-breakpoint-xs': '36em',
'mantine-breakpoint-sm': '48em',
'mantine-breakpoint-md': '62em',
'mantine-breakpoint-lg': '75em',
'mantine-breakpoint-xl': '88em',
},
},
},
};
POSTCSS
echo "postcss.config.cjs creado"
else
echo "postcss.config.cjs ya existe, no se sobreescribe"
fi
echo "Mantine instalado correctamente en $project_dir"
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
install_mantine "$@"
fi