From 01042bc23c94a4e771844cc18a2b8c1439491165 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Mon, 6 Apr 2026 23:47:10 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20add=20bash=20infra=20functions=20?= =?UTF-8?q?=E2=80=94=20Gitea,=20Android=20SDK,=20Mantine,=20Capacitor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- bash/functions/infra/frontend_doctor.md | 54 +++++ bash/functions/infra/frontend_doctor.sh | 150 +++++++++++++ .../functions/infra/gitea_add_collaborator.md | 53 +++++ .../functions/infra/gitea_add_collaborator.sh | 52 +++++ bash/functions/infra/gitea_create_repo.md | 56 +++++ bash/functions/infra/gitea_create_repo.sh | 83 +++++++ bash/functions/infra/gitea_list_repos.md | 51 +++++ bash/functions/infra/gitea_list_repos.sh | 53 +++++ bash/functions/infra/gitea_push_directory.md | 55 +++++ bash/functions/infra/gitea_push_directory.sh | 95 ++++++++ bash/functions/infra/install_android_sdk.md | 63 ++++++ bash/functions/infra/install_android_sdk.sh | 134 +++++++++++ bash/functions/infra/install_mantine.md | 40 ++++ bash/functions/infra/install_mantine.sh | 97 ++++++++ .../pipelines/capacitor_build_apk.md | 76 +++++++ .../pipelines/capacitor_build_apk.sh | 208 ++++++++++++++++++ bash/functions/pipelines/gitea_init_app.md | 67 ++++++ bash/functions/pipelines/gitea_init_app.sh | 79 +++++++ 18 files changed, 1466 insertions(+) create mode 100644 bash/functions/infra/frontend_doctor.md create mode 100644 bash/functions/infra/frontend_doctor.sh create mode 100644 bash/functions/infra/gitea_add_collaborator.md create mode 100644 bash/functions/infra/gitea_add_collaborator.sh create mode 100644 bash/functions/infra/gitea_create_repo.md create mode 100644 bash/functions/infra/gitea_create_repo.sh create mode 100644 bash/functions/infra/gitea_list_repos.md create mode 100644 bash/functions/infra/gitea_list_repos.sh create mode 100644 bash/functions/infra/gitea_push_directory.md create mode 100644 bash/functions/infra/gitea_push_directory.sh create mode 100644 bash/functions/infra/install_android_sdk.md create mode 100644 bash/functions/infra/install_android_sdk.sh create mode 100644 bash/functions/infra/install_mantine.md create mode 100644 bash/functions/infra/install_mantine.sh create mode 100644 bash/functions/pipelines/capacitor_build_apk.md create mode 100644 bash/functions/pipelines/capacitor_build_apk.sh create mode 100644 bash/functions/pipelines/gitea_init_app.md create mode 100644 bash/functions/pipelines/gitea_init_app.sh diff --git a/bash/functions/infra/frontend_doctor.md b/bash/functions/infra/frontend_doctor.md new file mode 100644 index 00000000..4b980a76 --- /dev/null +++ b/bash/functions/infra/frontend_doctor.md @@ -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. diff --git a/bash/functions/infra/frontend_doctor.sh b/bash/functions/infra/frontend_doctor.sh new file mode 100644 index 00000000..fc5b2c51 --- /dev/null +++ b/bash/functions/infra/frontend_doctor.sh @@ -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 diff --git a/bash/functions/infra/gitea_add_collaborator.md b/bash/functions/infra/gitea_add_collaborator.md new file mode 100644 index 00000000..fb7891fb --- /dev/null +++ b/bash/functions/infra/gitea_add_collaborator.md @@ -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. diff --git a/bash/functions/infra/gitea_add_collaborator.sh b/bash/functions/infra/gitea_add_collaborator.sh new file mode 100644 index 00000000..98a0b2ec --- /dev/null +++ b/bash/functions/infra/gitea_add_collaborator.sh @@ -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 +} diff --git a/bash/functions/infra/gitea_create_repo.md b/bash/functions/infra/gitea_create_repo.md new file mode 100644 index 00000000..82da9964 --- /dev/null +++ b/bash/functions/infra/gitea_create_repo.md @@ -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. diff --git a/bash/functions/infra/gitea_create_repo.sh b/bash/functions/infra/gitea_create_repo.sh new file mode 100644 index 00000000..17059454 --- /dev/null +++ b/bash/functions/infra/gitea_create_repo.sh @@ -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 +} diff --git a/bash/functions/infra/gitea_list_repos.md b/bash/functions/infra/gitea_list_repos.md new file mode 100644 index 00000000..ac3e9f08 --- /dev/null +++ b/bash/functions/infra/gitea_list_repos.md @@ -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 namehtml_urldescription." +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. diff --git a/bash/functions/infra/gitea_list_repos.sh b/bash/functions/infra/gitea_list_repos.sh new file mode 100644 index 00000000..91a6695f --- /dev/null +++ b/bash/functions/infra/gitea_list_repos.sh @@ -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 +} diff --git a/bash/functions/infra/gitea_push_directory.md b/bash/functions/infra/gitea_push_directory.md new file mode 100644 index 00000000..5f2d4b5f --- /dev/null +++ b/bash/functions/infra/gitea_push_directory.md @@ -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. diff --git a/bash/functions/infra/gitea_push_directory.sh b/bash/functions/infra/gitea_push_directory.sh new file mode 100644 index 00000000..f74da12f --- /dev/null +++ b/bash/functions/infra/gitea_push_directory.sh @@ -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 +} diff --git a/bash/functions/infra/install_android_sdk.md b/bash/functions/infra/install_android_sdk.md new file mode 100644 index 00000000..b8abcdc4 --- /dev/null +++ b/bash/functions/infra/install_android_sdk.md @@ -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`. diff --git a/bash/functions/infra/install_android_sdk.sh b/bash/functions/infra/install_android_sdk.sh new file mode 100644 index 00000000..fd65f27d --- /dev/null +++ b/bash/functions/infra/install_android_sdk.sh @@ -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" < 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. diff --git a/bash/functions/infra/install_mantine.sh b/bash/functions/infra/install_mantine.sh new file mode 100644 index 00000000..2f1ab6d7 --- /dev/null +++ b/bash/functions/infra/install_mantine.sh @@ -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 diff --git a/bash/functions/pipelines/capacitor_build_apk.md b/bash/functions/pipelines/capacitor_build_apk.md new file mode 100644 index 00000000..faba8813 --- /dev/null +++ b/bash/functions/pipelines/capacitor_build_apk.md @@ -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 /.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 `/.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 /.apk +``` diff --git a/bash/functions/pipelines/capacitor_build_apk.sh b/bash/functions/pipelines/capacitor_build_apk.sh new file mode 100644 index 00000000..7929a2a1 --- /dev/null +++ b/bash/functions/pipelines/capacitor_build_apk.sh @@ -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 [--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 [--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 [--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" <&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 "---------------------------------------------------------------------" diff --git a/bash/functions/pipelines/gitea_init_app.md b/bash/functions/pipelines/gitea_init_app.md new file mode 100644 index 00000000..d6dfeeee --- /dev/null +++ b/bash/functions/pipelines/gitea_init_app.md @@ -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. diff --git a/bash/functions/pipelines/gitea_init_app.sh b/bash/functions/pipelines/gitea_init_app.sh new file mode 100644 index 00000000..2a4dc920 --- /dev/null +++ b/bash/functions/pipelines/gitea_init_app.sh @@ -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 [name] [private]" >&2 + echo "gitea_init_app: o con flags: --directory --owner [--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 "$@"