diff --git a/cmd/init.go b/cmd/init.go index fa9f318f5..dcc8ff650 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -40,6 +40,12 @@ type initOptions struct { eventPreset string configRef string checkoutRef string + // Local onboarding options + localOnly bool + dir string + skipDocker bool + skipConfig bool + verbose bool } type UserInputReader interface { @@ -69,66 +75,81 @@ func newInitCommand(config *settings.Config) *cobra.Command { var initCmd = &cobra.Command{ Use: "init [org-slug] [flags]", Short: "Initialize a new CircleCI project", - Long: `Creates a new project, pipeline, and trigger in one easy step. + Long: `Detect your project's tech stack, run tests locally, and generate a CircleCI config. -This command will guide you through setting up a complete CircleCI project by: -1. Creating a new project in your CircleCI organization -2. Setting up a pipeline definition with your repository -3. Creating a trigger to automatically run the pipeline +When run without authentication, this command will: +1. Scan your repository to detect the language, dependencies, and test commands +2. Select the best matching CircleCI convenience image +3. Run tests locally in a Docker container +4. Generate a .circleci/config.yml +5. Suggest CI optimizations (caching, linting, parallelism) + +When authenticated, it can additionally create the project on CircleCI. Examples: - # Interactive mode - prompts for all required values + # Local onboarding (no account needed) circleci init - # With org slug argument - circleci init github/myorg - circleci init circleci/myorg + # Skip Docker test execution + circleci init --skip-docker - # With flags to skip some prompts - circleci init github/myorg --project-name myproject --repo-id 123456 + # Scan a different directory + circleci init --dir ./my-project - # Full specification with all flags - circleci init github/myorg --project-name myproject \ - --pipeline-name my-pipeline --repo-id 123456 --trigger-name my-trigger`, - PreRunE: func(cmd *cobra.Command, args []string) error { - // Initialize project client - projectClient, err := projectapi.NewProjectRestClient(*config) - if err != nil { - return err - } - opts.projectClient = projectClient + # Force local-only mode even when authenticated + circleci init --local - // Initialize pipeline client - pipelineClient, err := pipelineapi.NewPipelineRestClient(*config) - if err != nil { - return err - } - opts.pipelineClient = pipelineClient + # With org slug for remote project creation + circleci init github/myorg`, + RunE: func(cmd *cobra.Command, args []string) error { + // Determine if we should run the local onboarding flow. + // Run local flow if: --local flag is set, OR no auth token is configured. + runLocal := opts.localOnly || config.Token == "" + + if runLocal { + err := runOnboard(cmd.Context(), onboardOptions{ + dir: opts.dir, + skipDocker: opts.skipDocker, + skipConfig: opts.skipConfig, + verbose: opts.verbose, + }, cmd.OutOrStdout()) + if err != nil { + return err + } - // Initialize trigger client - triggerClient, err := triggerapi.NewTriggerRestClient(*config) - if err != nil { - return err + if config.Token == "" { + return nil + } + + // User is authenticated but used --local; don't chain into API flow + if opts.localOnly { + return nil + } } - opts.triggerClient = triggerClient - // Initialize collaborators client - collaboratorsClient, err := collaborators.NewCollaboratorsRestClient(*config) - if err != nil { - return err + // If we reach here, the user is authenticated. Offer to create the project on CircleCI. + if !runLocal { + // Run local flow first, then offer remote setup + err := runOnboard(cmd.Context(), onboardOptions{ + dir: opts.dir, + skipDocker: opts.skipDocker, + skipConfig: opts.skipConfig, + verbose: opts.verbose, + }, cmd.OutOrStdout()) + if err != nil { + return err + } + + if !reader.AskConfirm("Would you like to also create the project on CircleCI?") { + return nil + } } - opts.collaboratorsClient = collaboratorsClient - // Initialize repository client - repositoryClient, err := repository.NewRepositoryRestClient(*config) - if err != nil { + // Initialize API clients for remote project creation + if err := initAPIClients(config, &opts); err != nil { return err } - opts.repositoryClient = repositoryClient - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { // Parse org slug argument if provided if len(args) > 0 { orgSlug := args[0] @@ -158,10 +179,17 @@ Examples: return err } - return initCmd(opts, reader, cmd) + return runInitRemote(opts, reader, cmd) }, } + // Local onboarding flags + initCmd.Flags().BoolVar(&opts.localOnly, "local", false, "Run only the local onboarding flow (detect, test, generate config)") + initCmd.Flags().StringVar(&opts.dir, "dir", ".", "Directory to scan for tech stack detection") + initCmd.Flags().BoolVar(&opts.skipDocker, "skip-docker", false, "Skip running tests in Docker") + initCmd.Flags().BoolVar(&opts.skipConfig, "skip-config", false, "Skip generating .circleci/config.yml") + initCmd.Flags().BoolVar(&opts.verbose, "verbose", false, "Show full Docker build and test output") + // Project creation flags initCmd.Flags().StringVar(&opts.vcsType, "vcs-type", "", "Version control system type (e.g., 'github', 'circleci')") initCmd.Flags().StringVar(&opts.orgName, "org-name", "", "Organization name or slug") @@ -182,6 +210,40 @@ Examples: return initCmd } +func initAPIClients(config *settings.Config, opts *initOptions) error { + projectClient, err := projectapi.NewProjectRestClient(*config) + if err != nil { + return err + } + opts.projectClient = projectClient + + pipelineClient, err := pipelineapi.NewPipelineRestClient(*config) + if err != nil { + return err + } + opts.pipelineClient = pipelineClient + + triggerClient, err := triggerapi.NewTriggerRestClient(*config) + if err != nil { + return err + } + opts.triggerClient = triggerClient + + collaboratorsClient, err := collaborators.NewCollaboratorsRestClient(*config) + if err != nil { + return err + } + opts.collaboratorsClient = collaboratorsClient + + repositoryClient, err := repository.NewRepositoryRestClient(*config) + if err != nil { + return err + } + opts.repositoryClient = repositoryClient + + return nil +} + func promptTillYOrN(reader UserInputReader) string { for { input := reader.ReadStringFromUser("Does your CircleCI config file exist in a different repository? (y/n)", "", nil) @@ -406,7 +468,7 @@ func validateProjectName(name string) error { return nil } -func initCmd(opts initOptions, reader UserInputReader, cmd *cobra.Command) error { +func runInitRemote(opts initOptions, reader UserInputReader, cmd *cobra.Command) error { fmt.Println("🚀 Initializing CircleCI project...") fmt.Println() diff --git a/cmd/onboard.go b/cmd/onboard.go new file mode 100644 index 000000000..a537902cd --- /dev/null +++ b/cmd/onboard.go @@ -0,0 +1,439 @@ +package cmd + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + "text/template" + "time" + + "github.com/CircleCI-Public/chunk-cli/envbuilder" + "github.com/briandowns/spinner" + "github.com/charmbracelet/lipgloss" +) + +type onboardOptions struct { + dir string + skipDocker bool + skipConfig bool + verbose bool +} + +const configTemplate = `version: 2.1 + +jobs: + build-and-test: + docker: + - image: {{.Image}}:{{.ImageVersion}} + steps: + - checkout + - run: + name: Install dependencies + command: {{.Install}} + - run: + name: Run tests + command: {{.Test}} + +workflows: + main: + jobs: + - build-and-test +` + +// Styles for the onboarding output. +var ( + stepStyle = lipgloss.NewStyle().Bold(true). + Foreground(lipgloss.AdaptiveColor{Light: "#003740", Dark: "#3B6385"}) + infoStyle = lipgloss.NewStyle(). + Foreground(lipgloss.AdaptiveColor{Light: "#161616", Dark: "#FFFFFF"}) + successStyle = lipgloss.NewStyle().Bold(true). + Foreground(lipgloss.AdaptiveColor{Light: "#0B6623", Dark: "#4CAF50"}) + failStyle = lipgloss.NewStyle().Bold(true). + Foreground(lipgloss.AdaptiveColor{Light: "#B00020", Dark: "#EF5350"}) + dimStyle = lipgloss.NewStyle(). + Foreground(lipgloss.AdaptiveColor{Light: "#666666", Dark: "#888888"}) +) + +func renderStep(s string) string { return stepStyle.Render(s) } +func renderInfo(s string) string { return infoStyle.Render(s) } +func renderSuccess(s string) string { return successStyle.Render(s) } +func renderFail(s string) string { return failStyle.Render(s) } +func renderDim(s string) string { return dimStyle.Render(s) } + + +func runOnboard(ctx context.Context, opts onboardOptions, w io.Writer) error { + totalStart := time.Now() + + dir := opts.dir + if dir == "" { + dir = "." + } + + absDir, err := filepath.Abs(dir) + if err != nil { + return fmt.Errorf("resolve directory: %w", err) + } + + fmt.Fprintln(w) + + // Step 1: Detect environment + s := newTimedSpinner(w, "Scanning repository...") + s.start() + + env, err := envbuilder.DetectEnvironment(ctx, absDir) + + elapsed := s.stop() + + if err != nil { + fmt.Fprintf(w, " %s %s %s\n", renderFail("x"), "Scanning repository", renderDim(elapsed)) + return fmt.Errorf("failed to detect environment: %w", err) + } + + if env.Stack == "unknown" { + fmt.Fprintf(w, " %s Scanning repository %s\n", renderStep("1."), renderDim(elapsed)) + fmt.Fprintf(w, " %s\n", renderDim("Could not detect tech stack. A minimal config will be generated.")) + fmt.Fprintln(w) + } else { + pkgHint := detectPackageManager(env) + fmt.Fprintf(w, " %s Scanning repository %s\n", renderStep("1."), renderDim(elapsed)) + fmt.Fprintf(w, " Detected: %s\n", renderInfo(fmt.Sprintf("%s (%s)", env.Stack, pkgHint))) + fmt.Fprintf(w, " Image: %s\n", renderInfo(fmt.Sprintf("%s:%s", env.Image, env.ImageVersion))) + fmt.Fprintln(w) + } + + // Steps 2-3: Build & run tests in Docker + testsPassed := false + if !opts.skipDocker && env.Stack != "unknown" { + passed, output, dockerErr := runDockerTests(ctx, absDir, env, w, opts.verbose) + if dockerErr != nil { + fmt.Fprintf(w, " %s\n", renderDim(fmt.Sprintf("Skipped: %v", dockerErr))) + fmt.Fprintln(w) + } else if passed { + testsPassed = true + fmt.Fprintln(w) + } else { + fmt.Fprintln(w) + if output != "" { + printTail(w, output, 20) + } + fmt.Fprintf(w, " Fix your tests and re-run %s.\n", renderInfo("circleci init")) + fmt.Fprintln(w) + return nil + } + } + + // Step 4: Generate .circleci/config.yml (only if tests passed or Docker was skipped) + if !opts.skipConfig && (testsPassed || opts.skipDocker || env.Stack == "unknown") { + if err := writeConfig(absDir, env, w); err != nil { + return fmt.Errorf("failed to generate config: %w", err) + } + } + + // Next steps & suggestions + printNextSteps(env, w) + + // Total time + fmt.Fprintf(w, " %s\n", renderDim(fmt.Sprintf("Total time: %s", formatDuration(time.Since(totalStart))))) + fmt.Fprintln(w) + + return nil +} + +func runDockerTests(ctx context.Context, dir string, env *envbuilder.Environment, w io.Writer, verbose bool) (passed bool, output string, err error) { + dockerPath, err := exec.LookPath("docker") + if err != nil { + return false, "", fmt.Errorf("docker not found on PATH — install Docker or use --skip-docker") + } + + // Write Dockerfile.test + dockerfilePath, err := envbuilder.WriteDockerfile(dir, env) + if err != nil { + return false, "", fmt.Errorf("write Dockerfile: %w", err) + } + defer os.Remove(dockerfilePath) + defer os.Remove(filepath.Join(dir, "Dockerfile.test.dockerignore")) + + imageName := "circleci-init-test" + + // Step 2: docker build + var buildOutput bytes.Buffer + buildCmd := exec.CommandContext(ctx, dockerPath, "build", "-f", "Dockerfile.test", "-t", imageName, ".") + buildCmd.Dir = dir + + if verbose { + fmt.Fprintf(w, " %s Building test environment...\n", renderStep("2.")) + buildCmd.Stdout = os.Stdout + buildCmd.Stderr = os.Stderr + } else { + buildCmd.Stdout = &buildOutput + buildCmd.Stderr = &buildOutput + } + + s := newTimedSpinner(w, "Building test environment...") + if !verbose { + s.start() + } + + buildStart := time.Now() + buildErr := buildCmd.Run() + buildElapsed := formatDuration(time.Since(buildStart)) + + if !verbose { + s.stop() + } + + if buildErr != nil { + fmt.Fprintf(w, " %s Building test environment... %s %s\n", renderStep("2."), renderFail("failed"), renderDim(buildElapsed)) + return false, buildOutput.String(), nil + } + + if !verbose { + fmt.Fprintf(w, " %s Building test environment... %s %s\n", renderStep("2."), renderSuccess("done"), renderDim(buildElapsed)) + } else { + fmt.Fprintf(w, " %s %s\n", renderSuccess("done"), renderDim(buildElapsed)) + } + + // Step 3: docker run + var testOutput bytes.Buffer + runCmd := exec.CommandContext(ctx, dockerPath, "run", "--rm", imageName) + + if verbose { + fmt.Fprintf(w, " %s Running tests...\n", renderStep("3.")) + runCmd.Stdout = os.Stdout + runCmd.Stderr = os.Stderr + } else { + runCmd.Stdout = &testOutput + runCmd.Stderr = &testOutput + } + + s2 := newTimedSpinner(w, "Running tests...") + if !verbose { + s2.start() + } + + testStart := time.Now() + runErr := runCmd.Run() + testElapsed := formatDuration(time.Since(testStart)) + + if !verbose { + s2.stop() + } + + if runErr != nil { + fmt.Fprintf(w, " %s Running tests... %s %s\n", renderStep("3."), renderFail("failed"), renderDim(testElapsed)) + if _, ok := runErr.(*exec.ExitError); ok { + return false, testOutput.String(), nil + } + return false, testOutput.String(), fmt.Errorf("docker run failed: %w", runErr) + } + + if !verbose { + fmt.Fprintf(w, " %s Running tests... %s %s\n", renderStep("3."), renderSuccess("passed"), renderDim(testElapsed)) + } else { + fmt.Fprintf(w, " %s %s\n", renderSuccess("passed"), renderDim(testElapsed)) + } + return true, "", nil +} + +func generateOnboardConfig(env *envbuilder.Environment) (string, error) { + data := struct { + Image string + ImageVersion string + Install string + Test string + }{ + Image: env.Image, + ImageVersion: env.ImageVersion, + Install: env.Install, + Test: env.Test, + } + + if env.Stack == "unknown" { + data.Image = "cimg/base" + data.ImageVersion = "current" + data.Install = "echo 'TODO: add install command'" + data.Test = "echo 'TODO: add test command'" + } + + tmpl, err := template.New("config").Parse(configTemplate) + if err != nil { + return "", fmt.Errorf("parse template: %w", err) + } + + var buf strings.Builder + if err := tmpl.Execute(&buf, data); err != nil { + return "", fmt.Errorf("execute template: %w", err) + } + + return buf.String(), nil +} + +func writeConfig(dir string, env *envbuilder.Environment, w io.Writer) error { + configDir := filepath.Join(dir, ".circleci") + configPath := filepath.Join(configDir, "config.yml") + + content, err := generateOnboardConfig(env) + if err != nil { + return err + } + + if err := os.MkdirAll(configDir, 0755); err != nil { + return fmt.Errorf("create .circleci directory: %w", err) + } + + if err := os.WriteFile(configPath, []byte(content), 0644); err != nil { + return fmt.Errorf("write config file: %w", err) + } + + stepNum := "4." + fmt.Fprintf(w, " %s Generated %s\n", renderStep(stepNum), renderInfo(".circleci/config.yml")) + fmt.Fprintln(w) + + return nil +} + +func printNextSteps(env *envbuilder.Environment, w io.Writer) { + fmt.Fprintf(w, " %s\n", renderStep("Next steps:")) + fmt.Fprintln(w, " - Run `circleci signup` to create your CircleCI account") + fmt.Fprintln(w, " - Run `circleci init` again to push your config and trigger your first build") + fmt.Fprintln(w) + + suggestions := []string{ + "Enable dependency caching to speed up builds", + } + + switch env.Stack { + case "go": + suggestions = append(suggestions, "Add golangci-lint for Go linting") + case "python": + suggestions = append(suggestions, "Add ruff or flake8 for Python linting") + case "javascript", "typescript": + suggestions = append(suggestions, "Add eslint for JavaScript/TypeScript linting") + case "java": + suggestions = append(suggestions, "Add checkstyle or spotbugs for code quality") + case "rust": + suggestions = append(suggestions, "Add clippy for Rust linting") + case "ruby": + suggestions = append(suggestions, "Add rubocop for Ruby linting") + case "php": + suggestions = append(suggestions, "Add phpstan for static analysis") + case "elixir": + suggestions = append(suggestions, "Add credo for Elixir linting") + } + + suggestions = append(suggestions, "Add parallelism to distribute tests across containers") + + fmt.Fprintf(w, " %s\n", renderDim("Suggestions:")) + for _, s := range suggestions { + fmt.Fprintf(w, " %s %s\n", renderDim("-"), renderDim(s)) + } + fmt.Fprintln(w) +} + +// detectPackageManager extracts a short package manager hint from the environment. +func detectPackageManager(env *envbuilder.Environment) string { + install := env.Install + switch { + case strings.HasPrefix(install, "pnpm"): + return "pnpm" + case strings.HasPrefix(install, "yarn"): + return "yarn" + case strings.HasPrefix(install, "npm"): + return "npm" + case strings.HasPrefix(install, "pip"): + return "pip" + case strings.HasPrefix(install, "uv"): + return "uv" + case strings.HasPrefix(install, "pipenv"): + return "pipenv" + case strings.HasPrefix(install, "go mod"): + return "go modules" + case strings.HasPrefix(install, "cargo"): + return "cargo" + case strings.HasPrefix(install, "bundle"): + return "bundler" + case strings.HasPrefix(install, "composer"): + return "composer" + case strings.HasPrefix(install, "mix"): + return "mix" + case strings.Contains(install, "gradlew"), strings.Contains(install, "gradle"): + return "gradle" + case strings.Contains(install, "mvn"): + return "maven" + default: + return env.Stack + } +} + +// timedSpinner wraps a spinner with a live elapsed-time counter. +type timedSpinner struct { + s *spinner.Spinner + w io.Writer + label string + start_ time.Time + done chan struct{} +} + +func newTimedSpinner(w io.Writer, label string) *timedSpinner { + return &timedSpinner{ + s: spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(w)), + w: w, + label: label, + done: make(chan struct{}), + } +} + +func (ts *timedSpinner) start() { + ts.start_ = time.Now() + ts.s.Suffix = fmt.Sprintf(" %s %s", ts.label, renderDim("0s")) + ts.s.Start() + + go func() { + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + for { + select { + case <-ts.done: + return + case <-ticker.C: + ts.s.Suffix = fmt.Sprintf(" %s %s", ts.label, renderDim(formatDuration(time.Since(ts.start_)))) + } + } + }() +} + +func (ts *timedSpinner) stop() string { + close(ts.done) + ts.s.Stop() + return formatDuration(time.Since(ts.start_)) +} + +// formatDuration formats a duration as a human-friendly string (e.g. "4s", "1m 15s", "2m 3s"). +func formatDuration(d time.Duration) string { + d = d.Round(time.Second) + m := int(d.Minutes()) + s := int(d.Seconds()) % 60 + if m > 0 { + return fmt.Sprintf("%dm %ds", m, s) + } + return fmt.Sprintf("%ds", s) +} + +// printTail prints the last n lines of output, indented. +func printTail(w io.Writer, output string, n int) { + lines := strings.Split(strings.TrimRight(output, "\n"), "\n") + start := 0 + if len(lines) > n { + start = len(lines) - n + fmt.Fprintf(w, " %s\n", renderDim(fmt.Sprintf("... (%d lines omitted)", start))) + } + for _, line := range lines[start:] { + fmt.Fprintf(w, " %s\n", renderDim(line)) + } + fmt.Fprintln(w) +} diff --git a/go.mod b/go.mod index 57a7ab5ee..7c4bec642 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/CircleCI-Public/circleci-cli -go 1.25.0 +go 1.26 require ( github.com/AlecAivazis/survey/v2 v2.3.7 @@ -32,6 +32,7 @@ require ( ) require ( + github.com/CircleCI-Public/chunk-cli v0.7.12 github.com/CircleCI-Public/circleci-config v0.0.0-20231003143420-842d4b0025ef github.com/a8m/envsubst v1.4.2 github.com/charmbracelet/lipgloss v1.1.0 @@ -95,11 +96,13 @@ require ( github.com/charithe/durationcheck v0.0.11 // indirect github.com/charmbracelet/bubbles v0.21.0 // indirect github.com/charmbracelet/bubbletea v1.3.10 // indirect - github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/x/ansi v0.10.1 // indirect - github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect - github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/charmbracelet/colorprofile v0.4.2 // indirect + github.com/charmbracelet/x/ansi v0.11.6 // indirect + github.com/charmbracelet/x/cellbuf v0.0.15 // indirect + github.com/charmbracelet/x/term v0.2.2 // indirect github.com/ckaznocha/intrange v0.3.1 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect @@ -156,7 +159,9 @@ require ( github.com/gostaticanalysis/comment v1.5.0 // indirect github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect github.com/gostaticanalysis/nilerr v0.1.2 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-version v1.8.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 // indirect @@ -182,7 +187,7 @@ require ( github.com/ldez/tagliatelle v0.7.2 // indirect github.com/ldez/usetesting v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/macabu/inamedparam v0.2.0 // indirect github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect github.com/manuelarte/funcorder v0.5.0 // indirect @@ -191,7 +196,7 @@ require ( github.com/matoous/godox v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.21 // indirect github.com/mgechev/revive v1.15.0 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -287,7 +292,7 @@ require ( golang.org/x/sys v0.42.0 // indirect golang.org/x/text v0.35.0 // indirect golang.org/x/tools v0.43.0 // indirect - google.golang.org/protobuf v1.36.8 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index dc4286861..4e6468673 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS8 github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/CircleCI-Public/chunk-cli v0.7.12 h1:Sj3ifaJAUQGJcx8hm1GDXFkgRMxBRmUx38yNw+lqvdQ= +github.com/CircleCI-Public/chunk-cli v0.7.12/go.mod h1:6wjEAmah7t7ZL1g5dYmxQSN5XabJTI+yaT8QSAgqfVo= github.com/CircleCI-Public/circle-policy-agent v0.0.779 h1:yjKIhwYyd6DQL4UH37Ln5Myx/zMX7H8ussp28unZjnE= github.com/CircleCI-Public/circle-policy-agent v0.0.779/go.mod h1:bYS06KcciyAEH13ggXZhOdl8hYS4d/iSCGgX5b03B1U= github.com/CircleCI-Public/circleci-config v0.0.0-20231003143420-842d4b0025ef h1:Bhr3xH8/XV0CF8wHFxWgBkmDRTQIW5O1MiuL3n1AEug= @@ -131,18 +133,22 @@ github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY= +github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= -github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= -github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= -github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= -github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= +github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= +github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= +github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= +github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= +github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs= github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -334,8 +340,14 @@ github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+ github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -404,8 +416,8 @@ github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= +github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE= github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww= @@ -431,8 +443,8 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w= +github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q= github.com/mgechev/revive v1.15.0/go.mod h1:LlAKO3QQe9OJ0pVZzI2GPa8CbXGZ/9lNpCGvK4T/a8A= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -849,8 +861,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=