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 f4932ce64c
commit 89e443ab18
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
@@ -0,0 +1,76 @@
---
name: capacitor_build_apk
kind: pipeline
lang: bash
domain: pipelines
version: "1.0.0"
purity: impure
signature: "capacitor_build_apk(web_app_dir: string, [app_id: string], [app_name: string]) -> void"
description: "Pipeline que convierte una web app en un APK de Android usando Capacitor. Valida el entorno (ANDROID_HOME, Java 17+), construye el bundle web si no existe dist/, inicializa Capacitor si no está configurado, añade la plataforma Android, sincroniza y compila el APK con Gradle. El APK final queda en el directorio raíz de la web app."
tags: [android, apk, capacitor, mobile, build, pipeline, bash]
uses_functions:
- install_android_sdk_bash_infra
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: web_app_dir
desc: "directorio raíz de la web app; debe contener package.json; si no existe dist/ se ejecuta pnpm build automáticamente"
- name: app_id
desc: "identificador de la app Android en formato reverse-DNS (default: com.fnregistry.app)"
- name: app_name
desc: "nombre visible de la app Android; si se omite, se lee del campo name de package.json"
output: "APK de debug en <web_app_dir>/<app_name>.apk; imprime ruta y tamaño en MB al finalizar"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/pipelines/capacitor_build_apk.sh"
---
## Ejemplo
```bash
# Build con defaults (app-id y app-name desde package.json)
./bash/functions/pipelines/capacitor_build_apk.sh ~/projects/my-web-app
# Build especificando app-id y app-name
./bash/functions/pipelines/capacitor_build_apk.sh ~/projects/my-web-app \
--app-id com.miempresa.miapp \
--app-name "Mi Aplicación"
```
## Flujo
1. **Validación** — verifica que `web_app_dir` existe, tiene `package.json`, que `ANDROID_HOME` está seteado (o sourcea `$HOME/android-sdk/env.sh`) y que Java 17+ está disponible.
2. **Build web** — si no existe `dist/`, ejecuta `pnpm build` en el directorio de la app.
3. **Init Capacitor** — si no existe `capacitor.config.ts`, instala `@capacitor/core`, `@capacitor/cli` y `@capacitor/android` via npm y genera el archivo de configuración con el `appId`, `appName` y `webDir: dist`.
4. **Add Android** — si no existe el directorio `android/`, ejecuta `npx cap add android`.
5. **Sync** — ejecuta `npx cap sync android` para copiar los assets web al proyecto Android.
6. **Build APK** — ejecuta `./gradlew assembleDebug` desde `android/`; si falla sale con exit 1.
7. **Copia APK** — copia `android/app/build/outputs/apk/debug/app-debug.apk` a `<web_app_dir>/<app_name>.apk`.
8. **Resultado** — imprime la ruta del APK y su tamaño en MB.
## Requisitos
- **Node.js** y **pnpm** disponibles en PATH
- **Java 17+** disponible en PATH
- **Android SDK** instalado: `ANDROID_HOME` seteado, o bien `$HOME/android-sdk/env.sh` existente (generado por `install_android_sdk`)
- **Gradle wrapper** presente en el directorio `android/` (generado por `cap add android`)
## Notas
El pipeline usa `set -euo pipefail` — cualquier fallo detiene la ejecución inmediatamente.
El APK generado es un **debug build**, apto para desarrollo y pruebas. Para publicar en Play Store se necesita un release build firmado (`assembleRelease` con un keystore).
`install_android_sdk_bash_infra` se referencia como dependencia previa: el usuario debe haberlo ejecutado (o haber instalado el SDK manualmente) antes de invocar este pipeline.
La detección del `app_name` desde `package.json` usa `node -e` inline, lo que requiere que Node.js esté disponible. Si el campo `name` no existe en el JSON, se usa el valor por defecto `app`.
Para instalar el APK en un dispositivo Android conectado por USB (con depuración USB activada):
```bash
adb install <web_app_dir>/<app_name>.apk
```
@@ -0,0 +1,208 @@
#!/usr/bin/env bash
# capacitor_build_apk
# -------------------
# Pipeline que convierte una web app buildeada en un APK de Android usando Capacitor.
# Asume que el Android SDK está instalado (via install_android_sdk o manualmente).
#
# USO:
# ./capacitor_build_apk.sh <web_app_dir> [--app-id com.example.app] [--app-name "My App"]
#
# ARGUMENTOS:
# web_app_dir Directorio de la web app (debe contener package.json)
# --app-id ID de la app Android (default: com.fnregistry.app)
# --app-name Nombre visible de la app (default: name de package.json)
#
# REQUISITOS:
# - Node.js + pnpm instalados en PATH
# - Java 17+ instalado en PATH
# - Android SDK: ANDROID_HOME seteado o $HOME/android-sdk/env.sh disponible
set -euo pipefail
# ---------------------------------------------------------------------------
# Parseo de argumentos
# ---------------------------------------------------------------------------
WEB_APP_DIR=""
APP_ID="com.fnregistry.app"
APP_NAME=""
while [[ $# -gt 0 ]]; do
case "$1" in
--app-id)
APP_ID="$2"
shift 2
;;
--app-name)
APP_NAME="$2"
shift 2
;;
-*)
echo "[capacitor_build_apk] ERROR: argumento desconocido: $1" >&2
echo "USO: $0 <web_app_dir> [--app-id com.example.app] [--app-name \"My App\"]" >&2
exit 1
;;
*)
WEB_APP_DIR="$1"
shift
;;
esac
done
if [[ -z "$WEB_APP_DIR" ]]; then
echo "[capacitor_build_apk] ERROR: web_app_dir es obligatorio." >&2
echo "USO: $0 <web_app_dir> [--app-id com.example.app] [--app-name \"My App\"]" >&2
exit 1
fi
# ---------------------------------------------------------------------------
# 1. Validación
# ---------------------------------------------------------------------------
echo "[capacitor_build_apk] Validando entorno..."
# Verificar que web_app_dir existe y tiene package.json
if [[ ! -d "$WEB_APP_DIR" ]]; then
echo "[capacitor_build_apk] ERROR: directorio no existe: $WEB_APP_DIR" >&2
exit 1
fi
if [[ ! -f "$WEB_APP_DIR/package.json" ]]; then
echo "[capacitor_build_apk] ERROR: no se encontró package.json en $WEB_APP_DIR" >&2
exit 1
fi
# Resolver app name desde package.json si no se pasó
if [[ -z "$APP_NAME" ]]; then
APP_NAME=$(node -e "const p = require('$WEB_APP_DIR/package.json'); process.stdout.write(p.name || 'app');" 2>/dev/null || echo "app")
echo "[capacitor_build_apk] App name detectado desde package.json: $APP_NAME"
fi
# Verificar ANDROID_HOME o sourcea env.sh
if [[ -z "${ANDROID_HOME:-}" ]]; then
ANDROID_ENV="$HOME/android-sdk/env.sh"
if [[ -f "$ANDROID_ENV" ]]; then
echo "[capacitor_build_apk] ANDROID_HOME no seteado, sourceando $ANDROID_ENV ..."
# shellcheck source=/dev/null
source "$ANDROID_ENV"
else
echo "[capacitor_build_apk] ERROR: ANDROID_HOME no está seteado y no se encontró $ANDROID_ENV" >&2
echo " Instala el SDK con install_android_sdk o setea ANDROID_HOME manualmente." >&2
exit 1
fi
fi
echo "[capacitor_build_apk] ANDROID_HOME: $ANDROID_HOME"
# Verificar Java 17+
if ! command -v java &>/dev/null; then
echo "[capacitor_build_apk] ERROR: java no está en PATH." >&2
exit 1
fi
JAVA_VERSION=$(java -version 2>&1 | head -1 | grep -oP '(?<=version ")([0-9]+)' | head -1 || echo "0")
if [[ "$JAVA_VERSION" -lt 17 ]]; then
echo "[capacitor_build_apk] ERROR: se requiere Java 17+. Versión detectada: $JAVA_VERSION" >&2
exit 1
fi
echo "[capacitor_build_apk] Java $JAVA_VERSION detectado."
# ---------------------------------------------------------------------------
# 2. Build web (si no existe dist/)
# ---------------------------------------------------------------------------
if [[ ! -d "$WEB_APP_DIR/dist" ]]; then
echo "[capacitor_build_apk] No se encontró dist/, ejecutando pnpm build..."
(cd "$WEB_APP_DIR" && pnpm build)
echo "[capacitor_build_apk] Build web completado."
else
echo "[capacitor_build_apk] dist/ ya existe, omitiendo build web."
fi
# ---------------------------------------------------------------------------
# 3. Init Capacitor (si no existe capacitor.config.ts)
# ---------------------------------------------------------------------------
if [[ ! -f "$WEB_APP_DIR/capacitor.config.ts" ]]; then
echo "[capacitor_build_apk] Instalando dependencias de Capacitor..."
(cd "$WEB_APP_DIR" && npm install @capacitor/core @capacitor/cli @capacitor/android)
echo "[capacitor_build_apk] Generando capacitor.config.ts..."
cat > "$WEB_APP_DIR/capacitor.config.ts" <<CAPCONFIG
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: '${APP_ID}',
appName: '${APP_NAME}',
webDir: 'dist',
server: { androidScheme: 'https' }
};
export default config;
CAPCONFIG
echo "[capacitor_build_apk] capacitor.config.ts generado."
else
echo "[capacitor_build_apk] capacitor.config.ts ya existe, omitiendo init."
fi
# ---------------------------------------------------------------------------
# 4. Add Android (si no existe el directorio android/)
# ---------------------------------------------------------------------------
if [[ ! -d "$WEB_APP_DIR/android" ]]; then
echo "[capacitor_build_apk] Añadiendo plataforma Android..."
(cd "$WEB_APP_DIR" && npx cap add android)
echo "[capacitor_build_apk] Plataforma Android añadida."
else
echo "[capacitor_build_apk] Directorio android/ ya existe, omitiendo cap add."
fi
# ---------------------------------------------------------------------------
# 5. Sync
# ---------------------------------------------------------------------------
echo "[capacitor_build_apk] Sincronizando assets web con Android..."
(cd "$WEB_APP_DIR" && npx cap sync android)
echo "[capacitor_build_apk] Sync completado."
# ---------------------------------------------------------------------------
# 6. Build APK
# ---------------------------------------------------------------------------
echo "[capacitor_build_apk] Compilando APK con Gradle..."
if ! (cd "$WEB_APP_DIR/android" && ./gradlew assembleDebug); then
echo "[capacitor_build_apk] ERROR: Gradle falló. Revisa los logs anteriores." >&2
exit 1
fi
APK_SOURCE="$WEB_APP_DIR/android/app/build/outputs/apk/debug/app-debug.apk"
if [[ ! -f "$APK_SOURCE" ]]; then
echo "[capacitor_build_apk] ERROR: Gradle terminó sin error pero no se encontró el APK en $APK_SOURCE" >&2
exit 1
fi
# ---------------------------------------------------------------------------
# 7. Copia APK al directorio raíz
# ---------------------------------------------------------------------------
APK_DEST="$WEB_APP_DIR/${APP_NAME}.apk"
cp "$APK_SOURCE" "$APK_DEST"
# ---------------------------------------------------------------------------
# 8. Resultado
# ---------------------------------------------------------------------------
APK_SIZE_BYTES=$(stat -c%s "$APK_DEST" 2>/dev/null || stat -f%z "$APK_DEST" 2>/dev/null || echo "0")
APK_SIZE_MB=$(awk "BEGIN {printf \"%.1f\", $APK_SIZE_BYTES/1048576}")
echo ""
echo "---------------------------------------------------------------------"
echo "APK generado: $APK_DEST"
echo "Tamaño: ${APK_SIZE_MB} MB"
echo ""
echo "Para instalar en un dispositivo conectado por USB:"
echo " adb install '$APK_DEST'"
echo "---------------------------------------------------------------------"
@@ -0,0 +1,67 @@
---
name: gitea_init_app
kind: pipeline
lang: bash
domain: pipelines
version: "1.0.0"
purity: impure
signature: "gitea_init_app(directory: string, owner: string, name: string, private: string) -> string"
description: "Pipeline que crea un repositorio en Gitea, sube el directorio local y añade a egutierrez como colaborador admin. Compone gitea_create_repo → gitea_push_directory → gitea_add_collaborator."
tags: [gitea, git, pipeline, repo, create, push, launcher, infra]
uses_functions:
- gitea_create_repo_bash_infra
- gitea_push_directory_bash_infra
- gitea_add_collaborator_bash_infra
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: directory
desc: "ruta al directorio local a subir como repositorio"
- name: owner
desc: "usuario u organización en Gitea que será propietaria del repo"
- name: name
desc: "nombre del repositorio (opcional: se infiere del basename del directorio)"
- name: private
desc: "si el repo debe ser privado, 'true' o 'false' (default: false)"
output: "URL del repositorio creado en Gitea"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/pipelines/gitea_init_app.sh"
---
## Ejemplo
```bash
export GITEA_URL="$(pass agentes/gitea-url)"
export GITEA_TOKEN="$(pass agentes/dataforge-token)"
# Crear repo con nombre inferido del directorio
bash bash/functions/pipelines/gitea_init_app.sh /home/lucas/myapp myorg
# Nombre explícito y repo privado
bash bash/functions/pipelines/gitea_init_app.sh /home/lucas/myapp myorg my-custom-name true
# Con flags
bash bash/functions/pipelines/gitea_init_app.sh \
--directory /home/lucas/myapp \
--owner myorg \
--name my-app \
--private true
```
## Pasos del pipeline
1. `gitea_create_repo owner name private` — crea el repo (idempotente si ya existe)
2. `gitea_push_directory directory owner repo` — inicializa git y hace push del directorio
3. `gitea_add_collaborator owner repo egutierrez admin` — añade colaborador con permisos admin
## Notas
- Requiere `GITEA_URL` y `GITEA_TOKEN` seteadas.
- Si el repo ya existe (409), el pipeline continúa con el push y añade el colaborador.
- El colaborador `egutierrez` es fijo en el pipeline — para variarlo usar las funciones individuales.
- La URL del repo se imprime a stdout al finalizar.
@@ -0,0 +1,79 @@
#!/usr/bin/env bash
# Pipeline: gitea_init_app — Crea repo en Gitea, sube directorio y añade colaborador egutierrez
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../infra/gitea_create_repo.sh"
source "$SCRIPT_DIR/../infra/gitea_push_directory.sh"
source "$SCRIPT_DIR/../infra/gitea_add_collaborator.sh"
main() {
local directory=""
local owner=""
local name=""
local private="false"
# Parsear argumentos
while [[ $# -gt 0 ]]; do
case "$1" in
--directory) directory="$2"; shift 2 ;;
--owner) owner="$2"; shift 2 ;;
--name) name="$2"; shift 2 ;;
--private) private="$2"; shift 2 ;;
*)
# Argumentos posicionales: directory owner [name] [private]
if [[ -z "$directory" ]]; then
directory="$1"
elif [[ -z "$owner" ]]; then
owner="$1"
elif [[ -z "$name" ]]; then
name="$1"
else
private="$1"
fi
shift
;;
esac
done
if [[ -z "$directory" || -z "$owner" ]]; then
echo "gitea_init_app: uso: gitea_init_app <directory> <owner> [name] [private]" >&2
echo "gitea_init_app: o con flags: --directory <dir> --owner <owner> [--name <name>] [--private true]" >&2
return 1
fi
# Inferir nombre del repo desde basename del directorio si no se especificó
if [[ -z "$name" ]]; then
name=$(basename "$directory")
echo "gitea_init_app: nombre inferido del directorio: '$name'" >&2
fi
if [[ -z "${GITEA_URL:-}" ]]; then
echo "gitea_init_app: GITEA_URL no está seteada" >&2
return 1
fi
if [[ -z "${GITEA_TOKEN:-}" ]]; then
echo "gitea_init_app: GITEA_TOKEN no está seteado" >&2
return 1
fi
echo "gitea_init_app: iniciando pipeline para '$owner/$name'..." >&2
echo "gitea_init_app: directorio fuente: '$directory'" >&2
# Paso 1: Crear repo
echo "gitea_init_app: [1/3] creando repositorio..." >&2
gitea_create_repo "$owner" "$name" "$private" "" > /dev/null
# Paso 2: Subir directorio
echo "gitea_init_app: [2/3] subiendo directorio al repositorio..." >&2
gitea_push_directory "$directory" "$owner" "$name"
# Paso 3: Añadir colaborador egutierrez con permisos admin
echo "gitea_init_app: [3/3] añadiendo colaborador egutierrez..." >&2
gitea_add_collaborator "$owner" "$name" "egutierrez" "admin"
echo "gitea_init_app: pipeline completado — ${GITEA_URL}/${owner}/${name}" >&2
echo "${GITEA_URL}/${owner}/${name}"
}
main "$@"