Skip to content

Add pager to atmos describe affected command #1236

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 95 commits into
base: main
Choose a base branch
from

Conversation

samtholiya
Copy link
Collaborator

@samtholiya samtholiya commented May 4, 2025

what

  • Updated describe affected with pager

why

  • Makes easier for users to view the content

references

Summary by CodeRabbit

  • New Features
    • Introduced an interactive terminal pager with scrolling, clipboard support, and syntax highlighting for command outputs.
    • Added a --pager flag to CLI commands to enable or disable paged output.
    • Enhanced terminal paging configuration with environment variable support and customizable pager settings.
  • Bug Fixes
    • Improved output formatting and paging reliability for large or complex command outputs.
  • Documentation
    • Updated CLI documentation to include the --pager flag usage and examples.
  • Tests
    • Added extensive tests covering pager behavior, configuration parsing, output formatting, and CLI command execution.
  • Style/Refactor
    • Refactored command execution and output handling to modularize paging, formatting, and argument parsing for better maintainability.

samtholiya and others added 30 commits April 14, 2025 23:21
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@mergify mergify bot added the conflict This PR has conflicts label May 7, 2025
res any
}

func viewWithScroll(v viewWithScrollProps) error {

Check failure

Code scanning / golangci-lint

hugeParam: v is heavy (104 bytes); consider passing it by pointer Error

hugeParam: v is heavy (104 bytes); consider passing it by pointer
data any
}

func viewConfig(v viewConfigProps) error {

Check failure

Code scanning / golangci-lint

hugeParam: v is heavy (80 bytes); consider passing it by pointer Error

hugeParam: v is heavy (80 bytes); consider passing it by pointer
@Benbentwo Benbentwo marked this pull request as ready for review May 8, 2025 20:24
@Benbentwo Benbentwo requested a review from a team as a code owner May 8, 2025 20:24
Copy link
Contributor

coderabbitai bot commented May 8, 2025

📝 Walkthrough
## Walkthrough

This change introduces a terminal pager to several `describe` commands in the CLI, notably `describe affected`, `describe component`, and `describe config`. It adds a pager flag, updates configuration and environment variable support for paging, refactors command execution for modularity, and implements a Bubble Tea-based pager with syntax highlighting. Tests and documentation are updated accordingly.

## Changes

| File(s) / Path(s)                                                                                 | Change Summary                                                                                                                                                                                                                           |
|---------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `cmd/describe.go`, `cmd/describe_component.go`, `cmd/describe_config.go`                           | Added/updated `--pager` flag and refactored command execution to support paging and explicit flag parsing.                                                                                                                              |
| `cmd/describe_affected.go`                                                                        | Refactored CLI argument parsing, added pager flag, and improved validation.                                                                                                                       |
| `internal/exec/describe_affected.go`, `internal/exec/describe_component.go`, `internal/exec/describe_config.go` | Refactored execution logic into new structs with pager integration, modularized output handling, added syntax highlighting, and improved TTY detection.                                           |
| `internal/exec/file_utils.go`, `pkg/utils/highlight_utils.go`, `pkg/utils/json_utils.go`, `pkg/utils/yaml_utils.go` | Updated function signatures to accept config pointers and support syntax highlighting for paged output.                                                                                           |
| `pkg/pager/pager.go`, `pkg/pager/model.go`, `pkg/pager/mock_pager.go`, `pkg/pager/model_test.go`  | Introduced Bubble Tea-based pager implementation, interface, and tests.                                                                                                                           |
| `pkg/schema/schema.go`, `pkg/schema/schema_test.go`                                               | Changed pager config type to string, added `IsPagerEnabled()` method, and tested pager enablement logic.                                                                                         |
| `pkg/config/default.go`, `pkg/config/load.go`                                                     | Updated default pager config to string, added environment variable binding for pager.                                                                                                             |
| `website/docs/cli/commands/describe/describe-component.mdx`                                       | Documented new `--pager` flag and usage example.                                                                                                                                                  |
| `go.mod`                                                                                          | Updated dependencies to support new pager and highlighting features.                                                                                                                              |
| `tests/cli_describe_component_test.go`, `tests/describe_test.go`, `internal/exec/describe_component_test.go`, `internal/exec/describe_config_test.go` | Added/updated tests for pager functionality, flag support, and output validation.                                                                          |
| `tests/snapshots/…`, `tests/test-cases/demo-stacks.yaml`                                          | Updated test snapshots and cases to reflect pager config changes and output.                                                                                                                      |
| Other files (`internal/exec/atlantis_generate_repo_config.go`, `internal/exec/describe_dependents.go`, etc.) | Updated print/output functions to accept config pointers for highlighting and paging.                                                                                                             |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant CLI
    participant Config
    participant Pager
    participant Output

    User->>CLI: Run describe affected/component/config
    CLI->>Config: Load configuration
    CLI->>CLI: Parse flags (including --pager)
    CLI->>CLI: Execute describe logic (gather data)
    CLI->>Output: Format output (YAML/JSON)
    alt Pager enabled and TTY supports
        CLI->>Pager: Display output with syntax highlighting
        Pager->>User: Interactive paged view
    else
        CLI->>Output: Print or write output directly
        Output->>User: Output to stdout or file
    end

Assessment against linked issues

Objective Addressed Explanation
Add pager to atmos describe affected command (DEV-3182)
Pager loads only if content exceeds TTY max height (DEV-3182)
Pager supports forcing color via config or ENV (DEV-3182)
Pager implementation uses Bubble Tea (DEV-3182)

Suggested reviewers

  • aknysh
  • osterman

</details>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=cloudposse/atmos&utm_content=1236):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` or `@auto-summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` or `@auto-title` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🔭 Outside diff range comments (1)
cmd/describe_affected.go (1)

22-149: 💡 Verification agent

❓ Verification inconclusive

Add test coverage for new functions

The static analysis indicates that the new code lacks test coverage. Consider adding unit tests for:

  1. parseDescribeAffectedCliArgs
  2. setFlagValueInCliArgs
  3. checkFlagError

This will improve code reliability and help catch regressions.

Let's check if there are any existing tests for similar functions:


🏁 Script executed:

#!/bin/bash
# Look for test files that might contain related tests
find . -name "*_test.go" | xargs grep -l "parseDescribeAffectedCliArgs\|setFlagValueInCliArgs\|checkFlagError"

Length of output: 112


Add unit tests for CLI argument parsing helpers

The following functions in cmd/describe_affected.go currently have no test coverage. Please add unit tests that cover both success and error scenarios:

  • parseDescribeAffectedCliArgs
  • setFlagValueInCliArgs
  • checkFlagError

A new test file (e.g. cmd/describe_affected_test.go) in the same package should include cases for:

  • Valid flag combinations
  • Invalid --format values
  • Conflicting flags (e.g. --repo-path with --ref)
  • Error propagation from checkFlagError

This will help catch regressions in CLI flag handling.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 25-29: cmd/describe_affected.go#L25-L29
Added lines #L25 - L29 were not covered by tests


[warning] 62-66: cmd/describe_affected.go#L62-L66
Added lines #L62 - L66 were not covered by tests


[warning] 68-71: cmd/describe_affected.go#L68-L71
Added lines #L68 - L71 were not covered by tests


[warning] 73-76: cmd/describe_affected.go#L73-L76
Added lines #L73 - L76 were not covered by tests


[warning] 79-90: cmd/describe_affected.go#L79-L90
Added lines #L79 - L90 were not covered by tests


[warning] 92-100: cmd/describe_affected.go#L92-L100
Added lines #L92 - L100 were not covered by tests


[warning] 102-102: cmd/describe_affected.go#L102
Added line #L102 was not covered by tests


[warning] 105-131: cmd/describe_affected.go#L105-L131
Added lines #L105 - L131 were not covered by tests


[warning] 133-139: cmd/describe_affected.go#L133-L139
Added lines #L133 - L139 were not covered by tests


[warning] 141-141: cmd/describe_affected.go#L141
Added line #L141 was not covered by tests


[warning] 145-147: cmd/describe_affected.go#L145-L147
Added lines #L145 - L147 were not covered by tests

🪛 GitHub Check: golangci-lint

[failure] 62-62:
cyclomatic: function parseDescribeAffectedCliArgs has cyclomatic complexity 14 (> max enabled 10)


[failure] 93-93:
do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf("invalid '--format' flag '%s'. Valid values are 'json' (default) and 'yaml'", result.Format)"


[failure] 99-99:
do not define dynamic errors, use wrapped static errors instead: "errors.New("if the '--repo-path' flag is specified, the '--ref', '--sha', '--ssh-key' and '--ssh-key-password' flags can't be used")"

♻️ Duplicate comments (5)
internal/exec/describe_component_test.go (1)

13-13: Fix comment formatting: Add a period at the end.

The comment doesn't end with a period, which violates code style conventions.

-// --- Test ---
+// --- Test ---
🧰 Tools
🪛 GitHub Check: golangci-lint

[failure] 13-13:
Comment should end in a period

cmd/describe_component.go (1)

86-92: Sentence should start with a capital letter

The comment should start with a capital letter to follow code style conventions.

-// we prefer to panic because this is a developer error.
+// We prefer to panic because this is a developer error.
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 90-90: cmd/describe_component.go#L90
Added line #L90 was not covered by tests

🪛 GitHub Check: golangci-lint

[failure] 86-86:
Sentence should start with a capital letter

pkg/utils/json_utils.go (1)

24-40: ⚠️ Potential issue

Improved error handling needed in GetHighlightedJSON

The function ignores highlighting errors and returns success even when highlighting fails.

highlighted, err := HighlightCodeWithConfig(atmosConfig, prettyJSON.String())
if err != nil {
-	return prettyJSON.String(), nil
+	return prettyJSON.String(), err
}
🧰 Tools
🪛 GitHub Check: golangci-lint

[failure] 37-37:
error is not nil (line 35) but it returns nil

cmd/describe_affected.go (2)

98-100: 🛠️ Refactor suggestion

Use predefined errors instead of dynamic errors

Replace the dynamic error with a predefined error constant as flagged by static analysis.

+var ErrIncompatibleRepoPathFlags = errors.New("if the '--repo-path' flag is specified, the '--ref', '--sha', '--ssh-key' and '--ssh-key-password' flags can't be used")

+// In validateRepoPathFlags function
if result.RepoPath != "" && (result.Ref != "" || result.SHA != "" || result.SSHKeyPath != "" || result.SSHKeyPassword != "") {
-	return errors.New("if the '--repo-path' flag is specified, the '--ref', '--sha', '--ssh-key' and '--ssh-key-password' flags can't be used")
+	return ErrIncompatibleRepoPathFlags
}
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 92-100: cmd/describe_affected.go#L92-L100
Added lines #L92 - L100 were not covered by tests

🪛 GitHub Check: golangci-lint

[failure] 99-99:
do not define dynamic errors, use wrapped static errors instead: "errors.New("if the '--repo-path' flag is specified, the '--ref', '--sha', '--ssh-key' and '--ssh-key-password' flags can't be used")"


92-94: 🛠️ Refactor suggestion

Use predefined errors instead of dynamic errors

Replace the dynamic error with a predefined error constant to follow best practices as flagged by static analysis.

+var ErrInvalidFormat = func(format string) error {
+	return fmt.Errorf("invalid '--format' flag '%s'. Valid values are 'json' (default) and 'yaml'", format)
+}

+// In validateFormatFlag function
if result.Format != "" && result.Format != "yaml" && result.Format != "json" {
-	return fmt.Errorf("invalid '--format' flag '%s'. Valid values are 'json' (default) and 'yaml'", result.Format)
+	return ErrInvalidFormat(result.Format)
}
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 92-100: cmd/describe_affected.go#L92-L100
Added lines #L92 - L100 were not covered by tests

🪛 GitHub Check: golangci-lint

[failure] 93-93:
do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf("invalid '--format' flag '%s'. Valid values are 'json' (default) and 'yaml'", result.Format)"

🧹 Nitpick comments (13)
cmd/describe.go (1)

18-18: Pager flag has unclear string parameter usage.

The flag uses a string type with "true" as default, but the description only mentions enable/disable functionality. Consider clarifying that the parameter can accept program names like "less" or "more" in addition to boolean values.

-	describeCmd.PersistentFlags().String("pager", "true", "Disable / Enable the paging user experience")
+	describeCmd.PersistentFlags().String("pager", "true", "Control paging: 'true'/'false' to enable/disable, or specify pager name (e.g., 'less', 'more')")
internal/exec/atmos.go (1)

111-111: Consider adding tests for these modified lines

The static analysis flagged that these modified lines aren't covered by tests.

Consider adding test cases that verify the correct behavior when calling these functions with the atmosConfig parameter.

Also applies to: 123-123

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 111-111: internal/exec/atmos.go#L111
Added line #L111 was not covered by tests

tests/describe_test.go (2)

18-32: Good JSON format test with pager configuration

This test properly sets up the test environment, configures the pager via environment variable, and verifies that the command executes successfully with JSON output format.

Consider adding assertions to verify the actual output content matches expectations, not just that the command doesn't error.

 err := cmd.Execute()
 if err != nil {
     t.Fatalf("Failed to execute command: %v", err)
 }
+// Consider capturing output and verifying content
+// output := captureOutput(func() {
+//     cmd.Execute()
+// })
+// assert.Contains(t, output, "expected-json-content")

34-48: Good YAML format test with pager configuration

This test properly sets up the test environment, configures the pager via environment variable, and verifies that the command executes successfully with YAML output format.

As with the JSON test, consider adding assertions to verify the actual output content, not just that the command doesn't error.

 err := cmd.Execute()
 if err != nil {
     t.Fatalf("Failed to execute command: %v", err)
 }
+// Consider capturing output and verifying content
+// output := captureOutput(func() {
+//     cmd.Execute()
+// })
+// assert.Contains(t, output, "expected-yaml-content")
cmd/describe_config.go (1)

38-39: Remove TODO comment once PR #1174 is merged.

The TODO comment references another PR that might be merged by now. Check if this can be cleaned up.

-			// TODO: update this post pr:https://github.com/cloudposse/atmos/pull/1174 is merged
			atmosConfig.Settings.Terminal.Pager, err = cmd.Flags().GetString("pager")
cmd/describe_component.go (2)

25-27: Remove redundant argument validation

This manual argument check is redundant since you're already using cobra.ExactArgs(1) on line 20, which enforces the same constraint.

-if len(args) != 1 {
-	return errInvalidFlag
-}
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 26-27: cmd/describe_component.go#L26-L27
Added lines #L26 - L27 were not covered by tests


87-92: Consider a less drastic error handling approach

While panic makes sense for developer errors, consider returning errors instead of crashing the program, especially in a CLI tool. This would make the code more robust and user-friendly.

-// checkFlagNotPresentError checks if the error is nil.
-func checkFlagNotPresentError(err error) {
-	if err != nil {
-		panic(err)
-	}
+// checkFlagNotPresentError checks if the error is nil and returns it.
+func checkFlagNotPresentError(err error) error {
+	if err != nil {
+		return fmt.Errorf("flag error: %w", err)
+	}
+	return nil
}
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 90-90: cmd/describe_component.go#L90
Added line #L90 was not covered by tests

cmd/describe_affected.go (1)

145-149: Consider less abrupt error handling

Using panic for flag errors could be harsh in a CLI context. Consider returning errors instead for more graceful failure handling.

-func checkFlagError(err error) {
-	if err != nil {
-		panic(err)
-	}
-}

+func checkFlagError(err error) error {
+	return err
+}

+// Then in setFlagValueInCliArgs:
+var err error
+for k := range flagsKeyValue {
+	if !flags.Changed(k) {
+		continue
+	}
+	switch v := flagsKeyValue[k].(type) {
+	case *string:
+		*v, err = flags.GetString(k)
+	case *bool:
+		*v, err = flags.GetBool(k)
+	default:
+		return fmt.Errorf("unsupported type %T for flag %s", v, k)
+	}
+	if err != nil {
+		return fmt.Errorf("error getting flag %s: %w", k, err)
+	}
+}
+return nil
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 145-147: cmd/describe_affected.go#L145-L147
Added lines #L145 - L147 were not covered by tests

internal/exec/describe_component.go (1)

46-111: Reduce function length

The function exceeds the 60-line limit (it's 64 lines). Consider extracting some logic into helper methods for better readability and to satisfy the linter.

You could extract the parameter initialization or the query evaluation logic into separate methods:

func (d *DescribeComponentExec) ExecuteDescribeComponentCmd(describeComponentParams DescribeComponentParams) error {
-	component := describeComponentParams.Component
-	stack := describeComponentParams.Stack
-	processTemplates := describeComponentParams.ProcessTemplates
-	processYamlFunctions := describeComponentParams.ProcessYamlFunctions
-	skip := describeComponentParams.Skip
-	query := describeComponentParams.Query
-	pager := describeComponentParams.Pager
-	format := describeComponentParams.Format
-	file := describeComponentParams.File
+	params := d.extractParams(describeComponentParams)

	var err error
	var atmosConfig schema.AtmosConfiguration

	atmosConfig, err = d.initCliConfig(schema.ConfigAndStacksInfo{
-		ComponentFromArg: component,
-		Stack:            stack,
+		ComponentFromArg: params.component,
+		Stack:            params.stack,
	}, true)
	if err != nil {
		return err
	}

	componentSection, err := d.executeDescribeComponent(
-		component,
-		stack,
-		processTemplates,
-		processYamlFunctions,
-		skip,
+		params.component,
+		params.stack,
+		params.processTemplates,
+		params.processYamlFunctions,
+		params.skip,
	)
	if err != nil {
		return err
	}

	var res any
-	if pager != "" {
-		atmosConfig.Settings.Terminal.Pager = pager
+	if params.pager != "" {
+		atmosConfig.Settings.Terminal.Pager = params.pager
	}

-	if query != "" {
-		res, err = d.evaluateYqExpression(&atmosConfig, componentSection, query)
-		if err != nil {
-			return err
-		}
-	} else {
-		res = componentSection
-	}
+	res, err = d.evaluateQueryIfPresent(&atmosConfig, componentSection, params.query)
+	if err != nil {
+		return err
+	}

	if atmosConfig.Settings.Terminal.IsPagerEnabled() {
-		err = d.viewConfig(&atmosConfig, component, format, res)
+		err = d.viewConfig(&atmosConfig, params.component, params.format, res)
		switch err.(type) {
		case DescribeConfigFormatError:
			return err
		case nil:
			return nil
		default:
			log.Debug("Failed to use pager")
		}
	}

-	err = d.printOrWriteToFile(&atmosConfig, format, file, res)
+	err = d.printOrWriteToFile(&atmosConfig, params.format, params.file, res)
	if err != nil {
		return err
	}

	return nil
}

+type extractedParams struct {
+	component            string
+	stack                string
+	processTemplates     bool
+	processYamlFunctions bool
+	skip                 []string
+	query                string
+	pager                string
+	format               string
+	file                 string
+}
+
+func (d *DescribeComponentExec) extractParams(params DescribeComponentParams) extractedParams {
+	return extractedParams{
+		component:            params.Component,
+		stack:                params.Stack,
+		processTemplates:     params.ProcessTemplates,
+		processYamlFunctions: params.ProcessYamlFunctions,
+		skip:                 params.Skip,
+		query:                params.Query,
+		pager:                params.Pager,
+		format:               params.Format,
+		file:                 params.File,
+	}
+}
+
+func (d *DescribeComponentExec) evaluateQueryIfPresent(atmosConfig *schema.AtmosConfiguration, data any, query string) (any, error) {
+	if query == "" {
+		return data, nil
+	}
+	return d.evaluateYqExpression(atmosConfig, data, query)
+}
🧰 Tools
🪛 GitHub Check: golangci-lint

[failure] 46-46:
function-length: maximum number of lines per function exceeded; max 60 but got 64

pkg/utils/highlight_utils.go (1)

75-147: Consider breaking down the HighlightCodeWithConfig function

This function exceeds the 60-line limit (as flagged by static analysis). Consider extracting some of the lexer determination logic into a separate function.

func HighlightCodeWithConfig(config *schema.AtmosConfiguration, code string, format ...string) (string, error) {
	if !term.IsTerminal(int(os.Stdout.Fd())) {
		return code, nil
	}
	settings := GetHighlightSettings(config)
	if !settings.Enabled {
		return code, nil
	}

	// Get terminal width
	config.Settings.Terminal.MaxWidth = templates.GetTerminalWidth()

-	// Determine lexer based on format flag or content format
-	var lexerName string
-	if len(format) > 0 && format[0] != "" {
-		// Use format flag if provided
-		lexerName = strings.ToLower(format[0])
-	} else {
-		// This is just a fallback
-		trimmed := strings.TrimSpace(code)
-
-		// Try to parse as JSON first
-		if json.Valid([]byte(trimmed)) {
-			lexerName = "json"
-		} else {
-			// Check for common YAML indicators
-			// 1. Contains key-value pairs with colons
-			// 2. Does not start with a curly brace (which could indicate malformed JSON)
-			// 3. Contains indentation or list markers
-			if (strings.Contains(trimmed, ":") && !strings.HasPrefix(trimmed, "{")) ||
-				strings.Contains(trimmed, "\n  ") ||
-				strings.Contains(trimmed, "\n- ") {
-				lexerName = "yaml"
-			} else {
-				// Fallback to plaintext if format is unclear
-				lexerName = "plaintext"
-			}
-		}
-	}
+	lexerName := determineLexer(code, format...)

	// Get lexer
	lexer := lexers.Get(lexerName)
	if lexer == nil {
		lexer = lexers.Fallback
	}
	// Get style
	s := styles.Get(settings.Theme)
	if s == nil {
		s = styles.Fallback
	}
	// Get formatter
	var formatter chroma.Formatter
	if settings.LineNumbers {
		formatter = formatters.TTY256
	} else {
		formatter = formatters.Get(settings.Formatter)
		if formatter == nil {
			formatter = formatters.Fallback
		}
	}
	// Create buffer for output
	var buf bytes.Buffer
	// Format the code
	iterator, err := lexer.Tokenise(nil, code)
	if err != nil {
		return code, err
	}
	err = formatter.Format(&buf, s, iterator)
	if err != nil {
		return code, err
	}
	return buf.String(), nil
}

+// determineLexer determines the appropriate lexer name based on format or content
+func determineLexer(code string, format ...string) string {
+	// Use format flag if provided
+	if len(format) > 0 && format[0] != "" {
+		return strings.ToLower(format[0])
+	}
+
+	// Otherwise, try to determine from content
+	trimmed := strings.TrimSpace(code)
+
+	// Try to parse as JSON first
+	if json.Valid([]byte(trimmed)) {
+		return "json"
+	}
+
+	// Check for common YAML indicators
+	// 1. Contains key-value pairs with colons
+	// 2. Does not start with a curly brace (which could indicate malformed JSON)
+	// 3. Contains indentation or list markers
+	if (strings.Contains(trimmed, ":") && !strings.HasPrefix(trimmed, "{")) ||
+		strings.Contains(trimmed, "\n  ") ||
+		strings.Contains(trimmed, "\n- ") {
+		return "yaml"
+	}
+
+	// Fallback to plaintext if format is unclear
+	return "plaintext"
+}
🧰 Tools
🪛 GitHub Check: golangci-lint

[failure] 75-75:
function-length: maximum number of lines per function exceeded; max 60 but got 71

internal/exec/describe_config.go (1)

55-66: Pager skips --output writing

When the pager is enabled and there’s no error, the function returns early (line 61).
This means output is silently ignored when the user asks for both paged viewing and file export.

A small tweak lets us keep today’s UX while still honoring --output:

-        case nil:
-            return nil
+        case nil:
+            if output == "" {
+                return nil          // pure pager workflow
+            }
+            // fall-through to file writer

This keeps behaviour unchanged for the majority case yet still writes the file when requested.

pkg/pager/model.go (1)

80-82: Forward reference is safe but can be sharper

statusBarMessageHelpStyle relies on the green constant declared later.
Go handles this at compile-time, yet grouping the colour declarations first would improve readability and avoid head-scratchers for new contributors.

internal/exec/describe_affected.go (1)

219-328: Duplicate legacy function

ExecuteDescribeAffectedCmd duplicates the new struct’s logic almost verbatim.
Unless kept for backwards compatibility, this increases maintenance cost and test surface.
Evaluate deprecating it or delegating to DescribeAffectedExec.Execute.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f26dfe1 and 94cc2ce.

📒 Files selected for processing (43)
  • Makefile (1 hunks)
  • cmd/describe.go (1 hunks)
  • cmd/describe_affected.go (3 hunks)
  • cmd/describe_component.go (2 hunks)
  • cmd/describe_config.go (2 hunks)
  • cmd/docs.go (1 hunks)
  • go.mod (2 hunks)
  • internal/exec/atlantis_generate_repo_config.go (1 hunks)
  • internal/exec/atmos.go (2 hunks)
  • internal/exec/describe_affected.go (5 hunks)
  • internal/exec/describe_component.go (2 hunks)
  • internal/exec/describe_component_test.go (1 hunks)
  • internal/exec/describe_config.go (1 hunks)
  • internal/exec/describe_config_test.go (1 hunks)
  • internal/exec/describe_dependents.go (1 hunks)
  • internal/exec/describe_stacks.go (1 hunks)
  • internal/exec/describe_workflows.go (1 hunks)
  • internal/exec/file_utils.go (2 hunks)
  • pkg/atlantis/atlantis_generate_repo_config_test.go (2 hunks)
  • pkg/config/default.go (1 hunks)
  • pkg/config/load.go (2 hunks)
  • pkg/pager/mock_pager.go (1 hunks)
  • pkg/pager/model.go (1 hunks)
  • pkg/pager/model_test.go (1 hunks)
  • pkg/pager/pager.go (1 hunks)
  • pkg/schema/schema.go (1 hunks)
  • pkg/schema/schema_test.go (1 hunks)
  • pkg/utils/highlight_utils.go (3 hunks)
  • pkg/utils/json_utils.go (1 hunks)
  • pkg/utils/json_utils_test.go (1 hunks)
  • pkg/utils/yaml_utils.go (1 hunks)
  • pkg/utils/yq_test.go (5 hunks)
  • pkg/utils/yq_utils.go (1 hunks)
  • tests/cli_describe_component_test.go (1 hunks)
  • tests/describe_test.go (1 hunks)
  • tests/snapshots/TestCLICommands_atmos_describe_config.stdout.golden (1 hunks)
  • tests/snapshots/TestCLICommands_atmos_describe_config_-f_yaml.stdout.golden (1 hunks)
  • tests/snapshots/TestCLICommands_atmos_describe_config_imports.stderr.golden (1 hunks)
  • tests/snapshots/TestCLICommands_atmos_describe_config_imports.stdout.golden (1 hunks)
  • tests/snapshots/TestCLICommands_atmos_describe_configuration.stderr.golden (1 hunks)
  • tests/snapshots/TestCLICommands_atmos_describe_configuration.stdout.golden (1 hunks)
  • tests/test-cases/demo-stacks.yaml (3 hunks)
  • website/docs/cli/commands/describe/describe-component.mdx (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
internal/exec/describe_stacks.go (1)
Learnt from: Cerebrovinny
PR: cloudposse/atmos#764
File: internal/exec/describe_stacks.go:289-295
Timestamp: 2024-11-13T21:37:07.852Z
Learning: In the `internal/exec/describe_stacks.go` file of the `atmos` project written in Go, avoid extracting the stack name handling logic into a helper function within the `ExecuteDescribeStacks` method, even if the logic appears duplicated.
internal/exec/describe_affected.go (1)
Learnt from: samtholiya
PR: cloudposse/atmos#1147
File: internal/exec/validate_schema.go:42-57
Timestamp: 2025-04-11T22:06:46.999Z
Learning: The "ExecuteAtmosValidateSchemaCmd" function in internal/exec/validate_schema.go has been reviewed and confirmed to have acceptable cognitive complexity despite static analysis warnings. The function uses a clean structure with only three if statements for error handling and delegates complex operations to helper methods.
🧬 Code Graph Analysis (10)
pkg/atlantis/atlantis_generate_repo_config_test.go (1)
pkg/utils/yaml_utils.go (1)
  • PrintAsYAML (44-51)
internal/exec/atmos.go (1)
pkg/utils/yaml_utils.go (1)
  • PrintAsYAML (44-51)
pkg/utils/yq_test.go (2)
pkg/utils/yaml_utils.go (1)
  • PrintAsYAML (44-51)
pkg/utils/yq_utils.go (1)
  • EvaluateYqExpression (46-80)
pkg/utils/yq_utils.go (2)
pkg/schema/schema.go (1)
  • AtmosConfiguration (13-46)
pkg/utils/yaml_utils.go (2)
  • ConvertToYAML (89-95)
  • UnmarshalYAML (222-224)
internal/exec/file_utils.go (3)
pkg/schema/schema.go (1)
  • AtmosConfiguration (13-46)
pkg/utils/yaml_utils.go (1)
  • PrintAsYAML (44-51)
pkg/utils/json_utils.go (1)
  • PrintAsJSON (15-22)
internal/exec/atlantis_generate_repo_config.go (1)
pkg/utils/yaml_utils.go (1)
  • PrintAsYAML (44-51)
pkg/schema/schema_test.go (1)
pkg/schema/schema.go (1)
  • Terminal (190-196)
internal/exec/describe_config_test.go (4)
internal/exec/describe_config.go (2)
  • DescribeConfigFormatError (16-18)
  • NewDescribeConfig (33-40)
pkg/schema/schema.go (6)
  • AtmosConfiguration (13-46)
  • Components (296-299)
  • Terraform (266-277)
  • Settings (746-750)
  • AtmosSettings (212-228)
  • Terminal (190-196)
pkg/pager/mock_pager.go (1)
  • NewMockPageCreator (25-29)
internal/tui/templates/term/term_writer.go (1)
  • IsTTYSupportForStdout (83-87)
cmd/describe_component.go (3)
internal/exec/describe_component.go (2)
  • NewDescribeComponentExec (35-44)
  • DescribeComponentParams (14-24)
pkg/utils/markdown_utils.go (1)
  • PrintErrorMarkdownAndExit (87-89)
cmd/cmd_utils.go (2)
  • ComponentsArgCompletion (755-777)
  • AddStackCompletion (748-753)
pkg/utils/highlight_utils.go (1)
pkg/schema/schema.go (2)
  • AtmosConfiguration (13-46)
  • SyntaxHighlighting (202-210)
🪛 GitHub Check: codecov/patch
cmd/docs.go

[warning] 105-105: cmd/docs.go#L105
Added line #L105 was not covered by tests

internal/exec/atmos.go

[warning] 111-111: internal/exec/atmos.go#L111
Added line #L111 was not covered by tests


[warning] 123-123: internal/exec/atmos.go#L123
Added line #L123 was not covered by tests

internal/exec/atlantis_generate_repo_config.go

[warning] 528-528: internal/exec/atlantis_generate_repo_config.go#L528
Added line #L528 was not covered by tests

cmd/describe_component.go

[warning] 26-27: cmd/describe_component.go#L26-L27
Added lines #L26 - L27 were not covered by tests


[warning] 90-90: cmd/describe_component.go#L90
Added line #L90 was not covered by tests

cmd/describe_config.go

[warning] 19-25: cmd/describe_config.go#L19-L25
Added lines #L19 - L25 were not covered by tests


[warning] 27-30: cmd/describe_config.go#L27-L30
Added lines #L27 - L30 were not covered by tests


[warning] 32-35: cmd/describe_config.go#L32-L35
Added lines #L32 - L35 were not covered by tests


[warning] 37-42: cmd/describe_config.go#L37-L42
Added lines #L37 - L42 were not covered by tests


[warning] 45-45: cmd/describe_config.go#L45
Added line #L45 was not covered by tests


[warning] 49-49: cmd/describe_config.go#L49
Added line #L49 was not covered by tests

cmd/describe_affected.go

[warning] 25-29: cmd/describe_affected.go#L25-L29
Added lines #L25 - L29 were not covered by tests


[warning] 62-66: cmd/describe_affected.go#L62-L66
Added lines #L62 - L66 were not covered by tests


[warning] 68-71: cmd/describe_affected.go#L68-L71
Added lines #L68 - L71 were not covered by tests


[warning] 73-76: cmd/describe_affected.go#L73-L76
Added lines #L73 - L76 were not covered by tests


[warning] 79-90: cmd/describe_affected.go#L79-L90
Added lines #L79 - L90 were not covered by tests


[warning] 92-100: cmd/describe_affected.go#L92-L100
Added lines #L92 - L100 were not covered by tests


[warning] 102-102: cmd/describe_affected.go#L102
Added line #L102 was not covered by tests


[warning] 105-131: cmd/describe_affected.go#L105-L131
Added lines #L105 - L131 were not covered by tests


[warning] 133-139: cmd/describe_affected.go#L133-L139
Added lines #L133 - L139 were not covered by tests


[warning] 141-141: cmd/describe_affected.go#L141
Added line #L141 was not covered by tests


[warning] 145-147: cmd/describe_affected.go#L145-L147
Added lines #L145 - L147 were not covered by tests

internal/exec/describe_affected.go

[warning] 94-103: internal/exec/describe_affected.go#L94-L103
Added lines #L94 - L103 were not covered by tests


[warning] 107-151: internal/exec/describe_affected.go#L107-L151
Added lines #L107 - L151 were not covered by tests


[warning] 155-155: internal/exec/describe_affected.go#L155
Added line #L155 was not covered by tests


[warning] 159-160: internal/exec/describe_affected.go#L159-L160
Added lines #L159 - L160 were not covered by tests


[warning] 162-162: internal/exec/describe_affected.go#L162
Added line #L162 was not covered by tests


[warning] 166-171: internal/exec/describe_affected.go#L166-L171
Added lines #L166 - L171 were not covered by tests


[warning] 173-186: internal/exec/describe_affected.go#L173-L186
Added lines #L173 - L186 were not covered by tests

🪛 GitHub Check: golangci-lint
internal/exec/describe_component_test.go

[failure] 13-13:
Comment should end in a period

cmd/describe_component.go

[failure] 86-86:
Sentence should start with a capital letter

cmd/describe_affected.go

[failure] 62-62:
cyclomatic: function parseDescribeAffectedCliArgs has cyclomatic complexity 14 (> max enabled 10)


[failure] 93-93:
do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf("invalid '--format' flag '%s'. Valid values are 'json' (default) and 'yaml'", result.Format)"


[failure] 99-99:
do not define dynamic errors, use wrapped static errors instead: "errors.New("if the '--repo-path' flag is specified, the '--ref', '--sha', '--ssh-key' and '--ssh-key-password' flags can't be used")"

internal/exec/describe_component.go

[failure] 46-46:
function-length: maximum number of lines per function exceeded; max 60 but got 64

pkg/utils/json_utils.go

[failure] 37-37:
error is not nil (line 35) but it returns nil

pkg/utils/highlight_utils.go

[failure] 75-75:
function-length: maximum number of lines per function exceeded; max 60 but got 71

internal/exec/describe_affected.go

[failure] 107-107:
cognitive complexity 28 of func (*DescribeAffectedExec).Execute is high (> 20)


[failure] 112-112:
ifElseChain: rewrite if-else to switch statement


[warning] 166-166:
if a.Query == "" has complex nested blocks (complexity: 13)


[failure] 218-218:
cognitive complexity 28 of func ExecuteDescribeAffectedCmd is high (> 20)


[failure] 341-341:
hugeParam: v is heavy (104 bytes); consider passing it by pointer


[failure] 370-370:
hugeParam: v is heavy (80 bytes); consider passing it by pointer

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Acceptance Tests (windows-latest, windows)
  • GitHub Check: Summary
🔇 Additional comments (73)
Makefile (1)

64-64: Good improvement to test coverage.

Adding the -coverpkg=./... flag ensures coverage measurement across all packages in the module, not just those being directly tested. This will give you better visibility into how the new pager functionality is being exercised throughout the codebase.

tests/snapshots/TestCLICommands_atmos_describe_config_imports.stdout.golden (1)

113-113: Properly updated pager configuration.

The change from boolean true to string "1" for the pager setting aligns with the broader refactoring to support multiple pager options. This is the right update to reflect the new string-based configuration approach.

tests/snapshots/TestCLICommands_atmos_describe_config.stdout.golden (1)

70-70: Correctly updated pager configuration format.

The change from boolean false to string "0" properly reflects the new string-based approach for pager configuration. This is consistent with the implementation changes in the codebase.

tests/snapshots/TestCLICommands_atmos_describe_configuration.stderr.golden (1)

16-16: Added appropriate debug message for pager failures.

This new debug log line provides visibility into pager usage failures, which is important for troubleshooting. This aligns well with the implementation of the new pager functionality.

pkg/config/default.go (1)

58-58: Good update from boolean to string for pager configuration.

Changing Pager from a boolean to string value "less" makes the configuration more flexible, allowing users to specify their preferred pager. This is a common pattern in CLI tools that aligns with standard Unix practices.

tests/snapshots/TestCLICommands_atmos_describe_config_imports.stderr.golden (1)

20-20: Appropriate debug message for pager failure.

The added debug message provides helpful diagnostic information when the pager fails to initialize or run, which improves troubleshooting.

tests/snapshots/TestCLICommands_atmos_describe_configuration.stdout.golden (2)

48-60: Well-structured terminal configuration section.

The terminal configuration section is comprehensive and includes all necessary options for controlling the terminal display, paging, and syntax highlighting capabilities.


50-50:

✅ Verification successful

Verify pager value in test snapshot.

The pager is set to "1" in this test snapshot. Ensure this value is intentional and documented, as it differs from both the default "less" in the code and typical boolean strings like "true"/"false".


🏁 Script executed:

#!/bin/bash
# Check if "1" is a valid pager setting according to the codebase
# Look for code that handles pager values
rg -A 5 -B 5 "IsPagerEnabled|pager.*value"

Length of output: 5222


Pager value validated
The snapshot’s pager: "1" maps to an enabled pager—IsPagerEnabled() explicitly treats "1" as valid. No changes required.

go.mod (1)

13-13: Direct dependency declaration for terminal pager components.

The change correctly reclassifies these dependencies from indirect to direct. This aligns with the new pager functionality implementation that directly uses these libraries for terminal UI rendering, ANSI styling, and clipboard support.

Also applies to: 51-51, 55-56

tests/snapshots/TestCLICommands_atmos_describe_config_-f_yaml.stdout.golden (1)

35-47: Added terminal configuration section with pager settings.

The new terminal configuration section properly defines default values for the pager and related settings. This snapshot update reflects the newly added terminal pager functionality.

tests/test-cases/demo-stacks.yaml (3)

77-78: Disabled pager in docs command test.

Setting ATMOS_PAGER: false ensures consistent test output by preventing the pager from activating during test execution.


96-97: Disabled pager in describe config YAML test.

Setting ATMOS_PAGER: false ensures consistent test output by preventing the pager from activating during test execution.


118-119: Disabled pager in describe config JSON test.

Setting ATMOS_PAGER: false ensures consistent test output by preventing the pager from activating during test execution.

pkg/atlantis/atlantis_generate_repo_config_test.go (1)

18-18: Updated PrintAsYAML calls with configuration parameter.

The function calls have been properly updated to include the atmosConfig parameter, matching the refactored PrintAsYAML function signature. This change is necessary to support the new terminal pager functionality.

Also applies to: 40-40

website/docs/cli/commands/describe/describe-component.mdx (2)

58-59: Good addition of pager example.

This example clearly shows users how to invoke the component description with pager functionality using the more pager tool.


79-79: Documentation for pager flag is complete.

The added --pager flag documentation clearly explains its purpose to enable/disable paging.

internal/exec/describe_stacks.go (1)

139-139: Updated function call to support configuration-aware output.

The change properly integrates the describe stacks command with the new pager functionality by passing the configuration pointer to the printOrWriteToFile function.

cmd/docs.go (1)

105-109: Good usage of new pager configuration logic.

The code now uses the new IsPagerEnabled() method while maintaining backward compatibility with the deprecated settings.docs.pagination setting.

Since this line was flagged as not covered by tests, consider adding a test case that verifies the pager enablement logic for the docs command:

#!/bin/bash
# Verify test coverage for the IsPagerEnabled method in docs.go

# Look for existing tests that might be testing related functionality
rg -t go "Test.*Docs.*Pager" --glob "**/*_test.go"

# Check if the IsPagerEnabled method has test coverage elsewhere
rg -t go "Test.*IsPagerEnabled" --glob "**/*_test.go"
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 105-105: cmd/docs.go#L105
Added line #L105 was not covered by tests

pkg/schema/schema.go (2)

192-192: Good improvement from bool to string type for pager setting.

Changing the Pager field type from boolean to string adds flexibility, allowing users to specify which pager program to use instead of just turning it on or off.


198-200: Well-implemented pager enablement logic.

The IsPagerEnabled() method provides a clean interface to determine if paging should be enabled based on string values, supporting multiple affirmative options ("on", "less", "true", etc.). This maintains backward compatibility while adding new capabilities.

internal/exec/atmos.go (2)

123-123: Added atmosConfig parameter correctly to PrintAsYAML

The change passes the configuration to the YAML printer, enabling syntax highlighting and paging functionality for the describe dependents command output.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 123-123: internal/exec/atmos.go#L123
Added line #L123 was not covered by tests


111-111:

✅ Verification successful

Added atmosConfig parameter correctly to PrintAsYAML

The change passes the configuration to the YAML printer, aligning with the new pager feature. This enables syntax highlighting and paging functionality for the describe component command output.


🏁 Script executed:

#!/bin/bash
# Verify if all PrintAsYAML calls have been updated with the new parameter
rg "PrintAsYAML\([^&]" --type=go

Length of output: 962


All PrintAsYAML calls include the new atmosConfig parameter
Verified that every invocation of PrintAsYAML now accepts the atmosConfig argument:

  • internal/exec/atmos.go (uses u.PrintAsYAML(&atmosConfig, data))
  • internal/exec/file_utils.go (passes the pointer variable)
  • All tests in pkg/utils/yq_test.go
    No further updates required.
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 111-111: internal/exec/atmos.go#L111
Added line #L111 was not covered by tests

internal/exec/describe_workflows.go (1)

79-79: Added atmosConfig parameter correctly to printOrWriteToFile

The change passes the configuration to the file utility function, allowing the describe workflows command to use the new pager functionality.

internal/exec/file_utils.go (3)

35-35: Added atmosConfig parameter correctly to printOrWriteToFile function signature

The function signature has been updated to accept the atmosConfig parameter, which is necessary for the pager functionality.


43-43: Properly passes atmosConfig to PrintAsYAML

The change correctly forwards the atmosConfig to the YAML printer when outputting to console.


56-56: Properly passes atmosConfig to PrintAsJSON

The change correctly forwards the atmosConfig to the JSON printer when outputting to console.

pkg/utils/json_utils_test.go (1)

9-33: Good test coverage for PrintAsJSON with the new parameter

The test verifies that PrintAsJSON works correctly with the new atmosConfig parameter, including terminal syntax highlighting settings.

pkg/config/load.go (2)

103-103: Configuration environment variable binding for pager looks good

Binding the terminal pager setting to both ATMOS_PAGER and system PAGER environment variables provides flexible configuration options for users.


121-121: Default configuration for pager is appropriate

Setting the default pager value to true ensures users get a good out-of-box experience with the new pager feature without requiring explicit configuration.

pkg/schema/schema_test.go (1)

40-71: Comprehensive test coverage for IsPagerEnabled method

The test covers all relevant string values that users might provide for the pager setting. It properly tests both enabling and disabling scenarios, including case sensitivity and empty string handling. This provides good confidence in the string interpretation logic.

pkg/utils/yq_test.go (1)

68-68: Function signature update consistently applied

All calls to PrintAsYAML have been correctly updated to include the atmosConfig parameter, aligning with the function signature change in pkg/utils/yaml_utils.go.

Also applies to: 76-76, 84-84, 92-92, 100-100, 108-108, 120-120, 132-132, 144-144, 155-155, 161-161, 167-167, 173-173

tests/cli_describe_component_test.go (1)

1-19: Good test case for the new pager feature

This test verifies that the describe component command works correctly with the new --pager flag set to "more" and YAML output format. The test properly sets up the CLI arguments and verifies successful execution.

internal/exec/atlantis_generate_repo_config.go (1)

528-528:

✅ Verification successful

Function signature change to PrintAsYAML now requires atmosConfig parameter

The signature change to u.PrintAsYAML adds the ability to use CLI configuration for output formatting and paging support. This is consistent with broader changes throughout the codebase.


🏁 Script executed:

#!/bin/bash
# Verify test coverage for this line
grep -n "PrintAsYAML.*&atmosConfig" internal/exec/atlantis_generate_repo_config_test.go || echo "Test coverage needed for PrintAsYAML with atmosConfig parameter at line 528"

Length of output: 335


🏁 Script executed:

#!/bin/bash
# Search for PrintAsYAML usage in any test files across the repo
rg -n 'PrintAsYAML' -g '*_test.go' || echo "No PrintAsYAML coverage found in any test"

Length of output: 1045


Verified test coverage for PrintAsYAML signature change

Confirmed that the updated call is covered in the existing tests:

  • pkg/atlantis/atlantis_generate_repo_config_test.go:40
    err = u.PrintAsYAML(&atmosConfig, atlantisYaml)

All set—no additional test needed.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 528-528: internal/exec/atlantis_generate_repo_config.go#L528
Added line #L528 was not covered by tests

internal/exec/describe_dependents.go (1)

79-79: Update to printOrWriteToFile now requires configuration parameter

The change adds a pointer to atmosConfig as the first parameter to printOrWriteToFile. This aligns with the refactoring work to provide CLI configuration context for output formatting and paging support.

pkg/utils/yq_utils.go (1)

82-116: Good addition of type-safe YQ expression evaluator

The new EvaluateYqExpressionWithType function adds type safety by using generics, providing a cleaner API for callers that know the expected return type. This is a solid improvement over the untyped version.

A few observations:

  1. The function properly returns a pointer to the evaluated result
  2. Error messages are clearly labeled with the function name
  3. Implementation closely follows the existing pattern for consistency
tests/describe_test.go (1)

10-16: Useful helper function for CLI command testing

The ExecuteCommand function provides a clean way to test CLI commands by setting command line arguments and executing the main CLI command.

internal/exec/describe_component_test.go (1)

14-125: Test implementation looks good!

The test function provides thorough coverage of the pager functionality with various formats and configurations. Good use of mocks and assertions to verify behavior.

pkg/utils/yaml_utils.go (2)

44-51: Good refactoring of YAML output handling.

The function now cleanly delegates YAML conversion and highlighting to the new helper function while maintaining the same interface. This separation of concerns improves maintainability.


53-63: Well-structured new helper function.

The GetHighlightedYAML function properly handles the conversion and highlighting process with appropriate error handling, returning plain YAML as a fallback if highlighting fails.

pkg/pager/pager.go (3)

8-11: Good use of interface for testability.

Defining a PageCreator interface and generating mocks with gomock enables thorough testing of code that depends on this functionality.


13-21: Solid implementation with dependency injection.

The pageCreator struct and constructor allow for dependency injection of the tea.Program creator, which improves testability.


23-37: Well-implemented pager with good terminal UI options.

The Run method creates a Bubble Tea program with appropriate options for alternate screen buffer and mouse tracking, enhancing the user experience.

cmd/describe_config.go (1)

19-49:

❌ Incorrect review comment

Improved error handling and integration of pager functionality.

The command now properly handles errors and integrates with the pager functionality. Good use of explicit flag parsing and config initialization.

The implementation is missing test coverage. Consider adding tests for this command to ensure reliability:


🏁 Script executed:

#!/bin/bash
# Check for test files that might test this code
fd -t f "test.*describe_config" -x cat {} \; | grep -i "test.*describeconfig"

Length of output: 82


Let’s dig deeper to confirm test coverage for the describe_config command:


🏁 Script executed:

#!/bin/bash
# Search for any Go test files referencing describe_config or NewDescribeConfig
rg -n "describe_config" -g "*_test.go" || true
rg -n "DescribeConfig" -g "*_test.go" || true
rg -n "NewDescribeConfig" -g "*_test.go" || true

Length of output: 2017


Tests for describe_config already exist
The CLI behavior in cmd/describe_config.go is exercised by existing unit tests in internal/exec/describe_config_test.go (e.g. TestDescribeConfig, NewDescribeConfig, ExecuteDescribeConfigCmd in various scenarios). No additional test files are required for core logic coverage.

• internal/exec/describe_config_test.go

Likely an incorrect or invalid review comment.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 19-25: cmd/describe_config.go#L19-L25
Added lines #L19 - L25 were not covered by tests


[warning] 27-30: cmd/describe_config.go#L27-L30
Added lines #L27 - L30 were not covered by tests


[warning] 32-35: cmd/describe_config.go#L32-L35
Added lines #L32 - L35 were not covered by tests


[warning] 37-42: cmd/describe_config.go#L37-L42
Added lines #L37 - L42 were not covered by tests


[warning] 45-45: cmd/describe_config.go#L45
Added line #L45 was not covered by tests


[warning] 49-49: cmd/describe_config.go#L49
Added line #L49 was not covered by tests

pkg/pager/mock_pager.go (1)

1-49: Mock implementation meets requirements for testing

This mock implementation of the PageCreator interface provides the necessary functionality for testing components that depend on paging behavior.

pkg/pager/model_test.go (5)

14-18: Good test coverage for model initialization

The Init test verifies the model initialization works as expected.


20-81: Comprehensive test suite for model updates

Test cases effectively cover key press handling, window sizing (both when ready and not ready), and viewport updates. This ensures the pager's core interaction logic works properly.


83-106: View rendering tests cover all states

Tests appropriately check both the not-ready initialization state and ready state with content rendering.


108-132: Footer view tests validate scroll percentage display

Tests verify the footer displays correct scroll information in different viewport scenarios.


134-163: Helper function tests ensure utility functions work properly

The max function and styling tests confirm the supporting UI elements function correctly.

cmd/describe_component.go (4)

12-12: Create a defined static error

Good approach using a defined static error rather than creating dynamic errors in the function.


20-21: Improved command argument validation

Using cobra.ExactArgs(1) enhances the command's validation by enforcing exactly one argument.


29-60: Structured parameter handling improves maintainability

The refactored approach of explicitly retrieving all flags and passing them as a structured params object makes the code more maintainable and testable.


72-72: Simplified flag description

The format flag description is now cleaner.

pkg/utils/json_utils.go (2)

15-22: Configuration-aware JSON printing

Good update to accept configuration for customizing the output format.


42-60: Consistent fallback approach for AtmosConfig JSON

This function correctly implements a fallback mechanism by checking for nil error explicitly.

internal/exec/describe_config_test.go (8)

1-15: Well-structured test setup for DescribeConfigFormatError

The test case effectively validates the error message format for the DescribeConfigFormatError type. This is a good practice to ensure consistent error messaging.


17-37: Good test coverage for constructor and initialization

The test cases validate both the correct creation of the describeConfigExec object and the proper initialization of its fields.

Tests ensure that:

  1. The config is properly assigned
  2. The pageCreator is initialized
  3. The printOrWriteToFile function is set

This provides good verification of the initialization logic.


39-54: Solid mocking approach for pager testing with TTY support

This test case effectively:

  1. Mocks the pager dependency
  2. Sets expectations for the pager's behavior
  3. Simulates TTY availability
  4. Tests the YAML format output path

The use of gomock provides clear validation of the pager interaction.


56-70: JSON format test ensures format flexibility

This test complements the YAML test by ensuring the JSON output format is properly handled with TTY support. The consistent pattern between test cases enhances readability.


72-84: Error handling validation for invalid formats

The test properly validates that the function correctly returns a DescribeConfigFormatError when an invalid format is provided, ensuring robust input validation.


86-100: Thorough testing of non-TTY output path

This test verifies the fallback behavior when TTY is not supported, using a mock function for printOrWriteToFile that validates the expected arguments and returns the expected value. The assertions confirm that the right format, file, and data are passed to the output function.


102-116: Test case for query execution

This test verifies the execution path when a query parameter is provided. The test structure follows the established pattern seen in other test cases.


118-127: Edge case validation for YQ expression evaluation errors

This test ensures proper error handling when a malformed query is provided, verifying that the appropriate error message is returned, which helps with debugging.

cmd/describe_affected.go (1)

22-30: Improved separation of concerns

The refactoring separates CLI argument parsing from execution logic, which improves code modularity. This aligns with the broader changes in the PR to introduce a pager feature.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 25-29: cmd/describe_affected.go#L25-L29
Added lines #L25 - L29 were not covered by tests

internal/exec/describe_component.go (4)

14-24: Well-structured parameter struct

Creating a dedicated DescribeComponentParams struct is a clear improvement over passing numerous individual parameters. This makes the code more maintainable and the function signature cleaner.


26-33: Good dependency management with struct fields

The DescribeComponentExec struct encapsulates dependencies effectively, making testing easier through dependency injection and improving the overall architecture.


35-44: Clean constructor with sensible defaults

The constructor provides appropriate default implementations while still allowing for dependency injection during testing.


113-139: Well-implemented viewConfig method

This method effectively:

  1. Checks for TTY support
  2. Formats the output based on the specified format
  3. Uses the pager to display the output

The error handling is comprehensive and the method follows a clear logical flow.

pkg/utils/highlight_utils.go (3)

33-33: Appropriate parameter type change

Changing from value to pointer parameter for AtmosConfiguration is a good performance improvement, avoiding unnecessary struct copying for this potentially large struct.


75-75: Consistent parameter order in HighlightCodeWithConfig

Moving the configuration parameter to the first position is a good practice for consistency across the codebase.

🧰 Tools
🪛 GitHub Check: golangci-lint

[failure] 75-75:
function-length: maximum number of lines per function exceeded; max 60 but got 71


175-175: Proper pointer usage in struct method

Using a pointer to the struct's config field is appropriate here to avoid unnecessary copying and align with the updated function signature.

internal/exec/describe_config.go (1)

66-67: Injectable writer not used

printOrWriteToFile is already a dependency, but calling it directly prevents mocking.

-   return d.printOrWriteToFile(d.atmosConfig, format, output, res)
+   return d.printOrWriteToFile(d.atmosConfig, format, output, res)

(No functional change, only clarifies the dependency.)

internal/exec/describe_affected.go (1)

94-103: No test coverage for constructor

Lines 94-103 are uncovered. A trivial instantiation test would lift coverage and guard against future DI changes.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 94-103: internal/exec/describe_affected.go#L94-L103
Added lines #L94 - L103 were not covered by tests

Comment on lines 105 to 143
func setFlagValueInCliArgs(flags *pflag.FlagSet, describe *exec.DescribeAffectedCmdArgs) {
flagsKeyValue := map[string]any{
"ref": &describe.Ref,
"sha": &describe.SHA,
"repo-path": &describe.RepoPath,
"ssh-key": &describe.SSHKeyPath,
"ssh-key-password": &describe.SSHKeyPassword,
"include-spacelift-admin-stacks": &describe.IncludeSpaceliftAdminStacks,
"include-dependents": &describe.IncludeDependents,
"include-settings": &describe.IncludeSettings,
"upload": &describe.Upload,
"clone-target-ref": &describe.CloneTargetRef,
"process-templates": &describe.ProcessTemplates,
"process-functions": &describe.ProcessYamlFunctions,
"skip": &describe.Skip,
"pager": &describe.CLIConfig.Settings.Terminal.Pager,
"stack": &describe.Stack,
"format": &describe.Format,
"file": &describe.OutputFile,
"query": &describe.Query,
"sha-key-path": &describe.SSHKeyPath,
}

var err error
for k := range flagsKeyValue {
if !flags.Changed(k) {
continue
}
switch v := flagsKeyValue[k].(type) {
case *string:
*v, err = flags.GetString(k)
case *bool:
*v, err = flags.GetBool(k)
default:
panic(fmt.Sprintf("unsupported type %T for flag %s", v, k))
}
checkFlagError(err)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix potential typo in flag mapping

There appears to be a typo in the flag mapping where "sha-key-path" (line 125) is mapped to SSHKeyPath. This should likely be "ssh-key-path".

flagsKeyValue := map[string]any{
	"ref":                            &describe.Ref,
	"sha":                            &describe.SHA,
	"repo-path":                      &describe.RepoPath,
	"ssh-key":                        &describe.SSHKeyPath,
	"ssh-key-password":               &describe.SSHKeyPassword,
	"include-spacelift-admin-stacks": &describe.IncludeSpaceliftAdminStacks,
	"include-dependents":             &describe.IncludeDependents,
	"include-settings":               &describe.IncludeSettings,
	"upload":                         &describe.Upload,
	"clone-target-ref":               &describe.CloneTargetRef,
	"process-templates":              &describe.ProcessTemplates,
	"process-functions":              &describe.ProcessYamlFunctions,
	"skip":                           &describe.Skip,
	"pager":                          &describe.CLIConfig.Settings.Terminal.Pager,
	"stack":                          &describe.Stack,
	"format":                         &describe.Format,
	"file":                           &describe.OutputFile,
	"query":                          &describe.Query,
-	"sha-key-path":                   &describe.SSHKeyPath,
}

Let's verify if this is actually used:

#!/bin/bash
# Check if 'sha-key-path' flag is defined anywhere
grep -r "sha-key-path" --include="*.go" .
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 105-131: cmd/describe_affected.go#L105-L131
Added lines #L105 - L131 were not covered by tests


[warning] 133-139: cmd/describe_affected.go#L133-L139
Added lines #L133 - L139 were not covered by tests


[warning] 141-141: cmd/describe_affected.go#L141
Added line #L141 was not covered by tests

🤖 Prompt for AI Agents (early access)
In cmd/describe_affected.go between lines 105 and 143, the flag mapping has a
typo where "sha-key-path" is incorrectly used instead of "ssh-key-path". To fix
this, rename the key "sha-key-path" to "ssh-key-path" in the flagsKeyValue map
so it correctly maps to describe.SSHKeyPath.

Comment on lines 62 to 103
func parseDescribeAffectedCliArgs(cmd *cobra.Command, args []string) (exec.DescribeAffectedCmdArgs, error) {
info, err := exec.ProcessCommandLineArgs("", cmd, args, nil)
if err != nil {
return exec.DescribeAffectedCmdArgs{}, err
}

atmosConfig, err := cfg.InitCliConfig(info, true)
if err != nil {
return exec.DescribeAffectedCmdArgs{}, err
}

err = exec.ValidateStacks(atmosConfig)
if err != nil {
return exec.DescribeAffectedCmdArgs{}, err
}

// Process flags
flags := cmd.Flags()

result := exec.DescribeAffectedCmdArgs{
CLIConfig: atmosConfig,
}
setFlagValueInCliArgs(flags, &result)

// When uploading, always include dependents and settings for all affected components
if result.Upload {
result.IncludeDependents = true
result.IncludeSettings = true
}

if result.Format != "" && result.Format != "yaml" && result.Format != "json" {
return exec.DescribeAffectedCmdArgs{}, fmt.Errorf("invalid '--format' flag '%s'. Valid values are 'json' (default) and 'yaml'", result.Format)
}
if result.Format == "" {
result.Format = "json"
}
if result.RepoPath != "" && (result.Ref != "" || result.SHA != "" || result.SSHKeyPath != "" || result.SSHKeyPassword != "") {
return exec.DescribeAffectedCmdArgs{}, errors.New("if the '--repo-path' flag is specified, the '--ref', '--sha', '--ssh-key' and '--ssh-key-password' flags can't be used")
}

return result, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Reduce function complexity

The function has high cyclomatic complexity (14, exceeding the max of 10) as flagged by static analysis.

Consider breaking this function into smaller, focused subfunctions to improve readability and maintainability.

func parseDescribeAffectedCliArgs(cmd *cobra.Command, args []string) (exec.DescribeAffectedCmdArgs, error) {
	info, err := exec.ProcessCommandLineArgs("", cmd, args, nil)
	if err != nil {
		return exec.DescribeAffectedCmdArgs{}, err
	}

	atmosConfig, err := cfg.InitCliConfig(info, true)
	if err != nil {
		return exec.DescribeAffectedCmdArgs{}, err
	}

	err = exec.ValidateStacks(atmosConfig)
	if err != nil {
		return exec.DescribeAffectedCmdArgs{}, err
	}

	// Process flags
	flags := cmd.Flags()

	result := exec.DescribeAffectedCmdArgs{
		CLIConfig: atmosConfig,
	}
	setFlagValueInCliArgs(flags, &result)

	// When uploading, always include dependents and settings for all affected components
	if result.Upload {
		result.IncludeDependents = true
		result.IncludeSettings = true
	}

+	if err := validateFormatFlag(&result); err != nil {
+		return exec.DescribeAffectedCmdArgs{}, err
+	}
+
+	if err := validateRepoPathFlags(&result); err != nil {
+		return exec.DescribeAffectedCmdArgs{}, err
+	}
+
+	return result, nil
+}
+
+func validateFormatFlag(result *exec.DescribeAffectedCmdArgs) error {
	if result.Format != "" && result.Format != "yaml" && result.Format != "json" {
		return fmt.Errorf("invalid '--format' flag '%s'. Valid values are 'json' (default) and 'yaml'", result.Format)
	}
	if result.Format == "" {
		result.Format = "json"
	}
+	return nil
+}
+
+func validateRepoPathFlags(result *exec.DescribeAffectedCmdArgs) error {
	if result.RepoPath != "" && (result.Ref != "" || result.SHA != "" || result.SSHKeyPath != "" || result.SSHKeyPassword != "") {
		return errors.New("if the '--repo-path' flag is specified, the '--ref', '--sha', '--ssh-key' and '--ssh-key-password' flags can't be used")
	}
+	return nil
}

Run the following to check for direct usages of these validation functions:

#!/bin/bash
rg "parseDescribeAffectedCliArgs|validateFormatFlag|validateRepoPathFlags" --type go
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 62-66: cmd/describe_affected.go#L62-L66
Added lines #L62 - L66 were not covered by tests


[warning] 68-71: cmd/describe_affected.go#L68-L71
Added lines #L68 - L71 were not covered by tests


[warning] 73-76: cmd/describe_affected.go#L73-L76
Added lines #L73 - L76 were not covered by tests


[warning] 79-90: cmd/describe_affected.go#L79-L90
Added lines #L79 - L90 were not covered by tests


[warning] 92-100: cmd/describe_affected.go#L92-L100
Added lines #L92 - L100 were not covered by tests


[warning] 102-102: cmd/describe_affected.go#L102
Added line #L102 was not covered by tests

🪛 GitHub Check: golangci-lint

[failure] 62-62:
cyclomatic: function parseDescribeAffectedCliArgs has cyclomatic complexity 14 (> max enabled 10)


[failure] 93-93:
do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf("invalid '--format' flag '%s'. Valid values are 'json' (default) and 'yaml'", result.Format)"


[failure] 99-99:
do not define dynamic errors, use wrapped static errors instead: "errors.New("if the '--repo-path' flag is specified, the '--ref', '--sha', '--ssh-key' and '--ssh-key-password' flags can't be used")"

🤖 Prompt for AI Agents (early access)
In cmd/describe_affected.go between lines 62 and 103, the
parseDescribeAffectedCliArgs function is too complex with a cyclomatic
complexity of 14. To fix this, refactor the function by extracting the format
flag validation and the repo path flags validation into separate helper
functions, such as validateFormatFlag and validateRepoPathFlags. Then call these
helper functions from parseDescribeAffectedCliArgs to reduce its complexity and
improve readability and maintainability.

Comment on lines 107 to 151
func (d *DescribeAffectedExec) Execute(a DescribeAffectedCmdArgs) error {
var affected []schema.Affected
var headHead, baseHead *plumbing.Reference
var repoUrl string
var err error
if a.RepoPath != "" {
affected, headHead, baseHead, repoUrl, err = d.executeDescribeAffectedWithTargetRepoPath(
a.CLIConfig,
a.RepoPath,
a.Verbose,
a.IncludeSpaceliftAdminStacks,
a.IncludeSettings,
a.Stack,
a.ProcessTemplates,
a.ProcessYamlFunctions,
a.Skip,
)
} else if a.CloneTargetRef {
affected, headHead, baseHead, repoUrl, err = d.executeDescribeAffectedWithTargetRefClone(
a.CLIConfig,
a.Ref,
a.SHA,
a.SSHKeyPath,
a.SSHKeyPassword,
a.Verbose,
a.IncludeSpaceliftAdminStacks,
a.IncludeSettings,
a.Stack,
a.ProcessTemplates,
a.ProcessYamlFunctions,
a.Skip,
)
} else {
affected, headHead, baseHead, repoUrl, err = d.executeDescribeAffectedWithTargetRefCheckout(
a.CLIConfig,
a.Ref,
a.SHA,
a.Verbose,
a.IncludeSpaceliftAdminStacks,
a.IncludeSettings,
a.Stack,
a.ProcessTemplates,
a.ProcessYamlFunctions,
a.Skip,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Complexity regressed

(*DescribeAffectedExec).Execute still scores 28 on cognitive-complexity after the refactor.
Consider extracting the three “target” branches into standalone helpers to reach ≤ 20 and silence the linter.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 107-151: internal/exec/describe_affected.go#L107-L151
Added lines #L107 - L151 were not covered by tests

🪛 GitHub Check: golangci-lint

[failure] 107-107:
cognitive complexity 28 of func (*DescribeAffectedExec).Execute is high (> 20)


[failure] 112-112:
ifElseChain: rewrite if-else to switch statement

🤖 Prompt for AI Agents (early access)
In internal/exec/describe_affected.go around lines 107 to 151, the Execute
method is too complex with a cognitive complexity score of 28. To fix this,
extract the three main conditional branches that call
executeDescribeAffectedWithTargetRepoPath,
executeDescribeAffectedWithTargetRefClone, and
executeDescribeAffectedWithTargetRefCheckout into separate helper functions.
Then, replace the inline calls in Execute with calls to these new helpers to
reduce complexity and improve readability, aiming to bring the score to 20 or
below.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 94cc2ce and 22de4bb.

📒 Files selected for processing (1)
  • pkg/utils/highlight_utils.go (3 hunks)
🧰 Additional context used
🪛 GitHub Check: Build (macos-latest, macos)
pkg/utils/highlight_utils.go

[failure] 139-139:
undefined: HighlightSettings

🪛 GitHub Check: Build (ubuntu-latest, linux)
pkg/utils/highlight_utils.go

[failure] 139-139:
undefined: HighlightSettings

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Build (windows-latest, windows)
  • GitHub Check: Summary
🔇 Additional comments (4)
pkg/utils/highlight_utils.go (4)

33-33: Function signature updated correctly for configuration consistency.

The change to accept a pointer to AtmosConfiguration aligns with the broader refactoring for configuration consistency across the codebase. This approach ensures all highlight utility functions have access to the same configuration context.


74-114: Improved modularity through function refactoring.

The HighlightCodeWithConfig function has been successfully refactored to extract lexer and formatter selection into helper functions. This addresses the cyclomatic complexity issue flagged in previous reviews and improves code organization.


116-136: Good extraction of lexer selection logic.

The helper functions getLexer and isYAML effectively encapsulate the logic for determining the appropriate lexer based on content type. The YAML detection heuristic is simple but sufficient for common YAML formats.


172-172: Correctly updated to use pointer parameter.

The call to HighlightCodeWithConfig has been properly updated to pass a pointer to the configuration object, consistent with the function signature change.

@samtholiya samtholiya force-pushed the feature/dev-3182-add-pager-to-atmos-describe-affected-command branch from 22de4bb to 2bd026a Compare May 8, 2025 20:41
Copy link

mergify bot commented May 8, 2025

💥 This pull request now has conflicts. Could you fix it @samtholiya? 🙏

@mergify mergify bot added the conflict This PR has conflicts label May 8, 2025
@mergify mergify bot removed the conflict This PR has conflicts label May 8, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
internal/exec/describe_affected.go (1)

107-151: 🛠️ Refactor suggestion

Consider reducing cognitive complexity further

The Execute method still has high cognitive complexity (28) which exceeds the recommended threshold (20). The if-else chain could be refactored into a switch statement or strategy pattern.

-func (d *DescribeAffectedExec) Execute(a DescribeAffectedCmdArgs) error {
-	var affected []schema.Affected
-	var headHead, baseHead *plumbing.Reference
-	var repoUrl string
-	var err error
-	if a.RepoPath != "" {
-		affected, headHead, baseHead, repoUrl, err = d.executeDescribeAffectedWithTargetRepoPath(
-			a.CLIConfig,
-			a.RepoPath,
-			a.Verbose,
-			a.IncludeSpaceliftAdminStacks,
-			a.IncludeSettings,
-			a.Stack,
-			a.ProcessTemplates,
-			a.ProcessYamlFunctions,
-			a.Skip,
-		)
-	} else if a.CloneTargetRef {
-		affected, headHead, baseHead, repoUrl, err = d.executeDescribeAffectedWithTargetRefClone(
-			a.CLIConfig,
-			a.Ref,
-			a.SHA,
-			a.SSHKeyPath,
-			a.SSHKeyPassword,
-			a.Verbose,
-			a.IncludeSpaceliftAdminStacks,
-			a.IncludeSettings,
-			a.Stack,
-			a.ProcessTemplates,
-			a.ProcessYamlFunctions,
-			a.Skip,
-		)
-	} else {
-		affected, headHead, baseHead, repoUrl, err = d.executeDescribeAffectedWithTargetRefCheckout(
-			a.CLIConfig,
-			a.Ref,
-			a.SHA,
-			a.Verbose,
-			a.IncludeSpaceliftAdminStacks,
-			a.IncludeSettings,
-			a.Stack,
-			a.ProcessTemplates,
-			a.ProcessYamlFunctions,
-			a.Skip,
-		)
-	}
+func (d *DescribeAffectedExec) Execute(a DescribeAffectedCmdArgs) error {
+	var affected []schema.Affected
+	var headHead, baseHead *plumbing.Reference
+	var repoUrl string
+	var err error
+
+	affected, headHead, baseHead, repoUrl, err = d.getAffectedComponents(a)

And add this helper method:

func (d *DescribeAffectedExec) getAffectedComponents(a DescribeAffectedCmdArgs) ([]schema.Affected, *plumbing.Reference, *plumbing.Reference, string, error) {
	if a.RepoPath != "" {
		return d.executeDescribeAffectedWithTargetRepoPath(
			a.CLIConfig,
			a.RepoPath,
			a.Verbose,
			a.IncludeSpaceliftAdminStacks,
			a.IncludeSettings,
			a.Stack,
			a.ProcessTemplates,
			a.ProcessYamlFunctions,
			a.Skip,
		)
	} else if a.CloneTargetRef {
		return d.executeDescribeAffectedWithTargetRefClone(
			a.CLIConfig,
			a.Ref,
			a.SHA,
			a.SSHKeyPath,
			a.SSHKeyPassword,
			a.Verbose,
			a.IncludeSpaceliftAdminStacks,
			a.IncludeSettings,
			a.Stack,
			a.ProcessTemplates,
			a.ProcessYamlFunctions,
			a.Skip,
		)
	}
	return d.executeDescribeAffectedWithTargetRefCheckout(
		a.CLIConfig,
		a.Ref,
		a.SHA,
		a.Verbose,
		a.IncludeSpaceliftAdminStacks,
		a.IncludeSettings,
		a.Stack,
		a.ProcessTemplates,
		a.ProcessYamlFunctions,
		a.Skip,
	)
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2891ec9 and 1a93a97.

📒 Files selected for processing (11)
  • cmd/describe_component.go (2 hunks)
  • go.mod (2 hunks)
  • internal/exec/describe_affected.go (5 hunks)
  • internal/exec/file_utils.go (2 hunks)
  • pkg/config/default.go (1 hunks)
  • pkg/config/load.go (2 hunks)
  • pkg/schema/schema.go (1 hunks)
  • tests/snapshots/TestCLICommands_atmos_describe_config.stdout.golden (1 hunks)
  • tests/snapshots/TestCLICommands_atmos_describe_config_-f_yaml.stdout.golden (1 hunks)
  • tests/snapshots/TestCLICommands_atmos_describe_config_imports.stdout.golden (1 hunks)
  • tests/snapshots/TestCLICommands_atmos_describe_configuration.stdout.golden (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • tests/snapshots/TestCLICommands_atmos_describe_config.stdout.golden
  • tests/snapshots/TestCLICommands_atmos_describe_config_imports.stdout.golden
  • pkg/config/default.go
  • tests/snapshots/TestCLICommands_atmos_describe_config_-f_yaml.stdout.golden
  • internal/exec/file_utils.go
  • pkg/config/load.go
  • tests/snapshots/TestCLICommands_atmos_describe_configuration.stdout.golden
  • pkg/schema/schema.go
  • go.mod
  • cmd/describe_component.go
🧰 Additional context used
🧠 Learnings (1)
internal/exec/describe_affected.go (1)
Learnt from: samtholiya
PR: cloudposse/atmos#1147
File: internal/exec/validate_schema.go:42-57
Timestamp: 2025-04-11T22:06:46.999Z
Learning: The "ExecuteAtmosValidateSchemaCmd" function in internal/exec/validate_schema.go has been reviewed and confirmed to have acceptable cognitive complexity despite static analysis warnings. The function uses a clean structure with only three if statements for error handling and delegates complex operations to helper methods.
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: Build (windows-latest, windows)
  • GitHub Check: Lint (golangci)
  • GitHub Check: Analyze (go)
  • GitHub Check: website-deploy-preview
  • GitHub Check: Summary
🔇 Additional comments (5)
internal/exec/describe_affected.go (5)

38-105: Good refactoring with dependency injection structure

The introduction of DescribeAffectedExec struct with function fields enables better dependency injection and testability. This is a good approach for modularizing the command execution logic.


166-172: Good implementation of pager functionality

The addition of pager functionality through the viewWithScroll function is well implemented and improves the user experience when viewing large output.


330-339: Good use of struct for function parameters

Using a struct to group related parameters for viewWithScroll is a good practice that improves readability and maintainability.


341-359: Well-implemented pager fallback mechanism

The viewWithScroll function has a good fallback mechanism that tries to use the pager first and gracefully falls back to direct printing if there's an issue.


361-396: Good implementation of view configuration

The viewConfig function properly handles format-specific output generation with appropriate error handling and pager integration.

@@ -41,3 +82,11 @@

describeCmd.AddCommand(describeComponentCmd)
}

// We prefer to panic because this is a developer error.

Check failure

Code scanning / golangci-lint

Sentence should start with a capital letter Error

Sentence should start with a capital letter
cloneTargetRef, err := flags.GetBool("clone-target-ref")
if err != nil {
return DescribeAffectedCmdArgs{}, err
func (d *DescribeAffectedExec) Execute(a DescribeAffectedCmdArgs) error {

Check failure

Code scanning / golangci-lint

function-length: maximum number of lines per function exceeded; max 60 but got 75 Error

function-length: maximum number of lines per function exceeded; max 60 but got 75
if verbose {
atmosConfig.Logs.Level = u.LogLevelTrace
err := logger.SetLogLevel(l.LogLevelTrace)
if a.Query == "" {

Check warning

Code scanning / golangci-lint

if a.Query == "" has complex nested blocks (complexity: 4) Warning

if a.Query == "" has complex nested blocks (complexity: 4)

processTemplates, err := flags.GetBool("process-templates")
func (d *DescribeAffectedExec) uploadableQuery(args DescribeAffectedCmdArgs, repoUrl string, headHead, baseHead *plumbing.Reference, affected []schema.Affected) error {

Check failure

Code scanning / golangci-lint

hugeParam: args is heavy (6120 bytes); consider passing it by pointer Error

hugeParam: args is heavy (6120 bytes); consider passing it by pointer
if err != nil {
return DescribeAffectedCmdArgs{}, err
}
if args.Upload {

Check warning

Code scanning / golangci-lint

if args.Upload has complex nested blocks (complexity: 4) Warning

if args.Upload has complex nested blocks (complexity: 4)
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (7)
cmd/describe_component.go (1)

86-87: Comment should start with a capital letter.

According to Go's commenting conventions and the static analysis warning, the comment should start with a capital letter.

- // We prefer to panic because this is a developer error.
- // checkFlagNotPresentError checks if the error is nil.
+ // We prefer to panic because this is a developer error.
+ // CheckFlagNotPresentError checks if the error is nil.
🧰 Tools
🪛 GitHub Check: golangci-lint

[failure] 86-86:
Sentence should start with a capital letter

cmd/describe_affected.go (2)

116-116: ⚠️ Potential issue

Fix the typo in flag mapping

The key "sha-key-path" is mapping to SSHKeyPath, which should be "ssh-key-path" for consistency.

Apply this fix:

-		"sha-key-path":                   &describe.SSHKeyPath,
+		"ssh-key-path":                   &describe.SSHKeyPath,

65-93: 🛠️ Refactor suggestion

Reduce function complexity

The function parseDescribeAffectedCliArgs has high cyclomatic complexity (11) which makes it harder to maintain.

Consider breaking it into smaller functions:

func parseDescribeAffectedCliArgs(cmd *cobra.Command, args []string) (exec.DescribeAffectedCmdArgs, error) {
	var atmosConfig schema.AtmosConfiguration
	if info, err := exec.ProcessCommandLineArgs("", cmd, args, nil); err != nil {
		return exec.DescribeAffectedCmdArgs{}, err
	} else if atmosConfig, err = cfg.InitCliConfig(info, true); err != nil {
		return exec.DescribeAffectedCmdArgs{}, err
	}

	err := exec.ValidateStacks(atmosConfig)
	if err != nil {
		return exec.DescribeAffectedCmdArgs{}, err
	}

	// Process flags
	flags := cmd.Flags()

	result := exec.DescribeAffectedCmdArgs{
		CLIConfig: atmosConfig,
	}
	setFlagValueInCliArgs(flags, &result)

+	if err := validateFormatFlag(&result); err != nil {
+		return exec.DescribeAffectedCmdArgs{}, err
+	}
+
+	if err := validateRepoPathFlags(&result); err != nil {
+		return exec.DescribeAffectedCmdArgs{}, err
+	}
+
+	return result, nil
+}
+
+func validateFormatFlag(result *exec.DescribeAffectedCmdArgs) error {
	if result.Format != "yaml" && result.Format != "json" {
		return exec.ErrInvalidFormat
	}
+	return nil
+}
+
+func validateRepoPathFlags(result *exec.DescribeAffectedCmdArgs) error {
	if result.RepoPath != "" && (result.Ref != "" || result.SHA != "" || result.SSHKeyPath != "" || result.SSHKeyPassword != "") {
		return ErrRepoPathConflict
	}
+	return nil
}
🧰 Tools
🪛 GitHub Check: golangci-lint

[failure] 65-65:
cyclomatic: function parseDescribeAffectedCliArgs has cyclomatic complexity 11 (> max enabled 10)

internal/exec/describe_affected.go (4)

177-177: ⚠️ Potential issue

Fix inconsistent dependency usage

You're creating new instances instead of using the injected dependencies.

Use the dependencies from the struct:

-err = viewWithScroll(viewWithScrollProps{pager.New(), term.IsTTYSupportForStdout, d.printOrWriteToFile, d.atmosConfig, "Affected components and stacks", a.Format, a.OutputFile, res})
+err = viewWithScroll(viewWithScrollProps{d.pageCreator, d.IsTTYSupportForStdout, d.printOrWriteToFile, d.atmosConfig, "Affected components and stacks", a.Format, a.OutputFile, res})

378-404: 🛠️ Refactor suggestion

Pass heavy struct viewConfigProps by pointer

Similar to the previous comment, this function takes a heavy struct by value.

Pass by pointer instead:

-func viewConfig(v viewConfigProps) error {
+func viewConfig(v *viewConfigProps) error {

And update all callers:

-err := viewConfig(viewConfigProps{v.pageCreator, v.isTTYSupportForStdout, v.atmosConfig, v.displayName, v.format, v.res})
+err := viewConfig(&viewConfigProps{v.pageCreator, v.isTTYSupportForStdout, v.atmosConfig, v.displayName, v.format, v.res})
🧰 Tools
🪛 GitHub Check: golangci-lint

[failure] 378-378:
hugeParam: v is heavy (80 bytes); consider passing it by pointer


107-183: 🛠️ Refactor suggestion

Reduce Execute function length

The Execute method is 75 lines long (exceeds max 60) which makes it harder to follow.

Consider extracting the execution logic for each case into a helper method:

func (d *DescribeAffectedExec) Execute(a DescribeAffectedCmdArgs) error {
	var affected []schema.Affected
	var headHead, baseHead *plumbing.Reference
	var repoUrl string
	var err error
+	
+	// Extract execution into a separate function
+	affected, headHead, baseHead, repoUrl, err = d.executeDescribeAffected(a)
+	if err != nil {
+		return err
+	}

-	switch {
-	case a.RepoPath != "":
-		affected, headHead, baseHead, repoUrl, err = d.executeDescribeAffectedWithTargetRepoPath(
-			a.CLIConfig,
-			a.RepoPath,
-			a.Verbose,
-			a.IncludeSpaceliftAdminStacks,
-			a.IncludeSettings,
-			a.Stack,
-			a.ProcessTemplates,
-			a.ProcessYamlFunctions,
-			a.Skip,
-		)
-	case a.CloneTargetRef:
-		affected, headHead, baseHead, repoUrl, err = d.executeDescribeAffectedWithTargetRefClone(
-			a.CLIConfig,
-			a.Ref,
-			a.SHA,
-			a.SSHKeyPath,
-			a.SSHKeyPassword,
-			a.Verbose,
-			a.IncludeSpaceliftAdminStacks,
-			a.IncludeSettings,
-			a.Stack,
-			a.ProcessTemplates,
-			a.ProcessYamlFunctions,
-			a.Skip,
-		)
-	default:
-		affected, headHead, baseHead, repoUrl, err = d.executeDescribeAffectedWithTargetRefCheckout(
-			a.CLIConfig,
-			a.Ref,
-			a.SHA,
-			a.Verbose,
-			a.IncludeSpaceliftAdminStacks,
-			a.IncludeSettings,
-			a.Stack,
-			a.ProcessTemplates,
-			a.ProcessYamlFunctions,
-			a.Skip,
-		)
-	}
-
-	if err != nil {
-		return err
-	}

	// Add dependent components and stacks for each affected component
	if len(affected) > 0 && a.IncludeDependents {
		err = d.addDependentsToAffected(a.CLIConfig, &affected, a.IncludeSettings)
		if err != nil {
			return err
		}
	}

	if a.Query == "" {
		if err := d.uploadableQuery(a, repoUrl, headHead, baseHead, affected); err != nil {
			return err
		}
	} else {
		res, err := u.EvaluateYqExpression(d.atmosConfig, affected, a.Query)
		if err != nil {
			return err
		}

		err = viewWithScroll(viewWithScrollProps{pager.New(), term.IsTTYSupportForStdout, d.printOrWriteToFile, d.atmosConfig, "Affected components and stacks", a.Format, a.OutputFile, res})
		if err != nil {
			return err
		}
	}
	return nil
}

+// executeDescribeAffected handles the selection of the appropriate execution path
+func (d *DescribeAffectedExec) executeDescribeAffected(a DescribeAffectedCmdArgs) ([]schema.Affected, *plumbing.Reference, *plumbing.Reference, string, error) {
+	switch {
+	case a.RepoPath != "":
+		return d.executeDescribeAffectedWithTargetRepoPath(
+			a.CLIConfig,
+			a.RepoPath,
+			a.Verbose,
+			a.IncludeSpaceliftAdminStacks,
+			a.IncludeSettings,
+			a.Stack,
+			a.ProcessTemplates,
+			a.ProcessYamlFunctions,
+			a.Skip,
+		)
+	case a.CloneTargetRef:
+		return d.executeDescribeAffectedWithTargetRefClone(
+			a.CLIConfig,
+			a.Ref,
+			a.SHA,
+			a.SSHKeyPath,
+			a.SSHKeyPassword,
+			a.Verbose,
+			a.IncludeSpaceliftAdminStacks,
+			a.IncludeSettings,
+			a.Stack,
+			a.ProcessTemplates,
+			a.ProcessYamlFunctions,
+			a.Skip,
+		)
+	default:
+		return d.executeDescribeAffectedWithTargetRefCheckout(
+			a.CLIConfig,
+			a.Ref,
+			a.SHA,
+			a.Verbose,
+			a.IncludeSpaceliftAdminStacks,
+			a.IncludeSettings,
+			a.Stack,
+			a.ProcessTemplates,
+			a.ProcessYamlFunctions,
+			a.Skip,
+		)
+	}
+}
🧰 Tools
🪛 GitHub Check: golangci-lint

[failure] 107-107:
function-length: maximum number of lines per function exceeded; max 60 but got 75


[warning] 167-167:
if a.Query == "" has complex nested blocks (complexity: 4)


349-367: 🛠️ Refactor suggestion

Pass heavy struct viewWithScrollProps by pointer

The viewWithScroll function takes a heavy struct by value.

Pass by pointer instead:

-func viewWithScroll(v viewWithScrollProps) error {
+func viewWithScroll(v *viewWithScrollProps) error {

And update all callers:

-err = viewWithScroll(viewWithScrollProps{d.pageCreator, d.IsTTYSupportForStdout, d.printOrWriteToFile, d.atmosConfig, "Affected components and stacks", a.Format, a.OutputFile, res})
+err = viewWithScroll(&viewWithScrollProps{d.pageCreator, d.IsTTYSupportForStdout, d.printOrWriteToFile, d.atmosConfig, "Affected components and stacks", a.Format, a.OutputFile, res})
🧰 Tools
🪛 GitHub Check: golangci-lint

[failure] 349-349:
hugeParam: v is heavy (104 bytes); consider passing it by pointer

🧹 Nitpick comments (2)
cmd/describe_component.go (2)

12-13: Good practice defining a static error, but variable naming could be improved.

Using a predefined static error is a good practice. However, the variable name errInvalidFlag could be more specific to better reflect that it's related to argument validation.

- var errInvalidFlag = errors.New("invalid arguments. The command requires one argument `component`")
+ var errComponentArgumentRequired = errors.New("invalid arguments. The command requires one argument `component`")

88-92: Consider a more graceful error handling approach.

Using panic for flag errors means that unexpected flag issues could crash the application. Consider a more graceful approach that reports errors to the user without crashing.

func checkFlagNotPresentError(err error) {
  if err != nil {
-   panic(err)
+   u.LogErrorAndExit(err)
  }
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a93a97 and 0baebcc.

📒 Files selected for processing (4)
  • cmd/describe_affected.go (3 hunks)
  • cmd/describe_component.go (2 hunks)
  • internal/exec/describe_affected.go (5 hunks)
  • internal/exec/describe_component_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/exec/describe_component_test.go
🧰 Additional context used
🧠 Learnings (1)
internal/exec/describe_affected.go (1)
Learnt from: samtholiya
PR: cloudposse/atmos#1147
File: internal/exec/validate_schema.go:42-57
Timestamp: 2025-04-11T22:06:46.999Z
Learning: The "ExecuteAtmosValidateSchemaCmd" function in internal/exec/validate_schema.go has been reviewed and confirmed to have acceptable cognitive complexity despite static analysis warnings. The function uses a clean structure with only three if statements for error handling and delegates complex operations to helper methods.
🪛 GitHub Check: golangci-lint
cmd/describe_affected.go

[failure] 65-65:
cyclomatic: function parseDescribeAffectedCliArgs has cyclomatic complexity 11 (> max enabled 10)

internal/exec/describe_affected.go

[failure] 107-107:
function-length: maximum number of lines per function exceeded; max 60 but got 75


[warning] 167-167:
if a.Query == "" has complex nested blocks (complexity: 4)


[failure] 185-185:
hugeParam: args is heavy (6120 bytes); consider passing it by pointer


[warning] 192-192:
if args.Upload has complex nested blocks (complexity: 4)


[failure] 226-226:
cognitive complexity 28 of func ExecuteDescribeAffectedCmd is high (> 20)


[failure] 349-349:
hugeParam: v is heavy (104 bytes); consider passing it by pointer


[failure] 378-378:
hugeParam: v is heavy (80 bytes); consider passing it by pointer

cmd/describe_component.go

[failure] 86-86:
Sentence should start with a capital letter

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Build (windows-latest, windows)
  • GitHub Check: Summary
🔇 Additional comments (8)
cmd/describe_component.go (3)

29-47: Good job implementing detailed flag validation.

The explicit flag retrieval with error checking improves robustness by validating each flag individually. Passing the new pager flag to the executor is a clean implementation.


50-60: Great refactoring to use a parameter struct and dedicated executor.

This refactoring improves modularity by moving command execution logic to a dedicated executor and using a structured parameters approach. The code is now more maintainable and easier to test.


72-72: Streamlined flag description.

The simplified flag description is now more concise while still communicating the essential information.

cmd/describe_affected.go (2)

16-16: Use var declaration for ErrRepoPathConflict

Good job defining a package variable for the error. This is better than creating dynamic errors.


86-91: Good usage of error validation

Clean validation logic to check format values and repo path conflicts.

internal/exec/describe_affected.go (3)

38-105: Great use of dependency injection pattern

The new DescribeAffectedExec struct with explicit dependencies improves testability and maintainability. Well structured!


338-347: Good struct design for view properties

Well-structured props structs that bundle related parameters together. This is better than having many individual parameters.


112-153: Good switch statement usage

Using switch instead of if-else chains improves readability. This is a good change!

Comment on lines +20 to +27
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// Check Atmos configuration
checkAtmosConfig()

err := e.ExecuteDescribeComponentCmd(cmd, args)
if len(args) != 1 {
return errInvalidFlag
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove redundant argument validation.

You're using cobra.ExactArgs(1) on line 20, which already performs the validation that you're doing manually on lines 25-27. This creates redundant code that accomplishes the same check twice.

- Args:               cobra.ExactArgs(1),
  RunE: func(cmd *cobra.Command, args []string) error {
    // Check Atmos configuration
    checkAtmosConfig()

-   if len(args) != 1 {
-     return errInvalidFlag
-   }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents (early access)
In cmd/describe_component.go around lines 20 to 27, remove the manual argument
length check inside the RunE function since cobra.ExactArgs(1) on line 20
already validates that exactly one argument is provided. This eliminates
redundant validation and simplifies the code.

Comment on lines +141 to +145
func checkFlagError(err error) {
if err != nil {
panic(err)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling approach

Using panic for flag errors makes the code brittle and difficult to debug.

Consider returning errors instead of panicking:

-func checkFlagError(err error) {
-	if err != nil {
-		panic(err)
-	}
-}
+func checkFlagError(err error) error {
+	return err
+}

Then update the caller in setFlagValueInCliArgs:

 for k := range flagsKeyValue {
     if !flags.Changed(k) {
         continue
     }
     switch v := flagsKeyValue[k].(type) {
     case *string:
-        *v, err = flags.GetString(k)
+        var value string
+        value, err = flags.GetString(k)
+        if err != nil {
+            return err
+        }
+        *v = value
     case *bool:
-        *v, err = flags.GetBool(k)
+        var value bool
+        value, err = flags.GetBool(k)
+        if err != nil {
+            return err
+        }
+        *v = value
     default:
-        panic(fmt.Sprintf("unsupported type %T for flag %s", v, k))
+        return fmt.Errorf("unsupported type %T for flag %s", v, k)
     }
-    checkFlagError(err)
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents (early access)
In cmd/describe_affected.go around lines 141 to 145, the function checkFlagError
currently panics on error, which is brittle and hard to debug. Refactor
checkFlagError to return the error instead of panicking, and update its caller
in setFlagValueInCliArgs to handle the error properly, such as by returning it
up the call stack or logging it gracefully.

Comment on lines +185 to 223
func (d *DescribeAffectedExec) uploadableQuery(args DescribeAffectedCmdArgs, repoUrl string, headHead, baseHead *plumbing.Reference, affected []schema.Affected) error {
log.Debug("\nAffected components and stacks: \n")
err := viewWithScroll(viewWithScrollProps{d.pageCreator, d.IsTTYSupportForStdout, d.printOrWriteToFile, d.atmosConfig, "Affected components and stacks", args.Format, args.OutputFile, affected})
if err != nil {
return DescribeAffectedCmdArgs{}, err
return err
}

processYamlFunctions, err := flags.GetBool("process-functions")
if err != nil {
return DescribeAffectedCmdArgs{}, err
}
if args.Upload {
// Parse the repo URL
gitURL, err := giturl.NewGitURL(repoUrl)
if err != nil {
return err
}
logger, err := l.NewLoggerFromCliConfig(*d.atmosConfig)
if err != nil {
return err
}
apiClient, err := pro.NewAtmosProAPIClientFromEnv(logger)
if err != nil {
return err
}

skip, err := flags.GetStringSlice("skip")
if err != nil {
return DescribeAffectedCmdArgs{}, err
}
req := pro.AffectedStacksUploadRequest{
HeadSHA: headHead.Hash().String(),
BaseSHA: baseHead.Hash().String(),
RepoURL: repoUrl,
RepoName: gitURL.GetRepoName(),
RepoOwner: gitURL.GetOwnerName(),
RepoHost: gitURL.GetHostName(),
Stacks: affected,
}

result := DescribeAffectedCmdArgs{
CLIConfig: atmosConfig,
CloneTargetRef: cloneTargetRef,
Format: format,
IncludeDependents: includeDependents,
IncludeSettings: includeSettings,
IncludeSpaceliftAdminStacks: includeSpaceliftAdminStacks,
Logger: logger,
OutputFile: file,
Ref: ref,
RepoPath: repoPath,
SHA: sha,
SSHKeyPath: sshKeyPath,
SSHKeyPassword: sshKeyPassword,
Verbose: verbose,
Upload: upload,
Stack: stack,
Query: query,
ProcessTemplates: processTemplates,
ProcessYamlFunctions: processYamlFunctions,
Skip: skip,
err = apiClient.UploadAffectedStacks(req)
if err != nil {
return err
}
}

return result, nil
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Pass heavy structs by pointer

The uploadableQuery method receives a heavy struct by value (~6120 bytes).

Pass by pointer instead:

-func (d *DescribeAffectedExec) uploadableQuery(args DescribeAffectedCmdArgs, repoUrl string, headHead, baseHead *plumbing.Reference, affected []schema.Affected) error {
+func (d *DescribeAffectedExec) uploadableQuery(args *DescribeAffectedCmdArgs, repoUrl string, headHead, baseHead *plumbing.Reference, affected []schema.Affected) error {

And update the caller:

-if err := d.uploadableQuery(a, repoUrl, headHead, baseHead, affected); err != nil {
+if err := d.uploadableQuery(&a, repoUrl, headHead, baseHead, affected); err != nil {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (d *DescribeAffectedExec) uploadableQuery(args DescribeAffectedCmdArgs, repoUrl string, headHead, baseHead *plumbing.Reference, affected []schema.Affected) error {
log.Debug("\nAffected components and stacks: \n")
err := viewWithScroll(viewWithScrollProps{d.pageCreator, d.IsTTYSupportForStdout, d.printOrWriteToFile, d.atmosConfig, "Affected components and stacks", args.Format, args.OutputFile, affected})
if err != nil {
return DescribeAffectedCmdArgs{}, err
return err
}
processYamlFunctions, err := flags.GetBool("process-functions")
if err != nil {
return DescribeAffectedCmdArgs{}, err
}
if args.Upload {
// Parse the repo URL
gitURL, err := giturl.NewGitURL(repoUrl)
if err != nil {
return err
}
logger, err := l.NewLoggerFromCliConfig(*d.atmosConfig)
if err != nil {
return err
}
apiClient, err := pro.NewAtmosProAPIClientFromEnv(logger)
if err != nil {
return err
}
skip, err := flags.GetStringSlice("skip")
if err != nil {
return DescribeAffectedCmdArgs{}, err
}
req := pro.AffectedStacksUploadRequest{
HeadSHA: headHead.Hash().String(),
BaseSHA: baseHead.Hash().String(),
RepoURL: repoUrl,
RepoName: gitURL.GetRepoName(),
RepoOwner: gitURL.GetOwnerName(),
RepoHost: gitURL.GetHostName(),
Stacks: affected,
}
result := DescribeAffectedCmdArgs{
CLIConfig: atmosConfig,
CloneTargetRef: cloneTargetRef,
Format: format,
IncludeDependents: includeDependents,
IncludeSettings: includeSettings,
IncludeSpaceliftAdminStacks: includeSpaceliftAdminStacks,
Logger: logger,
OutputFile: file,
Ref: ref,
RepoPath: repoPath,
SHA: sha,
SSHKeyPath: sshKeyPath,
SSHKeyPassword: sshKeyPassword,
Verbose: verbose,
Upload: upload,
Stack: stack,
Query: query,
ProcessTemplates: processTemplates,
ProcessYamlFunctions: processYamlFunctions,
Skip: skip,
err = apiClient.UploadAffectedStacks(req)
if err != nil {
return err
}
}
return result, nil
return nil
}
func (d *DescribeAffectedExec) uploadableQuery(args *DescribeAffectedCmdArgs, repoUrl string, headHead, baseHead *plumbing.Reference, affected []schema.Affected) error {
log.Debug("\nAffected components and stacks: \n")
err := viewWithScroll(viewWithScrollProps{d.pageCreator, d.IsTTYSupportForStdout, d.printOrWriteToFile, d.atmosConfig, "Affected components and stacks", args.Format, args.OutputFile, affected})
if err != nil {
return err
}
if args.Upload {
// Parse the repo URL
gitURL, err := giturl.NewGitURL(repoUrl)
if err != nil {
return err
}
logger, err := l.NewLoggerFromCliConfig(*d.atmosConfig)
if err != nil {
return err
}
apiClient, err := pro.NewAtmosProAPIClientFromEnv(logger)
if err != nil {
return err
}
req := pro.AffectedStacksUploadRequest{
HeadSHA: headHead.Hash().String(),
BaseSHA: baseHead.Hash().String(),
RepoURL: repoUrl,
RepoName: gitURL.GetRepoName(),
RepoOwner: gitURL.GetOwnerName(),
RepoHost: gitURL.GetHostName(),
Stacks: affected,
}
err = apiClient.UploadAffectedStacks(req)
if err != nil {
return err
}
}
return nil
}
🧰 Tools
🪛 GitHub Check: golangci-lint

[failure] 185-185:
hugeParam: args is heavy (6120 bytes); consider passing it by pointer


[warning] 192-192:
if args.Upload has complex nested blocks (complexity: 4)

🤖 Prompt for AI Agents (early access)
In internal/exec/describe_affected.go around lines 185 to 223, the
uploadableQuery method receives the affected slice by value, which is a heavy
struct (~6120 bytes). Change the affected parameter to be a pointer to the slice
instead of passing it by value. Also, update all callers of uploadableQuery to
pass the address of the affected slice rather than the slice itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
minor New features that do not break anything needs-cloudposse Needs Cloud Posse assistance size/xl
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants