feat(pkg): support install refs in shorthand
Some checks are pending
Security Scan / security (push) Waiting to run
Some checks are pending
Security Scan / security (push) Waiting to run
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
27e44f069a
commit
fcadba08b1
4 changed files with 132 additions and 12 deletions
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
|
|
@ -22,10 +23,12 @@ var (
|
|||
installAddToReg bool
|
||||
)
|
||||
|
||||
var errInvalidPkgInstallSource = errors.New("invalid repo format: use org/repo or org/repo@ref")
|
||||
|
||||
// addPkgInstallCommand adds the 'pkg install' command.
|
||||
func addPkgInstallCommand(parent *cobra.Command) {
|
||||
installCmd := &cobra.Command{
|
||||
Use: "install [org/]repo",
|
||||
Use: "install [org/]repo[@ref]",
|
||||
Short: i18n.T("cmd.pkg.install.short"),
|
||||
Long: i18n.T("cmd.pkg.install.long"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -48,14 +51,9 @@ func runPkgInstall(repoArg, targetDir string, addToRegistry bool) error {
|
|||
// Parse repo shorthand:
|
||||
// - repoName -> defaults to host-uk/repoName
|
||||
// - org/repo -> uses the explicit org
|
||||
org := "host-uk"
|
||||
repoName := repoArg
|
||||
if strings.Contains(repoArg, "/") {
|
||||
parts := strings.Split(repoArg, "/")
|
||||
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||
return errors.New(i18n.T("cmd.pkg.error.invalid_repo_format"))
|
||||
}
|
||||
org, repoName = parts[0], parts[1]
|
||||
org, repoName, ref, err := parsePkgInstallSource(repoArg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Determine target directory
|
||||
|
|
@ -93,11 +91,18 @@ func runPkgInstall(repoArg, targetDir string, addToRegistry bool) error {
|
|||
}
|
||||
|
||||
fmt.Printf("%s %s/%s\n", dimStyle.Render(i18n.T("cmd.pkg.install.installing_label")), org, repoName)
|
||||
if ref != "" {
|
||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("ref")), ref)
|
||||
}
|
||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("target")), repoPath)
|
||||
fmt.Println()
|
||||
|
||||
fmt.Printf(" %s... ", dimStyle.Render(i18n.T("common.status.cloning")))
|
||||
err := gitClone(ctx, org, repoName, repoPath)
|
||||
if ref == "" {
|
||||
err = gitClone(ctx, org, repoName, repoPath)
|
||||
} else {
|
||||
err = gitCloneRef(ctx, org, repoName, repoPath, ref)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", errorStyle.Render("✗ "+err.Error()))
|
||||
return err
|
||||
|
|
@ -118,6 +123,36 @@ func runPkgInstall(repoArg, targetDir string, addToRegistry bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func parsePkgInstallSource(repoArg string) (org, repoName, ref string, err error) {
|
||||
org = "host-uk"
|
||||
repoName = strings.TrimSpace(repoArg)
|
||||
if repoName == "" {
|
||||
return "", "", "", errors.New("repository argument required")
|
||||
}
|
||||
|
||||
if at := strings.LastIndex(repoName, "@"); at >= 0 {
|
||||
ref = strings.TrimSpace(repoName[at+1:])
|
||||
repoName = strings.TrimSpace(repoName[:at])
|
||||
if ref == "" || repoName == "" {
|
||||
return "", "", "", errInvalidPkgInstallSource
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(repoName, "/") {
|
||||
parts := strings.Split(repoName, "/")
|
||||
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||
return "", "", "", errInvalidPkgInstallSource
|
||||
}
|
||||
org, repoName = parts[0], parts[1]
|
||||
}
|
||||
|
||||
if strings.Contains(repoName, "/") {
|
||||
return "", "", "", errInvalidPkgInstallSource
|
||||
}
|
||||
|
||||
return org, repoName, ref, nil
|
||||
}
|
||||
|
||||
func addToRegistryFile(org, repoName string) error {
|
||||
regPath, err := repos.FindRegistry(coreio.Local)
|
||||
if err != nil {
|
||||
|
|
@ -146,6 +181,30 @@ func addToRegistryFile(org, repoName string) error {
|
|||
return coreio.Local.Write(regPath, content)
|
||||
}
|
||||
|
||||
func clonePackageAtRef(ctx context.Context, org, repo, path, ref string) error {
|
||||
if ghAuthenticated() {
|
||||
httpsURL := fmt.Sprintf("https://github.com/%s/%s.git", org, repo)
|
||||
args := []string{"repo", "clone", httpsURL, path, "--", "--branch", ref, "--single-branch"}
|
||||
cmd := exec.CommandContext(ctx, "gh", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
errStr := strings.TrimSpace(string(output))
|
||||
if strings.Contains(errStr, "already exists") {
|
||||
return errors.New(errStr)
|
||||
}
|
||||
}
|
||||
|
||||
args := []string{"clone", "--branch", ref, "--single-branch", fmt.Sprintf("git@github.com:%s/%s.git", org, repo), path}
|
||||
cmd := exec.CommandContext(ctx, "git", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.New(strings.TrimSpace(string(output)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func detectRepoType(name string) string {
|
||||
lower := strings.ToLower(name)
|
||||
if strings.Contains(lower, "-mod-") || strings.HasSuffix(lower, "-mod") {
|
||||
|
|
|
|||
|
|
@ -67,3 +67,48 @@ func TestRunPkgInstall_InvalidRepoFormat_Bad(t *testing.T) {
|
|||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "invalid repo format")
|
||||
}
|
||||
|
||||
func TestParsePkgInstallSource_Good(t *testing.T) {
|
||||
t.Run("default org and repo", func(t *testing.T) {
|
||||
org, repo, ref, err := parsePkgInstallSource("core-api")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "host-uk", org)
|
||||
assert.Equal(t, "core-api", repo)
|
||||
assert.Empty(t, ref)
|
||||
})
|
||||
|
||||
t.Run("explicit org and ref", func(t *testing.T) {
|
||||
org, repo, ref, err := parsePkgInstallSource("myorg/core-api@v1.2.3")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "myorg", org)
|
||||
assert.Equal(t, "core-api", repo)
|
||||
assert.Equal(t, "v1.2.3", ref)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunPkgInstall_WithRef_UsesRefClone_Good(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
targetDir := filepath.Join(tmp, "packages")
|
||||
|
||||
originalGitCloneRef := gitCloneRef
|
||||
t.Cleanup(func() {
|
||||
gitCloneRef = originalGitCloneRef
|
||||
})
|
||||
|
||||
var gotOrg, gotRepo, gotPath, gotRef string
|
||||
gitCloneRef = func(_ context.Context, org, repoName, repoPath, ref string) error {
|
||||
gotOrg = org
|
||||
gotRepo = repoName
|
||||
gotPath = repoPath
|
||||
gotRef = ref
|
||||
return nil
|
||||
}
|
||||
|
||||
err := runPkgInstall("myorg/core-api@v1.2.3", targetDir, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "myorg", gotOrg)
|
||||
assert.Equal(t, "core-api", gotRepo)
|
||||
assert.Equal(t, filepath.Join(targetDir, "core-api"), gotPath)
|
||||
assert.Equal(t, "v1.2.3", gotRef)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ var (
|
|||
dimStyle = cli.DimStyle
|
||||
ghAuthenticated = cli.GhAuthenticated
|
||||
gitClone = cli.GitClone
|
||||
gitCloneRef = clonePackageAtRef
|
||||
)
|
||||
|
||||
// AddPkgCommands adds the 'pkg' command and subcommands for package management.
|
||||
|
|
|
|||
|
|
@ -486,9 +486,19 @@ func ChooseMultiAction[T any](verb, subject string, items []T, opts ...ChooseOpt
|
|||
// GitClone clones a GitHub repository to the specified path.
|
||||
// Prefers 'gh repo clone' if authenticated, falls back to SSH.
|
||||
func GitClone(ctx context.Context, org, repo, path string) error {
|
||||
return GitCloneRef(ctx, org, repo, path, "")
|
||||
}
|
||||
|
||||
// GitCloneRef clones a GitHub repository at a specific ref to the specified path.
|
||||
// Prefers 'gh repo clone' if authenticated, falls back to SSH.
|
||||
func GitCloneRef(ctx context.Context, org, repo, path, ref string) error {
|
||||
if GhAuthenticated() {
|
||||
httpsURL := fmt.Sprintf("https://github.com/%s/%s.git", org, repo)
|
||||
cmd := exec.CommandContext(ctx, "gh", "repo", "clone", httpsURL, path)
|
||||
args := []string{"repo", "clone", httpsURL, path}
|
||||
if ref != "" {
|
||||
args = append(args, "--", "--branch", ref, "--single-branch")
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, "gh", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
return nil
|
||||
|
|
@ -499,7 +509,12 @@ func GitClone(ctx context.Context, org, repo, path string) error {
|
|||
}
|
||||
}
|
||||
// Fall back to SSH clone
|
||||
cmd := exec.CommandContext(ctx, "git", "clone", fmt.Sprintf("git@github.com:%s/%s.git", org, repo), path)
|
||||
args := []string{"clone"}
|
||||
if ref != "" {
|
||||
args = append(args, "--branch", ref, "--single-branch")
|
||||
}
|
||||
args = append(args, fmt.Sprintf("git@github.com:%s/%s.git", org, repo), path)
|
||||
cmd := exec.CommandContext(ctx, "git", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.New(strings.TrimSpace(string(output)))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue