feat: init_desktop_app bash pipeline — scaffold Wails desktop app
Genera apps/{nombre}/ con Wails v2 backend (main.go con embed frontend/dist,
app.go con struct App y bindings Greet/GetVersion) + frontend Vite+React+Mantine
con alias @fn_library.
Flag --with-db anade store.go con SQLite (schema items) y bindings CRUD
(ListItems, CreateItem); app.go se regenera con campo db.
wails.json con scripts pnpm, go.mod con replace a fn-registry, app.md con
framework wails+vite+react+mantine y dir_path correcto.
Verifica con go mod tidy al final. wails build requiere CLI instalado pero
el scaffold funciona sin el.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,118 @@
|
|||||||
|
---
|
||||||
|
name: init_desktop_app
|
||||||
|
kind: pipeline
|
||||||
|
lang: bash
|
||||||
|
domain: pipelines
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "init_desktop_app(nombre: string, [--with-db]) -> void"
|
||||||
|
description: "Scaffold de Wails desktop app: Go backend + React frontend con Mantine y @fn_library. Genera main.go (Wails con embed frontend), app.go (bindings Greet/GetVersion), wails.json, go.mod con replace a fn-registry y frontend/ con vite + react + mantine."
|
||||||
|
tags: [init, scaffold, desktop, wails, pipeline, bash, launcher]
|
||||||
|
uses_functions:
|
||||||
|
- assert_command_exists_bash_shell
|
||||||
|
- scaffold_wails_app_go_infra
|
||||||
|
- install_wails_bash_infra
|
||||||
|
- wails_bind_crud_go_infra
|
||||||
|
- wails_build_go_infra
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: nombre
|
||||||
|
desc: "nombre de la app Wails (apps/{nombre}/)"
|
||||||
|
- name: "--with-db"
|
||||||
|
desc: "anade store.go con SQLite + bindings CRUD (Item, ListItems, CreateItem)"
|
||||||
|
output: "app Wails en apps/{nombre}/ con main.go + app.go + wails.json + frontend/ Vite+React. Ejecutar 'wails dev' o 'wails build' dentro del directorio."
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
example: "fn run init_desktop_app my_tool"
|
||||||
|
file_path: "bash/functions/pipelines/init_desktop_app.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sinopsis
|
||||||
|
|
||||||
|
```bash
|
||||||
|
fn run init_desktop_app <nombre> [--with-db]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ejemplo rapido
|
||||||
|
|
||||||
|
```bash
|
||||||
|
fn run init_desktop_app data_explorer --with-db
|
||||||
|
cd apps/data_explorer/frontend && pnpm install && cd ..
|
||||||
|
wails dev # dev con hot reload
|
||||||
|
# o bien:
|
||||||
|
wails build && ./build/bin/data_explorer
|
||||||
|
```
|
||||||
|
|
||||||
|
## Archivos generados
|
||||||
|
|
||||||
|
| Archivo | Descripcion |
|
||||||
|
|---------|-------------|
|
||||||
|
| `main.go` | Entry Wails con embed de frontend/dist, options.App, Bind: []interface{}{app} |
|
||||||
|
| `app.go` | Struct App con bindings `Greet(name)`, `GetVersion()` (y con `--with-db`: `ListItems`, `CreateItem`) |
|
||||||
|
| `wails.json` | Config Wails: name, outputfilename, scripts frontend (pnpm) |
|
||||||
|
| `go.mod` | Modulo Go con `github.com/wailsapp/wails/v2` + replace `fn-registry` |
|
||||||
|
| `frontend/package.json` | pnpm + vite + react + @mantine/core + @tabler/icons-react |
|
||||||
|
| `frontend/vite.config.ts` | Alias `@fn_library`, outDir `dist` para embed |
|
||||||
|
| `frontend/tsconfig.json` | TS strict con paths `@fn_library/*` |
|
||||||
|
| `frontend/src/main.tsx` | Root con `MantineProvider` (defaultColorScheme: dark) |
|
||||||
|
| `frontend/src/App.tsx` | Componente que llama bindings `Greet` y `GetVersion` via wailsjs |
|
||||||
|
| `frontend/postcss.config.cjs` | postcss-preset-mantine |
|
||||||
|
| `app.md` | Framework: `wails + vite + react + mantine` |
|
||||||
|
|
||||||
|
Con `--with-db` anade ademas:
|
||||||
|
- `store.go` — `openDB`, tipo `Item`, bindings CRUD `ListItems`, `CreateItem`
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
|
||||||
|
| Flag | Efecto |
|
||||||
|
|------|--------|
|
||||||
|
| `--with-db` | SQLite con schema `items` + bindings CRUD como ejemplo |
|
||||||
|
|
||||||
|
## Post-setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Instalar deps del frontend (una vez)
|
||||||
|
cd apps/{nombre}/frontend && pnpm install && cd ..
|
||||||
|
|
||||||
|
# 2. Desarrollo
|
||||||
|
wails dev # Ventana desktop con hot reload del frontend
|
||||||
|
|
||||||
|
# 3. Produccion
|
||||||
|
wails build # binario en build/bin/{nombre}
|
||||||
|
./build/bin/{nombre}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requisitos
|
||||||
|
|
||||||
|
El scaffold funciona sin Wails CLI, pero `wails dev`/`wails build` requiere:
|
||||||
|
|
||||||
|
- **Wails CLI:** `go install github.com/wailsapp/wails/v2/cmd/wails@latest`
|
||||||
|
- **Deps del sistema (Linux):** GTK3 + WebKit2GTK — usa
|
||||||
|
`install_wails_bash_infra` para instalarlas:
|
||||||
|
```bash
|
||||||
|
source bash/functions/infra/install_wails.sh && install_wails
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
Pipeline impuro: genera archivos via heredocs, ejecuta `go mod tidy` al
|
||||||
|
final como verificacion. Si Wails CLI no esta disponible, reporta el warning
|
||||||
|
y continua — el scaffold es valido, solo `wails build` falla hasta instalar
|
||||||
|
el CLI.
|
||||||
|
|
||||||
|
El frontend importa `@fn_library` via alias en vite.config.ts apuntando a
|
||||||
|
`../../../frontend/functions/ui/` (los componentes del registry sin
|
||||||
|
duplicarlos).
|
||||||
|
|
||||||
|
Los bindings de Wails (funciones del struct App) se regeneran en
|
||||||
|
`frontend/wailsjs/go/main/App.ts` automaticamente cuando corres `wails dev`
|
||||||
|
o `wails build`.
|
||||||
|
|
||||||
|
Abort si `apps/{nombre}/` ya existe.
|
||||||
|
|
||||||
|
El tag `launcher` permite que aparezca en el Pipeline Launcher TUI.
|
||||||
Executable
+594
@@ -0,0 +1,594 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# init_desktop_app
|
||||||
|
# ----------------
|
||||||
|
# Scaffold de Wails desktop app: Go backend + React frontend con @mantine y
|
||||||
|
# @fn_library. Genera main.go (Wails con embed del frontend), app.go (struct
|
||||||
|
# App con bindings base), wails.json, go.mod y frontend/ con vite+react+mantine.
|
||||||
|
#
|
||||||
|
# USO:
|
||||||
|
# ./init_desktop_app.sh <nombre> [--with-db]
|
||||||
|
#
|
||||||
|
# FLAGS:
|
||||||
|
# --with-db Anade store.go con SQLite + bindings CRUD de ejemplo
|
||||||
|
#
|
||||||
|
# EJEMPLO:
|
||||||
|
# ./init_desktop_app.sh my_tool
|
||||||
|
# ./init_desktop_app.sh data_explorer --with-db
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REGISTRY_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||||
|
|
||||||
|
source "$REGISTRY_ROOT/bash/functions/shell/assert_command_exists.sh"
|
||||||
|
|
||||||
|
NOMBRE=""
|
||||||
|
WITH_DB="false"
|
||||||
|
SKIP_WAILS_BUILD="${SKIP_WAILS_BUILD:-false}"
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--with-db) WITH_DB="true"; shift ;;
|
||||||
|
--skip-wails-build) SKIP_WAILS_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> [--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 DESKTOP APP (Wails): ${NOMBRE}"
|
||||||
|
echo " Directorio: ${APP_DIR}"
|
||||||
|
echo " DB: ${WITH_DB}"
|
||||||
|
echo "════════════════════════════════════════════════════════════"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ── 1. Verificar Go y Wails ──────────────────────────────────
|
||||||
|
|
||||||
|
echo "[1/5] Verificando herramientas..."
|
||||||
|
assert_command_exists go
|
||||||
|
echo " Go: $(go version)"
|
||||||
|
|
||||||
|
if command -v wails >/dev/null 2>&1; then
|
||||||
|
echo " Wails: $(wails version 2>/dev/null || echo detectado)"
|
||||||
|
else
|
||||||
|
echo " WARN: Wails CLI no detectado."
|
||||||
|
echo " Instalar: source ${REGISTRY_ROOT}/bash/functions/infra/install_wails.sh && install_wails"
|
||||||
|
echo " (Continuando con scaffold; \`wails build\` requiere el CLI.)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 2. Crear estructura + archivos Go ────────────────────────
|
||||||
|
|
||||||
|
echo "[2/5] Creando estructura y archivos Go..."
|
||||||
|
mkdir -p "$APP_DIR/frontend/src/pages"
|
||||||
|
mkdir -p "$APP_DIR/build/bin"
|
||||||
|
|
||||||
|
# go.mod con replace a fn-registry
|
||||||
|
cat > "$APP_DIR/go.mod" <<EOF
|
||||||
|
module ${NOMBRE}
|
||||||
|
|
||||||
|
go 1.25.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/wailsapp/wails/v2 v2.9.2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
fn-registry v0.0.0-00010101000000-000000000000
|
||||||
|
)
|
||||||
|
|
||||||
|
replace fn-registry => ${REGISTRY_ROOT}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# main.go: entry point Wails con embed frontend/dist
|
||||||
|
cat > "$APP_DIR/main.go" <<'EOF'
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed all:frontend/dist
|
||||||
|
var assets embed.FS
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := NewApp()
|
||||||
|
|
||||||
|
err := wails.Run(&options.App{
|
||||||
|
Title: "__APP_NAME__",
|
||||||
|
Width: 1024,
|
||||||
|
Height: 768,
|
||||||
|
AssetServer: &assetserver.Options{
|
||||||
|
Assets: assets,
|
||||||
|
},
|
||||||
|
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
|
||||||
|
OnStartup: app.startup,
|
||||||
|
Bind: []interface{}{
|
||||||
|
app,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
println("Error:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
sed -i "s/__APP_NAME__/${NOMBRE}/g" "$APP_DIR/main.go"
|
||||||
|
|
||||||
|
# app.go: struct App con bindings base
|
||||||
|
cat > "$APP_DIR/app.go" <<EOF
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// App struct — contexto de la app Wails y bindings al frontend.
|
||||||
|
type App struct {
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApp crea una nueva App.
|
||||||
|
func NewApp() *App {
|
||||||
|
return &App{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// startup se llama cuando la app arranca. Persistimos el ctx para usarlo en
|
||||||
|
// llamadas a runtime.EventsEmit, etc.
|
||||||
|
func (a *App) startup(ctx context.Context) {
|
||||||
|
a.ctx = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// Greet retorna un saludo. Accesible desde el frontend como wails.Greet(name).
|
||||||
|
func (a *App) Greet(name string) string {
|
||||||
|
return fmt.Sprintf("Hola, %s!", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersion retorna la version de la app y el Go runtime.
|
||||||
|
func (a *App) GetVersion() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
"app": "0.1.0",
|
||||||
|
"name": "${NOMBRE}",
|
||||||
|
"goVer": runtime.Version(),
|
||||||
|
"goOS": runtime.GOOS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# wails.json
|
||||||
|
cat > "$APP_DIR/wails.json" <<EOF
|
||||||
|
{
|
||||||
|
"\$schema": "https://wails.io/schemas/config.v2.json",
|
||||||
|
"name": "${NOMBRE}",
|
||||||
|
"outputfilename": "${NOMBRE}",
|
||||||
|
"frontend:install": "pnpm install",
|
||||||
|
"frontend:build": "pnpm build",
|
||||||
|
"frontend:dev:watcher": "pnpm dev",
|
||||||
|
"frontend:dev:serverUrl": "auto",
|
||||||
|
"author": {
|
||||||
|
"name": "",
|
||||||
|
"email": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Store opcional
|
||||||
|
if [ "$WITH_DB" = "true" ]; then
|
||||||
|
cat > "$APP_DIR/store.go" <<'EOF'
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Item es un registro de ejemplo.
|
||||||
|
type Item struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
CreatedAt string `json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// openDB abre (o crea) la base de datos SQLite en dbPath e inicializa el schema.
|
||||||
|
func openDB(dbPath string) (*sql.DB, error) {
|
||||||
|
db, err := sql.Open("sqlite3", dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open db: %w", err)
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS items (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
)`); err != nil {
|
||||||
|
return nil, fmt.Errorf("init schema: %w", err)
|
||||||
|
}
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListItems devuelve todos los items. Binding accesible desde el frontend.
|
||||||
|
func (a *App) ListItems() ([]Item, error) {
|
||||||
|
rows, err := a.db.Query(`SELECT id, name, created_at FROM items ORDER BY id DESC`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var items []Item
|
||||||
|
for rows.Next() {
|
||||||
|
var it Item
|
||||||
|
if err := rows.Scan(&it.ID, &it.Name, &it.CreatedAt); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, it)
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateItem inserta un nuevo item.
|
||||||
|
func (a *App) CreateItem(name string) (Item, error) {
|
||||||
|
res, err := a.db.Exec(`INSERT INTO items (name) VALUES (?)`, name)
|
||||||
|
if err != nil {
|
||||||
|
return Item{}, err
|
||||||
|
}
|
||||||
|
id, _ := res.LastInsertId()
|
||||||
|
return Item{ID: id, Name: name}, nil
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
# Cuando hay DB, actualizamos app.go para incluir db field
|
||||||
|
cat > "$APP_DIR/app.go" <<EOF
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// App struct — contexto de la app Wails, DB y bindings al frontend.
|
||||||
|
type App struct {
|
||||||
|
ctx context.Context
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApp crea una nueva App con la DB abierta.
|
||||||
|
func NewApp() *App {
|
||||||
|
db, err := openDB("${NOMBRE}.db")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("open db: %v", err)
|
||||||
|
}
|
||||||
|
return &App{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) startup(ctx context.Context) {
|
||||||
|
a.ctx = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Greet(name string) string {
|
||||||
|
return fmt.Sprintf("Hola, %s!", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetVersion() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
"app": "0.1.0",
|
||||||
|
"name": "${NOMBRE}",
|
||||||
|
"goVer": runtime.Version(),
|
||||||
|
"goOS": runtime.GOOS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
# Actualizar go.mod para incluir mattn/go-sqlite3
|
||||||
|
cat > "$APP_DIR/go.mod" <<EOF
|
||||||
|
module ${NOMBRE}
|
||||||
|
|
||||||
|
go 1.25.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/wailsapp/wails/v2 v2.9.2
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.37
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
fn-registry v0.0.0-00010101000000-000000000000
|
||||||
|
)
|
||||||
|
|
||||||
|
replace fn-registry => ${REGISTRY_ROOT}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 3. Frontend ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo "[3/5] Generando frontend..."
|
||||||
|
|
||||||
|
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",
|
||||||
|
"@tabler/icons-react": "^3.30.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.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
|
||||||
|
|
||||||
|
cat > "$APP_DIR/frontend/vite.config.ts" <<'EOF'
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
// Vite config para Wails: build → dist/, alias @fn_library a frontend/functions/ui
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@fn_library": path.resolve(__dirname, "../../../frontend/functions/ui"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: "dist",
|
||||||
|
emptyOutDir: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
EOF
|
||||||
|
|
||||||
|
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,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@fn_library/*": ["../../../frontend/functions/ui/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
cat > "$APP_DIR/frontend/postcss.config.cjs" <<'EOF'
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
"postcss-preset-mantine": {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$APP_DIR/frontend/src/theme.ts" <<'EOF'
|
||||||
|
import { createTheme } from "@mantine/core";
|
||||||
|
export const theme = createTheme({
|
||||||
|
primaryColor: "blue",
|
||||||
|
defaultRadius: "md",
|
||||||
|
});
|
||||||
|
EOF
|
||||||
|
|
||||||
|
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="dark">
|
||||||
|
<App />
|
||||||
|
</MantineProvider>
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$APP_DIR/frontend/src/App.tsx" <<EOF
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Stack, Title, Text, Paper, Button, Group, Badge } from "@mantine/core";
|
||||||
|
|
||||||
|
// @ts-expect-error Wails genera este modulo al hacer build
|
||||||
|
import { Greet, GetVersion } from "../wailsjs/go/main/App";
|
||||||
|
|
||||||
|
export function App() {
|
||||||
|
const [greeting, setGreeting] = useState("");
|
||||||
|
const [version, setVersion] = useState<Record<string, string> | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
GetVersion().then(setVersion).catch(() => setVersion(null));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack p="xl">
|
||||||
|
<Title order={2}>${NOMBRE}</Title>
|
||||||
|
<Text c="dimmed">Desktop app scaffoldeada por init_desktop_app.</Text>
|
||||||
|
|
||||||
|
<Paper p="md" withBorder>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text fw={500}>Version</Text>
|
||||||
|
<Badge color="blue">
|
||||||
|
{version ? \`\${version.app} (\${version.goOS} \${version.goVer})\` : "cargando..."}
|
||||||
|
</Badge>
|
||||||
|
</Group>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
<Paper p="md" withBorder>
|
||||||
|
<Stack>
|
||||||
|
<Button onClick={() => Greet("desktop").then(setGreeting)}>
|
||||||
|
Greet from Go
|
||||||
|
</Button>
|
||||||
|
{greeting && <Text>{greeting}</Text>}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$APP_DIR/frontend/.gitignore" <<'EOF'
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
wailsjs/
|
||||||
|
*.log
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# .gitignore raiz
|
||||||
|
cat > "$APP_DIR/.gitignore" <<EOF
|
||||||
|
# Wails build output
|
||||||
|
build/bin/
|
||||||
|
frontend/dist/
|
||||||
|
frontend/wailsjs/
|
||||||
|
frontend/node_modules/
|
||||||
|
|
||||||
|
# SQLite
|
||||||
|
*.db
|
||||||
|
*.db-shm
|
||||||
|
*.db-wal
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# ── 4. app.md ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo "[4/5] Escribiendo app.md..."
|
||||||
|
|
||||||
|
USES_FUNCTIONS=' - scaffold_wails_app_go_infra
|
||||||
|
- install_wails_bash_infra'
|
||||||
|
|
||||||
|
cat > "$APP_DIR/app.md" <<EOF
|
||||||
|
---
|
||||||
|
name: ${NOMBRE}
|
||||||
|
lang: go
|
||||||
|
domain: tools
|
||||||
|
description: "Desktop app (Wails + React + Mantine) generada por init_desktop_app."
|
||||||
|
tags: [desktop, wails, frontend]
|
||||||
|
uses_functions:
|
||||||
|
${USES_FUNCTIONS}
|
||||||
|
uses_types: []
|
||||||
|
framework: "wails + vite + react + mantine"
|
||||||
|
entry_point: "main.go"
|
||||||
|
dir_path: "apps/${NOMBRE}"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
Desktop app con backend Go (Wails v2) y frontend React. Bindings en
|
||||||
|
\`app.go\` son accesibles desde el frontend via \`wails dev\`/\`wails build\`
|
||||||
|
que genera \`frontend/wailsjs/go/main/App.ts\` automaticamente.
|
||||||
|
|
||||||
|
Desarrollo:
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
cd apps/${NOMBRE}
|
||||||
|
cd frontend && pnpm install && cd ..
|
||||||
|
wails dev
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Build:
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
wails build
|
||||||
|
./build/bin/${NOMBRE}
|
||||||
|
\`\`\`
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# ── 5. Verificacion ──────────────────────────────────────────
|
||||||
|
|
||||||
|
echo "[5/5] Verificacion..."
|
||||||
|
|
||||||
|
if [ "$SKIP_WAILS_BUILD" = "true" ]; then
|
||||||
|
echo " SKIP_WAILS_BUILD=true — saltando wails build"
|
||||||
|
elif command -v wails >/dev/null 2>&1; then
|
||||||
|
echo " wails detectado. Ejecutar manualmente 'wails build' en ${APP_DIR}"
|
||||||
|
echo " (el build real requiere pnpm install previo en frontend/)"
|
||||||
|
else
|
||||||
|
echo " wails no disponible — saltando build."
|
||||||
|
echo " El scaffold esta listo. Para builds: instalar Wails primero."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Al menos verificar que go mod tidy funciona (que el main.go/app.go es Go valido)
|
||||||
|
(
|
||||||
|
cd "$APP_DIR"
|
||||||
|
if CGO_ENABLED=1 go mod tidy 2>&1 | tail -5; then
|
||||||
|
echo " go mod tidy OK"
|
||||||
|
else
|
||||||
|
echo " WARN: go mod tidy fallo — revisa main.go/app.go/go.mod" >&2
|
||||||
|
fi
|
||||||
|
)
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "════════════════════════════════════════════════════════════"
|
||||||
|
echo " DESKTOP APP '${NOMBRE}' LISTA"
|
||||||
|
echo "════════════════════════════════════════════════════════════"
|
||||||
|
echo ""
|
||||||
|
echo " Pasos siguientes:"
|
||||||
|
echo " cd apps/${NOMBRE}/frontend && pnpm install && cd .."
|
||||||
|
echo " wails dev # modo desarrollo con hot reload"
|
||||||
|
echo " wails build # binario de produccion en build/bin/"
|
||||||
|
echo ""
|
||||||
Reference in New Issue
Block a user