refactor(cli): move commands from cmd/ to pkg/ with self-registration
Implements defence in depth through build variants - only compiled code
exists in the binary. Commands now self-register via cli.RegisterCommands()
in their init() functions, mirroring the i18n.RegisterLocales() pattern.
Structure changes:
- cmd/{ai,build,ci,dev,docs,doctor,go,php,pkg,sdk,setup,test,vm}/ → pkg/*/cmd_*.go
- cmd/core_dev.go, cmd/core_ci.go → cmd/variants/{full,ci,php,minimal}.go
- Added pkg/cli/commands.go with RegisterCommands API
- Updated pkg/cli/runtime.go to attach registered commands
Build variants:
- go build → full (21MB, all 13 command groups)
- go build -tags ci → ci (18MB, build/ci/sdk/doctor)
- go build -tags php → php (14MB, php/doctor)
- go build -tags minimal → minimal (11MB, doctor only)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 21:55:55 +00:00
|
|
|
// cmd_registry.go implements registry mode for cloning packages.
|
2026-01-30 00:22:47 +00:00
|
|
|
//
|
|
|
|
|
// Registry mode is activated when a repos.yaml exists. It reads the registry
|
|
|
|
|
// and clones all (or selected) packages into the configured packages directory.
|
|
|
|
|
|
|
|
|
|
package setup
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
|
2026-01-30 10:32:05 +00:00
|
|
|
"github.com/host-uk/core/pkg/cli"
|
feat(i18n): add translation keys to all CLI commands
Replace hardcoded strings with i18n.T() calls across all cmd/* packages:
- ai, build, ci, dev, docs, doctor, go, php, pkg, sdk, setup, test, vm
Adds 500+ translation keys to en.json for command descriptions,
flag descriptions, labels, messages, and error strings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 02:37:57 +00:00
|
|
|
"github.com/host-uk/core/pkg/i18n"
|
2026-01-30 00:22:47 +00:00
|
|
|
"github.com/host-uk/core/pkg/repos"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// runRegistrySetup loads a registry from path and runs setup.
|
|
|
|
|
func runRegistrySetup(ctx context.Context, registryPath, only string, dryRun, all, runBuild bool) error {
|
|
|
|
|
reg, err := repos.LoadRegistry(registryPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to load registry: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return runRegistrySetupWithReg(ctx, reg, registryPath, only, dryRun, all, runBuild)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// runRegistrySetupWithReg runs setup with an already-loaded registry.
|
|
|
|
|
func runRegistrySetupWithReg(ctx context.Context, reg *repos.Registry, registryPath, only string, dryRun, all, runBuild bool) error {
|
refactor(i18n): migrate all pkg/* to grammar engine
Replace verbose map-based translation calls with concise grammar
engine helpers across all command packages:
- i18n.T("common.label.xxx") → i18n.Label("xxx")
- i18n.T("common.error.failed", map) → i18n.T("i18n.fail.verb", subj)
- i18n.T("common.progress.running", map) → i18n.ProgressSubject()
- i18n.T("common.count.xxx", map) → i18n.T("i18n.count.xxx", n)
Packages updated: ai, ci, dev, docs, php, pkgcmd, sdk, setup, test, vm
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 23:09:45 +00:00
|
|
|
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
feat(i18n): add translation keys to all CLI commands
Replace hardcoded strings with i18n.T() calls across all cmd/* packages:
- ai, build, ci, dev, docs, doctor, go, php, pkg, sdk, setup, test, vm
Adds 500+ translation keys to en.json for command descriptions,
flag descriptions, labels, messages, and error strings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 02:37:57 +00:00
|
|
|
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.setup.org_label")), reg.Org)
|
2026-01-30 00:22:47 +00:00
|
|
|
|
2026-02-01 01:59:27 +00:00
|
|
|
registryDir := filepath.Dir(registryPath)
|
|
|
|
|
|
2026-01-30 00:22:47 +00:00
|
|
|
// Determine base path for cloning
|
|
|
|
|
basePath := reg.BasePath
|
|
|
|
|
if basePath == "" {
|
2026-02-01 01:59:27 +00:00
|
|
|
// Load workspace config to see if packages_dir is set
|
|
|
|
|
wsConfig, err := repos.LoadWorkspaceConfig(registryDir)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to load workspace config: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if wsConfig.PackagesDir != "" {
|
|
|
|
|
basePath = wsConfig.PackagesDir
|
|
|
|
|
} else {
|
|
|
|
|
basePath = "./packages"
|
|
|
|
|
}
|
2026-01-30 00:22:47 +00:00
|
|
|
}
|
2026-02-01 01:59:27 +00:00
|
|
|
|
2026-01-30 00:22:47 +00:00
|
|
|
// Expand ~
|
|
|
|
|
if strings.HasPrefix(basePath, "~/") {
|
|
|
|
|
home, _ := os.UserHomeDir()
|
|
|
|
|
basePath = filepath.Join(home, basePath[2:])
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-01 01:59:27 +00:00
|
|
|
// Resolve relative to registry location
|
|
|
|
|
if !filepath.IsAbs(basePath) {
|
|
|
|
|
basePath = filepath.Join(registryDir, basePath)
|
|
|
|
|
}
|
|
|
|
|
|
refactor(i18n): migrate all pkg/* to grammar engine
Replace verbose map-based translation calls with concise grammar
engine helpers across all command packages:
- i18n.T("common.label.xxx") → i18n.Label("xxx")
- i18n.T("common.error.failed", map) → i18n.T("i18n.fail.verb", subj)
- i18n.T("common.progress.running", map) → i18n.ProgressSubject()
- i18n.T("common.count.xxx", map) → i18n.T("i18n.count.xxx", n)
Packages updated: ai, ci, dev, docs, php, pkgcmd, sdk, setup, test, vm
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 23:09:45 +00:00
|
|
|
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("target")), basePath)
|
2026-01-30 00:22:47 +00:00
|
|
|
|
|
|
|
|
// Parse type filter
|
|
|
|
|
var typeFilter []string
|
|
|
|
|
if only != "" {
|
|
|
|
|
for _, t := range strings.Split(only, ",") {
|
|
|
|
|
typeFilter = append(typeFilter, strings.TrimSpace(t))
|
|
|
|
|
}
|
refactor(i18n): migrate all pkg/* to grammar engine
Replace verbose map-based translation calls with concise grammar
engine helpers across all command packages:
- i18n.T("common.label.xxx") → i18n.Label("xxx")
- i18n.T("common.error.failed", map) → i18n.T("i18n.fail.verb", subj)
- i18n.T("common.progress.running", map) → i18n.ProgressSubject()
- i18n.T("common.count.xxx", map) → i18n.T("i18n.count.xxx", n)
Packages updated: ai, ci, dev, docs, php, pkgcmd, sdk, setup, test, vm
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 23:09:45 +00:00
|
|
|
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("filter")), only)
|
2026-01-30 00:22:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure base path exists
|
|
|
|
|
if !dryRun {
|
|
|
|
|
if err := os.MkdirAll(basePath, 0755); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to create packages directory: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get all available repos
|
|
|
|
|
allRepos := reg.List()
|
|
|
|
|
|
|
|
|
|
// Determine which repos to clone
|
|
|
|
|
var toClone []*repos.Repo
|
|
|
|
|
var skipped, exists int
|
|
|
|
|
|
|
|
|
|
// Use wizard in interactive mode, unless --all specified
|
|
|
|
|
useWizard := isTerminal() && !all && !dryRun
|
|
|
|
|
|
|
|
|
|
if useWizard {
|
|
|
|
|
selected, err := runPackageWizard(reg, typeFilter)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("wizard error: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build set of selected repos
|
|
|
|
|
selectedSet := make(map[string]bool)
|
|
|
|
|
for _, name := range selected {
|
|
|
|
|
selectedSet[name] = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Filter repos based on selection
|
|
|
|
|
for _, repo := range allRepos {
|
|
|
|
|
if !selectedSet[repo.Name] {
|
|
|
|
|
skipped++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if already exists
|
|
|
|
|
repoPath := filepath.Join(basePath, repo.Name)
|
|
|
|
|
if _, err := os.Stat(filepath.Join(repoPath, ".git")); err == nil {
|
|
|
|
|
exists++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toClone = append(toClone, repo)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Non-interactive: filter by type
|
|
|
|
|
typeFilterSet := make(map[string]bool)
|
|
|
|
|
for _, t := range typeFilter {
|
|
|
|
|
typeFilterSet[t] = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, repo := range allRepos {
|
|
|
|
|
// Skip if type filter doesn't match (when filter is specified)
|
|
|
|
|
if len(typeFilterSet) > 0 && !typeFilterSet[repo.Type] {
|
|
|
|
|
skipped++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Skip if clone: false
|
|
|
|
|
if repo.Clone != nil && !*repo.Clone {
|
|
|
|
|
skipped++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if already exists
|
|
|
|
|
repoPath := filepath.Join(basePath, repo.Name)
|
|
|
|
|
if _, err := os.Stat(filepath.Join(repoPath, ".git")); err == nil {
|
|
|
|
|
exists++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toClone = append(toClone, repo)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Summary
|
|
|
|
|
fmt.Println()
|
feat(i18n): add translation keys to all CLI commands
Replace hardcoded strings with i18n.T() calls across all cmd/* packages:
- ai, build, ci, dev, docs, doctor, go, php, pkg, sdk, setup, test, vm
Adds 500+ translation keys to en.json for command descriptions,
flag descriptions, labels, messages, and error strings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 02:37:57 +00:00
|
|
|
fmt.Printf("%s, %s, %s\n",
|
|
|
|
|
i18n.T("cmd.setup.to_clone", map[string]interface{}{"Count": len(toClone)}),
|
|
|
|
|
i18n.T("cmd.setup.exist", map[string]interface{}{"Count": exists}),
|
refactor(i18n): consolidate duplicate translation keys into common section
Add common.* keys for reusable translations:
- common.label.* - UI labels (error, done, status, version, etc.)
- common.status.* - status words (running, stopped, dirty, synced)
- common.error.* - error messages (failed, not_found, working_dir)
- common.flag.* - CLI flag descriptions (registry, verbose, etc.)
- common.count.* - count templates (failed, passed, skipped)
- common.result.* - result messages (all_passed, no_issues)
- common.progress.* - progress messages (running, checking)
- common.hint.* - help hints (install_with, fix_deps)
Update all cmd/* files to use common keys instead of duplicated
command-specific keys. Reduces translation maintenance burden
and ensures consistency across the CLI.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 11:32:25 +00:00
|
|
|
i18n.T("common.count.skipped", map[string]interface{}{"Count": skipped}))
|
2026-01-30 00:22:47 +00:00
|
|
|
|
|
|
|
|
if len(toClone) == 0 {
|
feat(i18n): add translation keys to all CLI commands
Replace hardcoded strings with i18n.T() calls across all cmd/* packages:
- ai, build, ci, dev, docs, doctor, go, php, pkg, sdk, setup, test, vm
Adds 500+ translation keys to en.json for command descriptions,
flag descriptions, labels, messages, and error strings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 02:37:57 +00:00
|
|
|
fmt.Printf("\n%s\n", i18n.T("cmd.setup.nothing_to_clone"))
|
2026-01-30 00:22:47 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dryRun {
|
feat(i18n): add translation keys to all CLI commands
Replace hardcoded strings with i18n.T() calls across all cmd/* packages:
- ai, build, ci, dev, docs, doctor, go, php, pkg, sdk, setup, test, vm
Adds 500+ translation keys to en.json for command descriptions,
flag descriptions, labels, messages, and error strings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 02:37:57 +00:00
|
|
|
fmt.Printf("\n%s\n", i18n.T("cmd.setup.would_clone_list"))
|
2026-01-30 00:22:47 +00:00
|
|
|
for _, repo := range toClone {
|
|
|
|
|
fmt.Printf(" %s (%s)\n", repoNameStyle.Render(repo.Name), repo.Type)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Confirm in interactive mode
|
|
|
|
|
if useWizard {
|
|
|
|
|
confirmed, err := confirmClone(len(toClone), basePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if !confirmed {
|
feat(i18n): add translation keys to all CLI commands
Replace hardcoded strings with i18n.T() calls across all cmd/* packages:
- ai, build, ci, dev, docs, doctor, go, php, pkg, sdk, setup, test, vm
Adds 500+ translation keys to en.json for command descriptions,
flag descriptions, labels, messages, and error strings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 02:37:57 +00:00
|
|
|
fmt.Println(i18n.T("cmd.setup.cancelled"))
|
2026-01-30 00:22:47 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clone repos
|
|
|
|
|
fmt.Println()
|
|
|
|
|
var succeeded, failed int
|
|
|
|
|
|
|
|
|
|
for _, repo := range toClone {
|
refactor(i18n): consolidate duplicate translation keys into common section
Add common.* keys for reusable translations:
- common.label.* - UI labels (error, done, status, version, etc.)
- common.status.* - status words (running, stopped, dirty, synced)
- common.error.* - error messages (failed, not_found, working_dir)
- common.flag.* - CLI flag descriptions (registry, verbose, etc.)
- common.count.* - count templates (failed, passed, skipped)
- common.result.* - result messages (all_passed, no_issues)
- common.progress.* - progress messages (running, checking)
- common.hint.* - help hints (install_with, fix_deps)
Update all cmd/* files to use common keys instead of duplicated
command-specific keys. Reduces translation maintenance burden
and ensures consistency across the CLI.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 11:32:25 +00:00
|
|
|
fmt.Printf(" %s %s... ", dimStyle.Render(i18n.T("common.status.cloning")), repo.Name)
|
2026-01-30 00:22:47 +00:00
|
|
|
|
|
|
|
|
repoPath := filepath.Join(basePath, repo.Name)
|
|
|
|
|
|
|
|
|
|
err := gitClone(ctx, reg.Org, repo.Name, repoPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Printf("%s\n", errorStyle.Render("x "+err.Error()))
|
|
|
|
|
failed++
|
|
|
|
|
} else {
|
feat(i18n): add translation keys to all CLI commands
Replace hardcoded strings with i18n.T() calls across all cmd/* packages:
- ai, build, ci, dev, docs, doctor, go, php, pkg, sdk, setup, test, vm
Adds 500+ translation keys to en.json for command descriptions,
flag descriptions, labels, messages, and error strings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 02:37:57 +00:00
|
|
|
fmt.Printf("%s\n", successStyle.Render(i18n.T("cmd.setup.done")))
|
2026-01-30 00:22:47 +00:00
|
|
|
succeeded++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Summary
|
|
|
|
|
fmt.Println()
|
refactor(i18n): migrate all pkg/* to grammar engine
Replace verbose map-based translation calls with concise grammar
engine helpers across all command packages:
- i18n.T("common.label.xxx") → i18n.Label("xxx")
- i18n.T("common.error.failed", map) → i18n.T("i18n.fail.verb", subj)
- i18n.T("common.progress.running", map) → i18n.ProgressSubject()
- i18n.T("common.count.xxx", map) → i18n.T("i18n.count.xxx", n)
Packages updated: ai, ci, dev, docs, php, pkgcmd, sdk, setup, test, vm
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 23:09:45 +00:00
|
|
|
fmt.Printf("%s %s", successStyle.Render(i18n.Label("done")), i18n.T("cmd.setup.cloned_count", map[string]interface{}{"Count": succeeded}))
|
2026-01-30 00:22:47 +00:00
|
|
|
if failed > 0 {
|
refactor(i18n): migrate all pkg/* to grammar engine
Replace verbose map-based translation calls with concise grammar
engine helpers across all command packages:
- i18n.T("common.label.xxx") → i18n.Label("xxx")
- i18n.T("common.error.failed", map) → i18n.T("i18n.fail.verb", subj)
- i18n.T("common.progress.running", map) → i18n.ProgressSubject()
- i18n.T("common.count.xxx", map) → i18n.T("i18n.count.xxx", n)
Packages updated: ai, ci, dev, docs, php, pkgcmd, sdk, setup, test, vm
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 23:09:45 +00:00
|
|
|
fmt.Printf(", %s", errorStyle.Render(i18n.T("i18n.count.failed", failed)))
|
2026-01-30 00:22:47 +00:00
|
|
|
}
|
|
|
|
|
if exists > 0 {
|
feat(i18n): add translation keys to all CLI commands
Replace hardcoded strings with i18n.T() calls across all cmd/* packages:
- ai, build, ci, dev, docs, doctor, go, php, pkg, sdk, setup, test, vm
Adds 500+ translation keys to en.json for command descriptions,
flag descriptions, labels, messages, and error strings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 02:37:57 +00:00
|
|
|
fmt.Printf(", %s", i18n.T("cmd.setup.already_exist_count", map[string]interface{}{"Count": exists}))
|
2026-01-30 00:22:47 +00:00
|
|
|
}
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
// Run build if requested
|
|
|
|
|
if runBuild && succeeded > 0 {
|
|
|
|
|
fmt.Println()
|
refactor(i18n): migrate all pkg/* to grammar engine
Replace verbose map-based translation calls with concise grammar
engine helpers across all command packages:
- i18n.T("common.label.xxx") → i18n.Label("xxx")
- i18n.T("common.error.failed", map) → i18n.T("i18n.fail.verb", subj)
- i18n.T("common.progress.running", map) → i18n.ProgressSubject()
- i18n.T("common.count.xxx", map) → i18n.T("i18n.count.xxx", n)
Packages updated: ai, ci, dev, docs, php, pkgcmd, sdk, setup, test, vm
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 23:09:45 +00:00
|
|
|
fmt.Printf("%s %s\n", dimStyle.Render(">>"), i18n.ProgressSubject("run", "build"))
|
2026-01-30 00:22:47 +00:00
|
|
|
buildCmd := exec.Command("core", "build")
|
|
|
|
|
buildCmd.Dir = basePath
|
|
|
|
|
buildCmd.Stdout = os.Stdout
|
|
|
|
|
buildCmd.Stderr = os.Stderr
|
|
|
|
|
if err := buildCmd.Run(); err != nil {
|
refactor(i18n): migrate all pkg/* to grammar engine
Replace verbose map-based translation calls with concise grammar
engine helpers across all command packages:
- i18n.T("common.label.xxx") → i18n.Label("xxx")
- i18n.T("common.error.failed", map) → i18n.T("i18n.fail.verb", subj)
- i18n.T("common.progress.running", map) → i18n.ProgressSubject()
- i18n.T("common.count.xxx", map) → i18n.T("i18n.count.xxx", n)
Packages updated: ai, ci, dev, docs, php, pkgcmd, sdk, setup, test, vm
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 23:09:45 +00:00
|
|
|
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.run", "build"), err)
|
2026-01-30 00:22:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// gitClone clones a repository using gh CLI or git.
|
|
|
|
|
func gitClone(ctx context.Context, org, repo, path string) error {
|
|
|
|
|
// Try gh clone first with HTTPS (works without SSH keys)
|
2026-01-30 10:32:05 +00:00
|
|
|
if cli.GhAuthenticated() {
|
2026-01-30 00:22:47 +00:00
|
|
|
// Use HTTPS URL directly to bypass git_protocol config
|
|
|
|
|
httpsURL := fmt.Sprintf("https://github.com/%s/%s.git", org, repo)
|
|
|
|
|
cmd := exec.CommandContext(ctx, "gh", "repo", "clone", httpsURL, path)
|
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
|
|
|
if err == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
errStr := strings.TrimSpace(string(output))
|
|
|
|
|
// Only fall through to SSH if it's an auth error
|
|
|
|
|
if !strings.Contains(errStr, "Permission denied") &&
|
|
|
|
|
!strings.Contains(errStr, "could not read") {
|
|
|
|
|
return fmt.Errorf("%s", errStr)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fallback to git clone via SSH
|
|
|
|
|
url := fmt.Sprintf("git@github.com:%s/%s.git", org, repo)
|
|
|
|
|
cmd := exec.CommandContext(ctx, "git", "clone", url, path)
|
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("%s", strings.TrimSpace(string(output)))
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|