fix(pkgcmd): remove packages from registry

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 10:44:28 +00:00
parent 207a38e236
commit 1242723ac1
2 changed files with 107 additions and 0 deletions

View file

@ -18,6 +18,7 @@ import (
coreio "forge.lthn.ai/core/go-io" coreio "forge.lthn.ai/core/go-io"
"forge.lthn.ai/core/go-scm/repos" "forge.lthn.ai/core/go-scm/repos"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/yaml.v3"
) )
var removeForce bool var removeForce bool
@ -87,10 +88,78 @@ func runPkgRemove(name string, force bool) error {
return err return err
} }
if err := removeRepoFromRegistry(regPath, name); err != nil {
return fmt.Errorf("removed %s from disk, but failed to update registry: %w", name, err)
}
fmt.Printf("%s\n", successStyle.Render("ok")) fmt.Printf("%s\n", successStyle.Render("ok"))
return nil return nil
} }
func removeRepoFromRegistry(regPath, name string) error {
content, err := coreio.Local.Read(regPath)
if err != nil {
return err
}
var doc yaml.Node
if err := yaml.Unmarshal([]byte(content), &doc); err != nil {
return fmt.Errorf("failed to parse registry file: %w", err)
}
if len(doc.Content) == 0 {
return errors.New("registry file is empty")
}
root := doc.Content[0]
reposNode := mappingValue(root, "repos")
if reposNode == nil {
return errors.New("registry file has no repos section")
}
if reposNode.Kind != yaml.MappingNode {
return errors.New("registry repos section is malformed")
}
if removeMappingEntry(reposNode, name) {
out, err := yaml.Marshal(&doc)
if err != nil {
return fmt.Errorf("failed to format registry file: %w", err)
}
return coreio.Local.Write(regPath, string(out))
}
return nil
}
func mappingValue(node *yaml.Node, key string) *yaml.Node {
if node == nil || node.Kind != yaml.MappingNode {
return nil
}
for i := 0; i+1 < len(node.Content); i += 2 {
if node.Content[i].Value == key {
return node.Content[i+1]
}
}
return nil
}
func removeMappingEntry(node *yaml.Node, key string) bool {
if node == nil || node.Kind != yaml.MappingNode {
return false
}
for i := 0; i+1 < len(node.Content); i += 2 {
if node.Content[i].Value != key {
continue
}
node.Content = append(node.Content[:i], node.Content[i+2:]...)
return true
}
return false
}
// checkRepoSafety checks a git repo for uncommitted changes and unpushed branches. // checkRepoSafety checks a git repo for uncommitted changes and unpushed branches.
func checkRepoSafety(repoPath string) (blocked bool, reasons []string) { func checkRepoSafety(repoPath string) (blocked bool, reasons []string) {
// Check for uncommitted changes (staged, unstaged, untracked) // Check for uncommitted changes (staged, unstaged, untracked)

View file

@ -4,6 +4,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -78,6 +79,43 @@ func TestCheckRepoSafety_Stash(t *testing.T) {
assert.True(t, found, "expected stash warning in reasons: %v", reasons) assert.True(t, found, "expected stash warning in reasons: %v", reasons)
} }
func TestRunPkgRemove_RemovesRegistryEntry_Good(t *testing.T) {
tmp := t.TempDir()
repoPath := setupTestRepo(t, tmp, "core-alpha")
registry := strings.TrimSpace(`
version: 1
org: host-uk
base_path: .
repos:
core-alpha:
type: foundation
description: Alpha package
core-beta:
type: module
description: Beta package
`) + "\n"
require.NoError(t, os.WriteFile(filepath.Join(tmp, "repos.yaml"), []byte(registry), 0644))
oldwd, err := os.Getwd()
require.NoError(t, err)
require.NoError(t, os.Chdir(tmp))
t.Cleanup(func() {
require.NoError(t, os.Chdir(oldwd))
})
require.NoError(t, runPkgRemove("core-alpha", false))
_, err = os.Stat(repoPath)
assert.True(t, os.IsNotExist(err))
updated, err := os.ReadFile(filepath.Join(tmp, "repos.yaml"))
require.NoError(t, err)
assert.NotContains(t, string(updated), "core-alpha")
assert.Contains(t, string(updated), "core-beta")
}
func contains(s, substr string) bool { func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsStr(s, substr)) return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsStr(s, substr))
} }