Merge pull request '[agent/codex:gpt-5.4-mini] Read ~/spec/code/core/go/cli/RFC.md fully. Find ONE feature ...' (#53) from agent/read---spec-code-core-go-cli-rfc-md-full into dev
Some checks failed
Security Scan / security (push) Has been cancelled

This commit is contained in:
Virgil 2026-04-02 05:39:19 +00:00
commit 8dd4762ce8
3 changed files with 163 additions and 13 deletions

View file

@ -157,6 +157,7 @@ func runPkgList(format string) error {
}
var updateAll bool
var updateFormat string
// addPkgUpdateCommand adds the 'pkg update' command.
func addPkgUpdateCommand(parent *cobra.Command) {
@ -165,16 +166,40 @@ func addPkgUpdateCommand(parent *cobra.Command) {
Short: i18n.T("cmd.pkg.update.short"),
Long: i18n.T("cmd.pkg.update.long"),
RunE: func(cmd *cobra.Command, args []string) error {
return runPkgUpdate(args, updateAll)
format, err := cmd.Flags().GetString("format")
if err != nil {
return err
}
return runPkgUpdate(args, updateAll, format)
},
}
updateCmd.Flags().BoolVar(&updateAll, "all", false, i18n.T("cmd.pkg.update.flag.all"))
updateCmd.Flags().StringVar(&updateFormat, "format", "table", "Output format: table or json")
parent.AddCommand(updateCmd)
}
func runPkgUpdate(packages []string, all bool) error {
type pkgUpdateEntry struct {
Name string `json:"name"`
Path string `json:"path"`
Installed bool `json:"installed"`
Status string `json:"status"`
Output string `json:"output,omitempty"`
}
type pkgUpdateReport struct {
Format string `json:"format"`
Total int `json:"total"`
Installed int `json:"installed"`
Missing int `json:"missing"`
Updated int `json:"updated"`
UpToDate int `json:"upToDate"`
Failed int `json:"failed"`
Packages []pkgUpdateEntry `json:"packages"`
}
func runPkgUpdate(packages []string, all bool, format string) error {
regPath, err := repos.FindRegistry(coreio.Local)
if err != nil {
return errors.New(i18n.T("cmd.pkg.error.no_repos_yaml"))
@ -193,6 +218,7 @@ func runPkgUpdate(packages []string, all bool) error {
basePath = filepath.Join(filepath.Dir(regPath), basePath)
}
jsonOutput := strings.EqualFold(format, "json")
var toUpdate []string
if all || len(packages) == 0 {
for _, r := range reg.List() {
@ -202,44 +228,117 @@ func runPkgUpdate(packages []string, all bool) error {
toUpdate = packages
}
if !jsonOutput {
fmt.Printf("%s %s\n\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
var updated, upToDate, skipped, failed int
var entries []pkgUpdateEntry
for _, name := range toUpdate {
repoPath := filepath.Join(basePath, name)
if _, err := coreio.Local.List(filepath.Join(repoPath, ".git")); err != nil {
if !jsonOutput {
fmt.Printf(" %s %s (%s)\n", dimStyle.Render("○"), name, i18n.T("cmd.pkg.update.not_installed"))
}
if jsonOutput {
entries = append(entries, pkgUpdateEntry{
Name: name,
Path: repoPath,
Installed: false,
Status: "missing",
})
}
skipped++
continue
}
if !jsonOutput {
fmt.Printf(" %s %s... ", dimStyle.Render("↓"), name)
}
cmd := exec.Command("git", "-C", repoPath, "pull", "--ff-only")
output, err := cmd.CombinedOutput()
if err != nil {
if !jsonOutput {
fmt.Printf("%s\n", errorStyle.Render("✗"))
fmt.Printf(" %s\n", strings.TrimSpace(string(output)))
}
if jsonOutput {
entries = append(entries, pkgUpdateEntry{
Name: name,
Path: repoPath,
Installed: true,
Status: "failed",
Output: strings.TrimSpace(string(output)),
})
}
failed++
continue
}
if strings.Contains(string(output), "Already up to date") {
if !jsonOutput {
fmt.Printf("%s\n", dimStyle.Render(i18n.T("common.status.up_to_date")))
}
if jsonOutput {
entries = append(entries, pkgUpdateEntry{
Name: name,
Path: repoPath,
Installed: true,
Status: "up_to_date",
Output: strings.TrimSpace(string(output)),
})
}
upToDate++
} else {
if !jsonOutput {
fmt.Printf("%s\n", successStyle.Render("✓"))
}
if jsonOutput {
entries = append(entries, pkgUpdateEntry{
Name: name,
Path: repoPath,
Installed: true,
Status: "updated",
Output: strings.TrimSpace(string(output)),
})
}
updated++
}
}
if jsonOutput {
report := pkgUpdateReport{
Format: "json",
Total: len(toUpdate),
Installed: updated + upToDate + failed,
Missing: skipped,
Updated: updated,
UpToDate: upToDate,
Failed: failed,
Packages: entries,
}
return printPkgUpdateJSON(report)
}
fmt.Println()
fmt.Printf("%s %s\n",
dimStyle.Render(i18n.T("i18n.done.update")), i18n.T("cmd.pkg.update.summary", map[string]int{"Updated": updated, "Skipped": skipped, "Failed": failed}))
dimStyle.Render(i18n.T("i18n.done.update")), i18n.T("cmd.pkg.update.summary", map[string]int{"Updated": updated + upToDate, "Skipped": skipped, "Failed": failed}))
return nil
}
func printPkgUpdateJSON(report pkgUpdateReport) error {
out, err := json.MarshalIndent(report, "", " ")
if err != nil {
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.format", "update results"), err)
}
fmt.Println(string(out))
return nil
}
// addPkgOutdatedCommand adds the 'pkg outdated' command.
func addPkgOutdatedCommand(parent *cobra.Command) {
var format string

View file

@ -297,7 +297,7 @@ func TestRunPkgUpdate_NoArgs_UpdatesAll(t *testing.T) {
withWorkingDir(t, tmp)
out := capturePkgOutput(t, func() {
err := runPkgUpdate(nil, false)
err := runPkgUpdate(nil, false, "table")
require.NoError(t, err)
})
@ -305,3 +305,46 @@ func TestRunPkgUpdate_NoArgs_UpdatesAll(t *testing.T) {
assert.Contains(t, out, "core-fresh")
assert.Contains(t, out, "core-stale")
}
func TestRunPkgUpdate_JSON(t *testing.T) {
tmp := setupOutdatedRegistry(t)
withWorkingDir(t, tmp)
out := capturePkgOutput(t, func() {
err := runPkgUpdate(nil, false, "json")
require.NoError(t, err)
})
var report pkgUpdateReport
require.NoError(t, json.Unmarshal([]byte(strings.TrimSpace(out)), &report))
assert.Equal(t, "json", report.Format)
assert.Equal(t, 3, report.Total)
assert.Equal(t, 2, report.Installed)
assert.Equal(t, 1, report.Missing)
assert.Equal(t, 1, report.Updated)
assert.Equal(t, 1, report.UpToDate)
assert.Equal(t, 0, report.Failed)
require.Len(t, report.Packages, 3)
var updatedFound, upToDateFound, missingFound bool
for _, pkg := range report.Packages {
switch pkg.Name {
case "core-stale":
updatedFound = true
assert.True(t, pkg.Installed)
assert.Equal(t, "updated", pkg.Status)
case "core-fresh":
upToDateFound = true
assert.True(t, pkg.Installed)
assert.Equal(t, "up_to_date", pkg.Status)
case "core-missing":
missingFound = true
assert.False(t, pkg.Installed)
assert.Equal(t, "missing", pkg.Status)
}
}
assert.True(t, updatedFound)
assert.True(t, upToDateFound)
assert.True(t, missingFound)
}

View file

@ -126,6 +126,7 @@ core pkg update [<name>...] [flags]
| Flag | Description |
|------|-------------|
| `--all` | Update all packages |
| `--format` | Output format (`table` or `json`) |
### Examples
@ -135,8 +136,15 @@ core pkg update core-php
# Update all packages
core pkg update --all
# JSON output for automation
core pkg update --format json
```
### JSON Output
When `--format json` is set, `core pkg update` emits a structured report with per-package update status and summary totals.
---
## pkg outdated