feat: init_web_app bash pipeline — scaffold Go API + React frontend
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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 <nombre> [--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
|
||||||
|
./<nombre> # 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.
|
||||||
Executable
+466
@@ -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 <nombre> [--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 <nombre> [--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" <<EOF
|
||||||
|
{
|
||||||
|
"name": "${NOMBRE}-frontend",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@mantine/core": "^8.0.0",
|
||||||
|
"@mantine/hooks": "^8.0.0",
|
||||||
|
"@mantine/notifications": "^8.0.0",
|
||||||
|
"@tabler/icons-react": "^3.30.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"react-router-dom": "^7.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
|
"typescript": "~5.7.0",
|
||||||
|
"vite": "^7.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# vite.config.ts
|
||||||
|
cat > "$APP_DIR/frontend/vite.config.ts" <<EOF
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
// Vite config: proxy API al backend Go + alias @fn_library a frontend/functions/ui del registry
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@fn_library": path.resolve(__dirname, "../../../frontend/functions/ui"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 5173,
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
target: "http://localhost:${PORT}",
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
"/health": {
|
||||||
|
target: "http://localhost:${PORT}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: "dist",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# tsconfig.json
|
||||||
|
cat > "$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" <<EOF
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>${NOMBRE}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
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" <<EOF
|
||||||
|
import { createTheme } from "@mantine/core";
|
||||||
|
|
||||||
|
// Tema Mantine de ${NOMBRE}. Mantine genera sus propias CSS variables.
|
||||||
|
export const theme = createTheme({
|
||||||
|
primaryColor: "blue",
|
||||||
|
defaultRadius: "md",
|
||||||
|
});
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# src/main.tsx
|
||||||
|
cat > "$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(
|
||||||
|
<React.StrictMode>
|
||||||
|
<MantineProvider theme={theme} defaultColorScheme="auto">
|
||||||
|
<App />
|
||||||
|
</MantineProvider>
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# src/App.tsx
|
||||||
|
cat > "$APP_DIR/frontend/src/App.tsx" <<EOF
|
||||||
|
import { AppShell, Title, Burger, Group, NavLink, Stack } from "@mantine/core";
|
||||||
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
|
import { IconHome, IconSettings } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
import { Home } from "./pages/Home";
|
||||||
|
|
||||||
|
export function App() {
|
||||||
|
const [opened, { toggle }] = useDisclosure();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppShell
|
||||||
|
header={{ height: 56 }}
|
||||||
|
navbar={{ width: 240, breakpoint: "sm", collapsed: { mobile: !opened } }}
|
||||||
|
padding="md"
|
||||||
|
>
|
||||||
|
<AppShell.Header>
|
||||||
|
<Group h="100%" px="md">
|
||||||
|
<Burger opened={opened} onClick={toggle} hiddenFrom="sm" size="sm" />
|
||||||
|
<Title order={4}>${NOMBRE}</Title>
|
||||||
|
</Group>
|
||||||
|
</AppShell.Header>
|
||||||
|
|
||||||
|
<AppShell.Navbar p="sm">
|
||||||
|
<Stack gap={4}>
|
||||||
|
<NavLink label="Home" leftSection={<IconHome size={16} />} active />
|
||||||
|
<NavLink label="Settings" leftSection={<IconSettings size={16} />} />
|
||||||
|
</Stack>
|
||||||
|
</AppShell.Navbar>
|
||||||
|
|
||||||
|
<AppShell.Main>
|
||||||
|
<Home />
|
||||||
|
</AppShell.Main>
|
||||||
|
</AppShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# src/pages/Home.tsx
|
||||||
|
cat > "$APP_DIR/frontend/src/pages/Home.tsx" <<EOF
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Stack, Title, Text, Paper, Group, Badge } from "@mantine/core";
|
||||||
|
|
||||||
|
type StatusResponse = {
|
||||||
|
app: string;
|
||||||
|
version: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Home() {
|
||||||
|
const [status, setStatus] = useState<StatusResponse | null>(null);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch("/api/v1/status")
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then(setStatus)
|
||||||
|
.catch((e) => setError(String(e)));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Title order={2}>${NOMBRE}</Title>
|
||||||
|
<Text c="dimmed">Full-stack app scaffoldeada por init_web_app.</Text>
|
||||||
|
|
||||||
|
<Paper p="md" withBorder>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text fw={500}>API status</Text>
|
||||||
|
{status ? (
|
||||||
|
<Badge color="green">{status.app} v{status.version}</Badge>
|
||||||
|
) : error ? (
|
||||||
|
<Badge color="red">{error}</Badge>
|
||||||
|
) : (
|
||||||
|
<Badge color="gray">loading...</Badge>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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" <<EOF
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "${PORT}:${PORT}"
|
||||||
|
env_file: .env
|
||||||
|
volumes:
|
||||||
|
- ./migrations:/app/migrations
|
||||||
|
frontend:
|
||||||
|
image: node:20-alpine
|
||||||
|
working_dir: /app
|
||||||
|
command: sh -c "corepack enable && pnpm install && pnpm dev --host"
|
||||||
|
ports:
|
||||||
|
- "5173:5173"
|
||||||
|
volumes:
|
||||||
|
- ./frontend:/app
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Actualizar Makefile con targets frontend + dev
|
||||||
|
cat > "$APP_DIR/Makefile" <<EOF
|
||||||
|
.PHONY: build build-frontend run dev test vet clean install
|
||||||
|
|
||||||
|
BIN=${NOMBRE}
|
||||||
|
|
||||||
|
install:
|
||||||
|
cd frontend && pnpm install
|
||||||
|
|
||||||
|
build-frontend:
|
||||||
|
cd frontend && pnpm build
|
||||||
|
|
||||||
|
build: build-frontend
|
||||||
|
CGO_ENABLED=1 go build -tags fts5 -o \$(BIN) .
|
||||||
|
|
||||||
|
run: build
|
||||||
|
./\$(BIN)
|
||||||
|
|
||||||
|
dev:
|
||||||
|
@echo "Arranca el backend (API en :${PORT}):"
|
||||||
|
@echo " CGO_ENABLED=1 go run ."
|
||||||
|
@echo "Arranca el frontend (Vite en :5173 con proxy):"
|
||||||
|
@echo " cd frontend && pnpm dev"
|
||||||
|
|
||||||
|
test:
|
||||||
|
CGO_ENABLED=1 go test -tags fts5 -v ./...
|
||||||
|
|
||||||
|
vet:
|
||||||
|
CGO_ENABLED=1 go vet -tags fts5 ./...
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f \$(BIN) *.db *.db-shm *.db-wal
|
||||||
|
rm -rf frontend/dist frontend/node_modules
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Actualizar app.md con framework + uses frontend
|
||||||
|
cat > "$APP_DIR/app.md" <<EOF
|
||||||
|
---
|
||||||
|
name: ${NOMBRE}
|
||||||
|
lang: go
|
||||||
|
domain: tools
|
||||||
|
description: "Full-stack app (Go API + React/Mantine frontend) generada por init_web_app."
|
||||||
|
tags: [service, web, frontend]
|
||||||
|
uses_functions:
|
||||||
|
- http_serve_go_infra
|
||||||
|
- http_router_go_infra
|
||||||
|
- http_middleware_chain_go_infra
|
||||||
|
- http_cors_middleware_go_infra
|
||||||
|
- http_logger_middleware_go_infra
|
||||||
|
- http_json_response_go_infra
|
||||||
|
- http_error_response_go_infra
|
||||||
|
- migration_up_go_infra
|
||||||
|
uses_types:
|
||||||
|
- Route_go_infra
|
||||||
|
- Middleware_go_infra
|
||||||
|
- HTTPError_go_infra
|
||||||
|
framework: "net/http + vite + react + mantine"
|
||||||
|
entry_point: "main.go"
|
||||||
|
dir_path: "apps/${NOMBRE}"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
App full-stack: backend en Go (\`main.go\`) + frontend en \`frontend/\` con
|
||||||
|
Vite + React + Mantine. El vite.config.ts hace proxy de \`/api\` y \`/health\`
|
||||||
|
al backend en :${PORT}, y usa alias \`@fn_library\` hacia \`frontend/functions/ui\`
|
||||||
|
del registry.
|
||||||
|
|
||||||
|
Dev: \`make install && make dev\` (dos terminales: backend con \`go run .\`,
|
||||||
|
frontend con \`cd frontend && pnpm dev\`).
|
||||||
|
|
||||||
|
Prod: \`make build\` genera \`frontend/dist\` + binario; el binario sirve los
|
||||||
|
static files del build embebido (pendiente embed en main.go).
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# ── 3. Verificar frontend si pnpm disponible ─────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[3/3] Verificacion..."
|
||||||
|
|
||||||
|
if [ "$SKIP_PNPM_BUILD" = "true" ]; then
|
||||||
|
echo " SKIP_PNPM_BUILD=true — saltando pnpm install/build"
|
||||||
|
elif command -v pnpm >/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 ""
|
||||||
Reference in New Issue
Block a user