feat: subcomando project en CLI con busqueda y listado integrado
Añade cmd/fn/project.go con subcomandos init, list, show y status para gestionar proyectos desde la CLI. Integra projects en fn search, fn list y fn show para que aparezcan junto a functions, types y apps. También añade soporte para vaults en fn show y template project en fn add -k project.
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
func cmdProject(args []string) {
|
||||
if len(args) < 1 {
|
||||
printProjectUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case "init":
|
||||
cmdProjectInit(args[1:])
|
||||
case "list":
|
||||
cmdProjectList()
|
||||
case "show":
|
||||
cmdProjectShow(args[1:])
|
||||
case "status":
|
||||
cmdProjectStatus(args[1:])
|
||||
case "help", "-h", "--help":
|
||||
printProjectUsage()
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown project subcommand: %s\n", args[0])
|
||||
printProjectUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func printProjectUsage() {
|
||||
fmt.Println(`fn project — manage project workspaces
|
||||
|
||||
Usage:
|
||||
fn project init <nombre> Crea scaffold de proyecto
|
||||
fn project list Lista proyectos del registry
|
||||
fn project show <id> Muestra proyecto con apps, analysis y vaults
|
||||
fn project status [<id>] Estado resumido de un proyecto`)
|
||||
}
|
||||
|
||||
func cmdProjectInit(args []string) {
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: fn project init <nombre>")
|
||||
os.Exit(1)
|
||||
}
|
||||
name := args[0]
|
||||
r := root()
|
||||
projDir := filepath.Join(r, "projects", name)
|
||||
|
||||
if _, err := os.Stat(projDir); err == nil {
|
||||
fmt.Fprintf(os.Stderr, "project %q already exists at %s\n", name, projDir)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create directory structure
|
||||
dirs := []string{
|
||||
projDir,
|
||||
filepath.Join(projDir, "apps"),
|
||||
filepath.Join(projDir, "analysis"),
|
||||
filepath.Join(projDir, "vaults"),
|
||||
}
|
||||
for _, d := range dirs {
|
||||
if err := os.MkdirAll(d, 0o755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error creating %s: %v\n", d, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Create project.md
|
||||
projectMD := fmt.Sprintf(`---
|
||||
name: %s
|
||||
description: ""
|
||||
tags: []
|
||||
repo_url: ""
|
||||
---
|
||||
|
||||
## Notas
|
||||
|
||||
`, name)
|
||||
if err := os.WriteFile(filepath.Join(projDir, "project.md"), []byte(projectMD), 0o644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error writing project.md: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create vault.yaml
|
||||
vaultYAML := `vaults: []
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(projDir, "vaults", "vault.yaml"), []byte(vaultYAML), 0o644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error writing vault.yaml: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Project %q created at %s\n", name, projDir)
|
||||
fmt.Println("\nStructure:")
|
||||
fmt.Printf(" %s/\n", filepath.Join("projects", name))
|
||||
fmt.Printf(" project.md\n")
|
||||
fmt.Printf(" apps/\n")
|
||||
fmt.Printf(" analysis/\n")
|
||||
fmt.Printf(" vaults/\n")
|
||||
fmt.Printf(" vault.yaml\n")
|
||||
fmt.Println("\nNext steps:")
|
||||
fmt.Printf(" 1. Edit projects/%s/project.md (add description and tags)\n", name)
|
||||
fmt.Printf(" 2. Create apps or analysis inside the project\n")
|
||||
fmt.Printf(" 3. Run 'fn index' to register the project\n")
|
||||
}
|
||||
|
||||
func cmdProjectList() {
|
||||
db := openDB()
|
||||
defer db.Close()
|
||||
|
||||
projects, err := db.ListAllProjects()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(projects) == 0 {
|
||||
fmt.Println("No projects found. Create one with 'fn project init <nombre>'.")
|
||||
return
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
fmt.Fprintln(w, "ID\tDESCRIPTION\tTAGS")
|
||||
for _, p := range projects {
|
||||
desc := truncate(p.Description, 50)
|
||||
tags := strings.Join(p.Tags, ", ")
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\n", p.ID, desc, tags)
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func cmdProjectShow(args []string) {
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: fn project show <id>")
|
||||
os.Exit(1)
|
||||
}
|
||||
id := args[0]
|
||||
|
||||
db := openDB()
|
||||
defer db.Close()
|
||||
|
||||
p, err := db.GetProject(id)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "project not found: %s\n", id)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("ID: %s\n", p.ID)
|
||||
fmt.Printf("Name: %s\n", p.Name)
|
||||
fmt.Printf("Description: %s\n", p.Description)
|
||||
fmt.Printf("Tags: %s\n", strings.Join(p.Tags, ", "))
|
||||
fmt.Printf("Dir: %s\n", p.DirPath)
|
||||
if p.RepoURL != "" {
|
||||
fmt.Printf("Repo URL: %s\n", p.RepoURL)
|
||||
}
|
||||
if p.Notes != "" {
|
||||
fmt.Printf("\nNotes:\n%s\n", p.Notes)
|
||||
}
|
||||
if p.Documentation != "" {
|
||||
fmt.Printf("\nDocumentation:\n%s\n", p.Documentation)
|
||||
}
|
||||
|
||||
// Show project apps
|
||||
apps, _ := db.GetProjectApps(id)
|
||||
if len(apps) > 0 {
|
||||
fmt.Printf("\nApps (%d):\n", len(apps))
|
||||
for _, a := range apps {
|
||||
fmt.Printf(" - %s (%s) — %s\n", a.ID, a.Lang, truncate(a.Description, 60))
|
||||
}
|
||||
}
|
||||
|
||||
// Show project analysis
|
||||
analyses, _ := db.GetProjectAnalysis(id)
|
||||
if len(analyses) > 0 {
|
||||
fmt.Printf("\nAnalysis (%d):\n", len(analyses))
|
||||
for _, an := range analyses {
|
||||
fmt.Printf(" - %s (%s) — %s\n", an.ID, an.Lang, truncate(an.Description, 60))
|
||||
}
|
||||
}
|
||||
|
||||
// Show project vaults
|
||||
vaults, _ := db.GetProjectVaults(id)
|
||||
if len(vaults) > 0 {
|
||||
fmt.Printf("\nVaults (%d):\n", len(vaults))
|
||||
for _, v := range vaults {
|
||||
sym := ""
|
||||
if v.Symlink {
|
||||
sym = fmt.Sprintf(" -> %s", v.Path)
|
||||
}
|
||||
fmt.Printf(" - %s — %s%s\n", v.Name, v.Description, sym)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cmdProjectStatus(args []string) {
|
||||
db := openDB()
|
||||
defer db.Close()
|
||||
|
||||
if len(args) > 0 {
|
||||
// Status for a specific project
|
||||
id := args[0]
|
||||
p, err := db.GetProject(id)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "project not found: %s\n", id)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
apps, _ := db.GetProjectApps(id)
|
||||
analyses, _ := db.GetProjectAnalysis(id)
|
||||
vaults, _ := db.GetProjectVaults(id)
|
||||
|
||||
fmt.Printf("%s — %s\n", p.Name, p.Description)
|
||||
fmt.Printf(" Apps: %d\n", len(apps))
|
||||
fmt.Printf(" Analysis: %d\n", len(analyses))
|
||||
fmt.Printf(" Vaults: %d\n", len(vaults))
|
||||
fmt.Printf(" Updated: %s\n", p.UpdatedAt.Format("2006-01-02 15:04"))
|
||||
return
|
||||
}
|
||||
|
||||
// Status for all projects
|
||||
projects, err := db.ListAllProjects()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(projects) == 0 {
|
||||
fmt.Println("No projects found.")
|
||||
return
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
fmt.Fprintln(w, "PROJECT\tAPPS\tANALYSIS\tVAULTS\tUPDATED")
|
||||
for _, p := range projects {
|
||||
apps, _ := db.GetProjectApps(p.ID)
|
||||
analyses, _ := db.GetProjectAnalysis(p.ID)
|
||||
vaults, _ := db.GetProjectVaults(p.ID)
|
||||
fmt.Fprintf(w, "%s\t%d\t%d\t%d\t%s\n",
|
||||
p.Name, len(apps), len(analyses), len(vaults),
|
||||
p.UpdatedAt.Format("2006-01-02"))
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
Reference in New Issue
Block a user