feat: CLI fn ops snapshot check y snapshot update
Comando check compara versiones locales vs registry y marca outdated. Comando update re-snapshottea por ID o --all, muestra diff de definition y description. Requiere registry accesible via FN_REGISTRY_ROOT.
This commit is contained in:
+127
-2
@@ -56,6 +56,8 @@ Usage:
|
||||
fn ops relation delete <id> Elimina relation
|
||||
fn ops graph Grafo ASCII de entities y relations
|
||||
fn ops snapshot list Lista tipos snapshotted
|
||||
fn ops snapshot check Compara snapshots vs registry
|
||||
fn ops snapshot update <id>|--all Re-snapshot desde registry
|
||||
|
||||
Entity flags:
|
||||
--id <id> --name <name> --type-ref <type_id> --source <source>
|
||||
@@ -540,11 +542,25 @@ func cmdOpsGraph() {
|
||||
// --- ops snapshot ---
|
||||
|
||||
func cmdOpsSnapshot(args []string) {
|
||||
if len(args) < 1 || args[0] != "list" {
|
||||
fmt.Fprintln(os.Stderr, "usage: fn ops snapshot list")
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: fn ops snapshot <list|check|update> [id|--all]")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case "list":
|
||||
cmdOpsSnapshotList()
|
||||
case "check":
|
||||
cmdOpsSnapshotCheck()
|
||||
case "update":
|
||||
cmdOpsSnapshotUpdate(args[1:])
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown snapshot command: %s\n", args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func cmdOpsSnapshotList() {
|
||||
db := openOpsDB()
|
||||
defer db.Close()
|
||||
|
||||
@@ -568,6 +584,115 @@ func cmdOpsSnapshot(args []string) {
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func cmdOpsSnapshotCheck() {
|
||||
opsDB := openOpsDB()
|
||||
defer opsDB.Close()
|
||||
|
||||
regDB := requireRegistryDB()
|
||||
defer regDB.Close()
|
||||
|
||||
results, err := ops.CheckSnapshots(opsDB, regDB)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
fmt.Println("No type snapshots to check.")
|
||||
return
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
fmt.Fprintln(w, "ID\tLOCAL\tREGISTRY\tSTATUS")
|
||||
for _, r := range results {
|
||||
regVer := r.RegistryVersion
|
||||
if regVer == "" {
|
||||
regVer = "-"
|
||||
}
|
||||
status := "✓"
|
||||
switch r.Status {
|
||||
case ops.SnapshotOutdated:
|
||||
status = "← OUTDATED"
|
||||
case ops.SnapshotMissing:
|
||||
status = "⚠ not in registry"
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", r.ID, r.LocalVersion, regVer, status)
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func cmdOpsSnapshotUpdate(args []string) {
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: fn ops snapshot update <type_id> | --all")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
opsDB := openOpsDB()
|
||||
defer opsDB.Close()
|
||||
|
||||
regDB := requireRegistryDB()
|
||||
defer regDB.Close()
|
||||
|
||||
if args[0] == "--all" {
|
||||
// Update all outdated
|
||||
results, err := ops.CheckSnapshots(opsDB, regDB)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
updated := 0
|
||||
for _, r := range results {
|
||||
if r.Status != ops.SnapshotOutdated {
|
||||
continue
|
||||
}
|
||||
old, new_, err := ops.UpdateSnapshot(opsDB, regDB, r.ID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, " error updating %s: %v\n", r.ID, err)
|
||||
continue
|
||||
}
|
||||
printSnapshotDiff(r.ID, old, new_)
|
||||
updated++
|
||||
}
|
||||
if updated == 0 {
|
||||
fmt.Println("All snapshots are up to date.")
|
||||
} else {
|
||||
fmt.Printf("\n%d snapshot(s) updated.\n", updated)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Update single
|
||||
typeID := args[0]
|
||||
old, new_, err := ops.UpdateSnapshot(opsDB, regDB, typeID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
printSnapshotDiff(typeID, old, new_)
|
||||
}
|
||||
|
||||
func printSnapshotDiff(id string, old, new_ *ops.TypeSnapshot) {
|
||||
fmt.Printf("Updated %s: %s → %s\n", id, old.Version, new_.Version)
|
||||
if old.Definition != new_.Definition {
|
||||
fmt.Println(" Definition changed:")
|
||||
fmt.Printf(" - %s\n", strings.ReplaceAll(strings.TrimSpace(old.Definition), "\n", "\n - "))
|
||||
fmt.Printf(" + %s\n", strings.ReplaceAll(strings.TrimSpace(new_.Definition), "\n", "\n + "))
|
||||
}
|
||||
if old.Description != new_.Description {
|
||||
fmt.Printf(" Description: %q → %q\n", old.Description, new_.Description)
|
||||
}
|
||||
}
|
||||
|
||||
func requireRegistryDB() *registry.DB {
|
||||
db := tryOpenRegistryDB()
|
||||
if db == nil {
|
||||
fmt.Fprintln(os.Stderr, "error: cannot find registry.db")
|
||||
fmt.Fprintln(os.Stderr, "Set FN_REGISTRY_ROOT or run from the registry directory.")
|
||||
os.Exit(1)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
func findOpsDB() string {
|
||||
|
||||
Reference in New Issue
Block a user