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
Some checks failed
Security Scan / security (push) Has been cancelled
This commit is contained in:
commit
8dd4762ce8
3 changed files with 163 additions and 13 deletions
|
|
@ -157,6 +157,7 @@ func runPkgList(format string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var updateAll bool
|
var updateAll bool
|
||||||
|
var updateFormat string
|
||||||
|
|
||||||
// addPkgUpdateCommand adds the 'pkg update' command.
|
// addPkgUpdateCommand adds the 'pkg update' command.
|
||||||
func addPkgUpdateCommand(parent *cobra.Command) {
|
func addPkgUpdateCommand(parent *cobra.Command) {
|
||||||
|
|
@ -165,16 +166,40 @@ func addPkgUpdateCommand(parent *cobra.Command) {
|
||||||
Short: i18n.T("cmd.pkg.update.short"),
|
Short: i18n.T("cmd.pkg.update.short"),
|
||||||
Long: i18n.T("cmd.pkg.update.long"),
|
Long: i18n.T("cmd.pkg.update.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
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().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)
|
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)
|
regPath, err := repos.FindRegistry(coreio.Local)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(i18n.T("cmd.pkg.error.no_repos_yaml"))
|
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)
|
basePath = filepath.Join(filepath.Dir(regPath), basePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jsonOutput := strings.EqualFold(format, "json")
|
||||||
var toUpdate []string
|
var toUpdate []string
|
||||||
if all || len(packages) == 0 {
|
if all || len(packages) == 0 {
|
||||||
for _, r := range reg.List() {
|
for _, r := range reg.List() {
|
||||||
|
|
@ -202,44 +228,117 @@ func runPkgUpdate(packages []string, all bool) error {
|
||||||
toUpdate = packages
|
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)}))
|
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 {
|
for _, name := range toUpdate {
|
||||||
repoPath := filepath.Join(basePath, name)
|
repoPath := filepath.Join(basePath, name)
|
||||||
|
|
||||||
if _, err := coreio.Local.List(filepath.Join(repoPath, ".git")); err != nil {
|
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"))
|
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++
|
skipped++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !jsonOutput {
|
||||||
fmt.Printf(" %s %s... ", dimStyle.Render("↓"), name)
|
fmt.Printf(" %s %s... ", dimStyle.Render("↓"), name)
|
||||||
|
}
|
||||||
|
|
||||||
cmd := exec.Command("git", "-C", repoPath, "pull", "--ff-only")
|
cmd := exec.Command("git", "-C", repoPath, "pull", "--ff-only")
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if !jsonOutput {
|
||||||
fmt.Printf("%s\n", errorStyle.Render("✗"))
|
fmt.Printf("%s\n", errorStyle.Render("✗"))
|
||||||
fmt.Printf(" %s\n", strings.TrimSpace(string(output)))
|
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++
|
failed++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(string(output), "Already up to date") {
|
if strings.Contains(string(output), "Already up to date") {
|
||||||
|
if !jsonOutput {
|
||||||
fmt.Printf("%s\n", dimStyle.Render(i18n.T("common.status.up_to_date")))
|
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 {
|
} else {
|
||||||
|
if !jsonOutput {
|
||||||
fmt.Printf("%s\n", successStyle.Render("✓"))
|
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++
|
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.Println()
|
||||||
fmt.Printf("%s %s\n",
|
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
|
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.
|
// addPkgOutdatedCommand adds the 'pkg outdated' command.
|
||||||
func addPkgOutdatedCommand(parent *cobra.Command) {
|
func addPkgOutdatedCommand(parent *cobra.Command) {
|
||||||
var format string
|
var format string
|
||||||
|
|
|
||||||
|
|
@ -297,7 +297,7 @@ func TestRunPkgUpdate_NoArgs_UpdatesAll(t *testing.T) {
|
||||||
withWorkingDir(t, tmp)
|
withWorkingDir(t, tmp)
|
||||||
|
|
||||||
out := capturePkgOutput(t, func() {
|
out := capturePkgOutput(t, func() {
|
||||||
err := runPkgUpdate(nil, false)
|
err := runPkgUpdate(nil, false, "table")
|
||||||
require.NoError(t, err)
|
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-fresh")
|
||||||
assert.Contains(t, out, "core-stale")
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,7 @@ core pkg update [<name>...] [flags]
|
||||||
| Flag | Description |
|
| Flag | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `--all` | Update all packages |
|
| `--all` | Update all packages |
|
||||||
|
| `--format` | Output format (`table` or `json`) |
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
|
@ -135,8 +136,15 @@ core pkg update core-php
|
||||||
|
|
||||||
# Update all packages
|
# Update all packages
|
||||||
core pkg update --all
|
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
|
## pkg outdated
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue