From dc0ebe8a49552279970d28e15ca52a265facd883 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 18 Apr 2026 17:52:34 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20init=5Fweb=5Fapp=20bash=20pipeline=20?= =?UTF-8?q?=E2=80=94=20scaffold=20Go=20API=20+=20React=20frontend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extiende init_api_app anadiendo la capa frontend: pnpm + vite + react + @mantine/core. Genera frontend/ con vite.config.ts (proxy /api y /health al backend + alias @fn_library a frontend/functions/ui), src/main.tsx con MantineProvider, src/App.tsx con AppShell y src/pages/Home.tsx consumiendo /api/v1/status. Flags: --port N, --with-auth, --with-db (delegadas a init_api_app). Docker compose para desarrollo. Verifica con pnpm install && pnpm build si pnpm esta disponible (skippable con SKIP_PNPM_BUILD=true). Co-Authored-By: Claude Opus 4.7 (1M context) --- bash/functions/pipelines/init_web_app.md | 119 ++++++ bash/functions/pipelines/init_web_app.sh | 466 +++++++++++++++++++++++ 2 files changed, 585 insertions(+) create mode 100644 bash/functions/pipelines/init_web_app.md create mode 100755 bash/functions/pipelines/init_web_app.sh diff --git a/bash/functions/pipelines/init_web_app.md b/bash/functions/pipelines/init_web_app.md new file mode 100644 index 00000000..26a2420a --- /dev/null +++ b/bash/functions/pipelines/init_web_app.md @@ -0,0 +1,119 @@ +--- +name: init_web_app +kind: pipeline +lang: bash +domain: pipelines +version: "1.0.0" +purity: impure +signature: "init_web_app(nombre: string, [--port N], [--with-auth], [--with-db]) -> void" +description: "Scaffold de full-stack app: Go HTTP API backend + React frontend con Mantine y @fn_library. Extiende init_api_app anadiendo frontend/ con pnpm + vite + react + mantine. Genera vite.config.ts con proxy al backend y alias @fn_library, src/main.tsx con MantineProvider, src/App.tsx con AppShell, src/pages/Home.tsx con ejemplo consumiendo /api/v1/status." +tags: [init, scaffold, web, fullstack, frontend, pipeline, bash, launcher] +uses_functions: + - assert_command_exists_bash_shell + - http_serve_go_infra + - http_router_go_infra + - http_middleware_chain_go_infra + - http_logger_middleware_go_infra + - http_cors_middleware_go_infra + - http_json_response_go_infra + - http_error_response_go_infra + - migration_up_go_infra + - mantine_provider_ts_ui + - app_shell_ts_ui +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: nombre + desc: "nombre de la app a crear (apps/{nombre}/)" + - name: "--port" + desc: "puerto del backend HTTP (default 8080); el frontend usa 5173 con proxy" + - name: "--with-auth" + desc: "anade jwt_middleware + handlers login/register + tabla users al backend" + - name: "--with-db" + desc: "anade store.go con helpers CRUD y setup SQLite al backend" +output: "app full-stack en apps/{nombre}/ con backend Go (main.go) y frontend/ (vite + react + mantine). Dev: terminal 1 go run .; terminal 2 cd frontend && pnpm dev." +tested: false +tests: [] +test_file_path: "" +example: "fn run init_web_app my_dashboard --with-auth" +file_path: "bash/functions/pipelines/init_web_app.sh" +--- + +## Sinopsis + +```bash +fn run init_web_app [--port N] [--with-auth] [--with-db] +``` + +## Ejemplo rapido + +```bash +fn run init_web_app inventory_dashboard --with-auth +cd apps/inventory_dashboard +make install # pnpm install del frontend +# Terminal 1: +CGO_ENABLED=1 go run . +# Terminal 2: +cd frontend && pnpm dev +# → http://localhost:5173 (frontend) proxea a :8080 (backend) +``` + +## Archivos generados + +Todos los de `init_api_app`, mas: + +| Archivo | Descripcion | +|---------|-------------| +| `frontend/package.json` | pnpm, vite, react, @mantine/core, @tabler/icons-react | +| `frontend/vite.config.ts` | Proxy `/api` y `/health` al backend + alias `@fn_library` | +| `frontend/tsconfig.json` | TS strict con paths `@fn_library/*` | +| `frontend/index.html` | Entry HTML minimo | +| `frontend/postcss.config.cjs` | postcss-preset-mantine + breakpoints | +| `frontend/src/main.tsx` | Root con `MantineProvider` + theme | +| `frontend/src/theme.ts` | `createTheme()` con primaryColor | +| `frontend/src/App.tsx` | `AppShell` con Burger + Navbar + Header | +| `frontend/src/pages/Home.tsx` | Pagina ejemplo que consume `/api/v1/status` | +| `docker-compose.yml` | Services: api + frontend (node alpine) | +| `Makefile` | Targets `install`, `build-frontend`, `build`, `dev` | +| `app.md` | Framework: `net/http + vite + react + mantine` | + +## Flags + +| Flag | Efecto | +|------|--------| +| `--port N` | Puerto del backend Go (default: 8080) — frontend Vite siempre en 5173 | +| `--with-auth` | JWT + tabla users al backend | +| `--with-db` | Store + SQLite setup al backend | + +## Post-setup + +```bash +cd apps/{nombre} +cp .env.example .env +make install # pnpm install del frontend + +# Desarrollo (2 terminales): +CGO_ENABLED=1 go run . # Terminal 1: backend :PORT +cd frontend && pnpm dev # Terminal 2: frontend :5173 + +# Produccion: +make build # build frontend + binario Go +./ # sirve todo en :PORT +``` + +## Notas + +Pipeline impuro: invoca primero `init_api_app` para el backend y luego +escribe el frontend. Si pnpm esta disponible y `SKIP_PNPM_BUILD` no es +`true`, ejecuta `pnpm install && pnpm build` como verificacion final. + +El alias `@fn_library` en `vite.config.ts` apunta a +`../../../frontend/functions/ui` (relativo desde `apps/{nombre}/frontend/`). +Los componentes del registry se consumen sin duplicarlos. + +Si `apps/{nombre}/` ya existe, aborta sin sobrescribir. + +El tag `launcher` permite que aparezca en el Pipeline Launcher TUI. diff --git a/bash/functions/pipelines/init_web_app.sh b/bash/functions/pipelines/init_web_app.sh new file mode 100755 index 00000000..7709cfe1 --- /dev/null +++ b/bash/functions/pipelines/init_web_app.sh @@ -0,0 +1,466 @@ +#!/usr/bin/env bash +# init_web_app +# ------------ +# Scaffold de full-stack app: Go HTTP API backend + React frontend con +# Mantine y @fn_library. Extiende init_api_app anadiendo la capa frontend. +# +# Genera todo lo de init_api_app mas frontend/ con pnpm + vite + react + +# mantine, vite.config.ts con alias @fn_library y proxy al backend, +# src/main.tsx con FnMantineProvider, src/App.tsx, src/theme.ts y +# src/pages/Home.tsx de ejemplo. +# +# USO: +# ./init_web_app.sh [--port N] [--with-auth] [--with-db] +# +# EJEMPLO: +# ./init_web_app.sh my_dashboard +# ./init_web_app.sh my_dashboard --port 8080 --with-auth + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REGISTRY_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +# Source funciones atomicas +source "$REGISTRY_ROOT/bash/functions/shell/assert_command_exists.sh" + +# ── Parsing ────────────────────────────────────────────────── + +NOMBRE="" +PORT="8080" +WITH_AUTH="false" +WITH_DB="false" +SKIP_PNPM_BUILD="${SKIP_PNPM_BUILD:-false}" + +while [ $# -gt 0 ]; do + case "$1" in + --port) PORT="$2"; shift 2 ;; + --with-auth) WITH_AUTH="true"; shift ;; + --with-db) WITH_DB="true"; shift ;; + --skip-pnpm-build) SKIP_PNPM_BUILD="true"; shift ;; + -h|--help) grep "^#" "$0" | sed 's/^# \?//' ; exit 0 ;; + -*) echo "Flag desconocido: $1" >&2 ; exit 1 ;; + *) + if [ -z "$NOMBRE" ]; then NOMBRE="$1" + else echo "Argumento extra ignorado: $1" >&2 + fi + shift ;; + esac +done + +if [ -z "$NOMBRE" ]; then + echo "Uso: $0 [--port N] [--with-auth] [--with-db]" >&2 + exit 1 +fi + +APP_DIR="${REGISTRY_ROOT}/apps/${NOMBRE}" +if [ -d "$APP_DIR" ]; then + echo "ERROR: ${APP_DIR} ya existe. Abortando." >&2 + exit 1 +fi + +echo "" +echo "════════════════════════════════════════════════════════════" +echo " INIT WEB APP: ${NOMBRE}" +echo " Directorio: ${APP_DIR}" +echo " Puerto: ${PORT}" +echo " Auth: ${WITH_AUTH}" +echo " DB: ${WITH_DB}" +echo "════════════════════════════════════════════════════════════" +echo "" + +# ── 1. Invocar init_api_app para generar backend ───────────── + +echo "[1/3] Generando backend con init_api_app..." +BACKEND_FLAGS=() +BACKEND_FLAGS+=(--port "$PORT") +[ "$WITH_AUTH" = "true" ] && BACKEND_FLAGS+=(--with-auth) +[ "$WITH_DB" = "true" ] && BACKEND_FLAGS+=(--with-db) + +bash "$SCRIPT_DIR/init_api_app.sh" "$NOMBRE" "${BACKEND_FLAGS[@]}" + +# ── 2. Generar frontend ────────────────────────────────────── + +echo "" +echo "[2/3] Generando frontend..." +mkdir -p "$APP_DIR/frontend/src/pages" + +# package.json +cat > "$APP_DIR/frontend/package.json" < "$APP_DIR/frontend/vite.config.ts" < "$APP_DIR/frontend/tsconfig.json" <<'EOF' +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "allowImportingTsExtensions": false, + "baseUrl": ".", + "paths": { + "@fn_library/*": ["../../../frontend/functions/ui/*"] + } + }, + "include": ["src"] +} +EOF + +# index.html +cat > "$APP_DIR/frontend/index.html" < + + + + + ${NOMBRE} + + +
+ + + +EOF + +# postcss.config.cjs (Mantine usa postcss-preset-mantine) +cat > "$APP_DIR/frontend/postcss.config.cjs" <<'EOF' +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", + }, + }, + }, +}; +EOF + +# src/theme.ts +cat > "$APP_DIR/frontend/src/theme.ts" < "$APP_DIR/frontend/src/main.tsx" <<'EOF' +import React from "react"; +import ReactDOM from "react-dom/client"; +import { MantineProvider } from "@mantine/core"; +import "@mantine/core/styles.css"; + +import { theme } from "./theme"; +import { App } from "./App"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + , +); +EOF + +# src/App.tsx +cat > "$APP_DIR/frontend/src/App.tsx" < + + + + ${NOMBRE} + + + + + + } active /> + } /> + + + + + + + + ); +} +EOF + +# src/pages/Home.tsx +cat > "$APP_DIR/frontend/src/pages/Home.tsx" <(null); + const [error, setError] = useState(null); + + useEffect(() => { + fetch("/api/v1/status") + .then((r) => r.json()) + .then(setStatus) + .catch((e) => setError(String(e))); + }, []); + + return ( + + ${NOMBRE} + Full-stack app scaffoldeada por init_web_app. + + + + API status + {status ? ( + {status.app} v{status.version} + ) : error ? ( + {error} + ) : ( + loading... + )} + + + + ); +} +EOF + +# .gitignore del frontend +cat > "$APP_DIR/frontend/.gitignore" <<'EOF' +node_modules/ +dist/ +*.log +.DS_Store +EOF + +# docker-compose.yml +cat > "$APP_DIR/docker-compose.yml" < "$APP_DIR/Makefile" < "$APP_DIR/app.md" </dev/null 2>&1; then + echo " pnpm detectado, ejecutando install + build..." + ( + cd "$APP_DIR/frontend" + if pnpm install --silent 2>&1 | tail -5; then + : + fi + if pnpm build 2>&1 | tail -5; then + echo " frontend build OK" + else + echo " WARN: frontend build fallo (revisa el output arriba)" >&2 + fi + ) +else + echo " pnpm no disponible — saltando verificacion frontend" + echo " Cuando este disponible: cd ${APP_DIR}/frontend && pnpm install && pnpm build" +fi + +echo "" +echo "════════════════════════════════════════════════════════════" +echo " WEB APP '${NOMBRE}' LISTA" +echo "════════════════════════════════════════════════════════════" +echo "" +echo " Pasos siguientes:" +echo " cd apps/${NOMBRE}" +echo " cp .env.example .env" +echo " make install # pnpm install del frontend" +echo "" +echo " Desarrollo (2 terminales):" +echo " Terminal 1: cd apps/${NOMBRE} && CGO_ENABLED=1 go run ." +echo " Terminal 2: cd apps/${NOMBRE}/frontend && pnpm dev" +echo "" +echo " Abrir http://localhost:5173" +echo ""