package cli import ( "encoding/json" "fmt" "os" "path/filepath" "github.com/spf13/cobra" "github.com/m3tam3re/agent-lib/internal/lockfile" "github.com/m3tam3re/agent-lib/internal/vendor" ) func loadLockfileOrFail(cmd *cobra.Command) *lockfile.Lockfile { lf, err := lockfile.Load(filepath.Join(".", lockfile.FileName)) if err != nil { if err == lockfile.ErrNotExist { fmt.Fprintln(os.Stderr, "error: no lockfile found — run `agent-lib vendor add` first") } else { fmt.Fprintln(os.Stderr, "error:", err) } os.Exit(1) } return lf } func emitJSON(cmd *cobra.Command, v any) error { enc := json.NewEncoder(cmd.OutOrStdout()) enc.SetIndent("", " ") return enc.Encode(v) } func newValidateCmd() *cobra.Command { var asJSON bool cmd := &cobra.Command{ Use: "validate", Short: "Check lockfile rules and external-area consistency offline", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { lf := loadLockfileOrFail(cmd) result := vendor.Validate(".", lf) if asJSON { return emitJSON(cmd, result) } w := cmd.OutOrStdout() if result.Valid { fmt.Fprintf(w, "lockfile valid (%d sources)\n", len(lf.Sources)) return nil } for _, e := range result.Errors { fmt.Fprintf(w, "%s: %s\n", e.Rule, e.Message) } cmd.SilenceErrors = true os.Exit(1) return nil }, } cmd.Flags().BoolVar(&asJSON, "json", false, "machine-readable output") return cmd } func newVendorListCmd() *cobra.Command { var asJSON bool cmd := &cobra.Command{ Use: "list", Short: "List all vendored sources with pin, mode and item counts", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { lf := loadLockfileOrFail(cmd) rows := vendor.List(lf) if asJSON { return emitJSON(cmd, map[string]any{"sources": rows}) } w := cmd.OutOrStdout() if len(rows) == 0 { fmt.Fprintln(w, "no sources vendored yet") return nil } fmt.Fprintf(w, "%-16s %-6s %-9s %-8s %s\n", "SOURCE", "REF", "REV", "MODE", "URL") for _, r := range rows { rev := r.Rev if len(rev) > 8 { rev = rev[:8] } fmt.Fprintf(w, "%-16s %-6s %-9s %-8s %s\n", r.Name, r.Ref, rev, r.Mode, r.URL) fmt.Fprintf(w, "%16s %s\n", "", countsLine(r.Counts)) } return nil }, } cmd.Flags().BoolVar(&asJSON, "json", false, "machine-readable output") return cmd } func countsLine(counts map[string]int) string { line := "" total := 0 for _, t := range lockfile.Types { if n, ok := counts[t]; ok { if line != "" { line += ", " } line += fmt.Sprintf("%d %s", n, t) total += n } } return fmt.Sprintf("%d items: %s", total, line) } func newVendorInspectCmd() *cobra.Command { var asJSON bool cmd := &cobra.Command{ Use: "inspect ", Short: "Show one source's full item inventory with metadata and warnings", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { lf := loadLockfileOrFail(cmd) report, err := vendor.Inspect(".", args[0], lf) if err != nil { return err } if asJSON { return emitJSON(cmd, report) } w := cmd.OutOrStdout() fmt.Fprintf(w, "source %s\n", report.Name) fmt.Fprintf(w, " url: %s\n", report.URL) fmt.Fprintf(w, " ref: %s\n", report.Ref) fmt.Fprintf(w, " rev: %s\n", report.Rev) fmt.Fprintf(w, " mode: %s\n", report.Mode) for _, t := range lockfile.Types { items := report.Items[t] if len(items) == 0 { continue } fmt.Fprintf(w, " %s:\n", t) for _, it := range items { fmt.Fprintf(w, " %s", it.DeployedID) if it.DeployedID != it.UpstreamID { fmt.Fprintf(w, " (upstream: %s)", it.UpstreamID) } if it.Name != "" { fmt.Fprintf(w, " name=%s", it.Name) } fmt.Fprintln(w) for _, warn := range it.Warnings { fmt.Fprintf(w, " warning: %s\n", warn) } } } return nil }, } cmd.Flags().BoolVar(&asJSON, "json", false, "machine-readable output") return cmd }