cli/cmd/core/pkgcmd/cmd_manage.go
Claude 6b321fe5c9
Some checks are pending
Security Scan / security (push) Waiting to run
refactor(ax): Pass 1 AX compliance sweep — banned imports, naming, tests
- Remove banned imports (fmt, log, errors, os, strings, path/filepath,
  encoding/json) from all cmd/ packages; replace with core.* primitives
  and cli.* wrappers
- Rename abbreviated variables (cfg→configuration, reg→registry,
  cmd→proc, c→toolCheck/checkBuilder, sb→builder, out→output,
  r→repo/reason, b→branchName) across config, doctor, pkgcmd, help
- Add usage-example comments to all exported functions in pkg/cli
  (strings.go, log.go, i18n.go)
- Add complete Good/Bad/Ugly test triads to all pkg/cli test files:
  new files for command, errors, frame_components, i18n, log, render,
  runtime, strings, utils; updated existing check, daemon, glyph,
  layout, output, ansi, commands, frame, prompt, stream, styles,
  tracker, tree

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 17:28:36 +00:00

254 lines
6.7 KiB
Go

package pkgcmd
import (
"os/exec"
"dappco.re/go/core"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/go-i18n"
coreio "forge.lthn.ai/core/go-io"
"forge.lthn.ai/core/go-scm/repos"
"github.com/spf13/cobra"
)
// addPkgListCommand adds the 'pkg list' command.
func addPkgListCommand(parent *cobra.Command) {
listCmd := &cobra.Command{
Use: "list",
Short: i18n.T("cmd.pkg.list.short"),
Long: i18n.T("cmd.pkg.list.long"),
RunE: func(cmd *cobra.Command, args []string) error {
return runPkgList()
},
}
parent.AddCommand(listCmd)
}
func runPkgList() error {
registryPath, err := repos.FindRegistry(coreio.Local)
if err != nil {
return cli.Err(i18n.T("cmd.pkg.error.no_repos_yaml_workspace"))
}
registry, err := repos.LoadRegistry(coreio.Local, registryPath)
if err != nil {
return cli.Wrap(err, i18n.T("i18n.fail.load", "registry"))
}
basePath := registry.BasePath
if basePath == "" {
basePath = "."
}
if !core.PathIsAbs(basePath) {
basePath = core.Path(core.PathDir(registryPath), basePath)
}
allRepos := registry.List()
if len(allRepos) == 0 {
cli.Println("%s", i18n.T("cmd.pkg.list.no_packages"))
return nil
}
cli.Println("%s\n", repoNameStyle.Render(i18n.T("cmd.pkg.list.title")))
var installed, missing int
for _, repo := range allRepos {
repoPath := core.Path(basePath, repo.Name)
exists := coreio.Local.Exists(core.Path(repoPath, ".git"))
if exists {
installed++
} else {
missing++
}
status := successStyle.Render("✓")
if !exists {
status = dimStyle.Render("○")
}
description := repo.Description
if len(description) > 40 {
description = description[:37] + "..."
}
if description == "" {
description = dimStyle.Render(i18n.T("cmd.pkg.no_description"))
}
cli.Println(" %s %s", status, repoNameStyle.Render(repo.Name))
cli.Println(" %s", description)
}
cli.Blank()
cli.Println("%s %s", dimStyle.Render(i18n.Label("total")), i18n.T("cmd.pkg.list.summary", map[string]int{"Installed": installed, "Missing": missing}))
if missing > 0 {
cli.Println("\n%s %s", i18n.T("cmd.pkg.list.install_missing"), dimStyle.Render("core setup"))
}
return nil
}
var updateAll bool
// addPkgUpdateCommand adds the 'pkg update' command.
func addPkgUpdateCommand(parent *cobra.Command) {
updateCmd := &cobra.Command{
Use: "update [packages...]",
Short: i18n.T("cmd.pkg.update.short"),
Long: i18n.T("cmd.pkg.update.long"),
RunE: func(cmd *cobra.Command, args []string) error {
if !updateAll && len(args) == 0 {
return cli.Err(i18n.T("cmd.pkg.error.specify_package"))
}
return runPkgUpdate(args, updateAll)
},
}
updateCmd.Flags().BoolVar(&updateAll, "all", false, i18n.T("cmd.pkg.update.flag.all"))
parent.AddCommand(updateCmd)
}
func runPkgUpdate(packages []string, all bool) error {
registryPath, err := repos.FindRegistry(coreio.Local)
if err != nil {
return cli.Err(i18n.T("cmd.pkg.error.no_repos_yaml"))
}
registry, err := repos.LoadRegistry(coreio.Local, registryPath)
if err != nil {
return cli.Wrap(err, i18n.T("i18n.fail.load", "registry"))
}
basePath := registry.BasePath
if basePath == "" {
basePath = "."
}
if !core.PathIsAbs(basePath) {
basePath = core.Path(core.PathDir(registryPath), basePath)
}
var toUpdate []string
if all {
for _, repo := range registry.List() {
toUpdate = append(toUpdate, repo.Name)
}
} else {
toUpdate = packages
}
cli.Println("%s %s\n", dimStyle.Render(i18n.T("cmd.pkg.update.update_label")), i18n.T("cmd.pkg.update.updating", map[string]int{"Count": len(toUpdate)}))
var updated, skipped, failed int
for _, name := range toUpdate {
repoPath := core.Path(basePath, name)
if _, err := coreio.Local.List(core.Path(repoPath, ".git")); err != nil {
cli.Println(" %s %s (%s)", dimStyle.Render("○"), name, i18n.T("cmd.pkg.update.not_installed"))
skipped++
continue
}
cli.Print(" %s %s... ", dimStyle.Render("↓"), name)
proc := exec.Command("git", "-C", repoPath, "pull", "--ff-only")
output, err := proc.CombinedOutput()
if err != nil {
cli.Println("%s", errorStyle.Render("✗"))
cli.Println(" %s", core.Trim(string(output)))
failed++
continue
}
if core.Contains(string(output), "Already up to date") {
cli.Println("%s", dimStyle.Render(i18n.T("common.status.up_to_date")))
} else {
cli.Println("%s", successStyle.Render("✓"))
}
updated++
}
cli.Blank()
cli.Println("%s %s",
dimStyle.Render(i18n.T("i18n.done.update")), i18n.T("cmd.pkg.update.summary", map[string]int{"Updated": updated, "Skipped": skipped, "Failed": failed}))
return nil
}
// addPkgOutdatedCommand adds the 'pkg outdated' command.
func addPkgOutdatedCommand(parent *cobra.Command) {
outdatedCmd := &cobra.Command{
Use: "outdated",
Short: i18n.T("cmd.pkg.outdated.short"),
Long: i18n.T("cmd.pkg.outdated.long"),
RunE: func(cmd *cobra.Command, args []string) error {
return runPkgOutdated()
},
}
parent.AddCommand(outdatedCmd)
}
func runPkgOutdated() error {
registryPath, err := repos.FindRegistry(coreio.Local)
if err != nil {
return cli.Err(i18n.T("cmd.pkg.error.no_repos_yaml"))
}
registry, err := repos.LoadRegistry(coreio.Local, registryPath)
if err != nil {
return cli.Wrap(err, i18n.T("i18n.fail.load", "registry"))
}
basePath := registry.BasePath
if basePath == "" {
basePath = "."
}
if !core.PathIsAbs(basePath) {
basePath = core.Path(core.PathDir(registryPath), basePath)
}
cli.Println("%s %s\n", dimStyle.Render(i18n.T("cmd.pkg.outdated.outdated_label")), i18n.T("common.progress.checking_updates"))
var outdated, upToDate, notInstalled int
for _, repo := range registry.List() {
repoPath := core.Path(basePath, repo.Name)
if !coreio.Local.Exists(core.Path(repoPath, ".git")) {
notInstalled++
continue
}
// Fetch updates silently.
_ = exec.Command("git", "-C", repoPath, "fetch", "--quiet").Run()
// Check commit count behind upstream.
proc := exec.Command("git", "-C", repoPath, "rev-list", "--count", "HEAD..@{u}")
output, err := proc.Output()
if err != nil {
continue
}
commitCount := core.Trim(string(output))
if commitCount != "0" {
cli.Println(" %s %s (%s)",
errorStyle.Render("↓"), repoNameStyle.Render(repo.Name), i18n.T("cmd.pkg.outdated.commits_behind", map[string]string{"Count": commitCount}))
outdated++
} else {
upToDate++
}
}
cli.Blank()
if outdated == 0 {
cli.Println("%s %s", successStyle.Render(i18n.T("i18n.done.update")), i18n.T("cmd.pkg.outdated.all_up_to_date"))
} else {
cli.Println("%s %s",
dimStyle.Render(i18n.Label("summary")), i18n.T("cmd.pkg.outdated.summary", map[string]int{"Outdated": outdated, "UpToDate": upToDate}))
cli.Println("\n%s %s", i18n.T("cmd.pkg.outdated.update_with"), dimStyle.Render("core pkg update --all"))
}
return nil
}