Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ screenshots:
url: "docs/demo.gif"

description: |-
Atmos is a framework for orchestrating and operating infrastructure workflows across multiple cloud and DevOps toolchains.

## Use Atmos to break your architecture into reusable [Components](https://atmos.tools/core-concepts/components) that you implement using [Terraform "root modules"](https://atmos.tools/core-concepts/components/terraform). Then tie everything together using [Stack](https://atmos.tools/core-concepts/stacks) configurations defined in YAML.

Atmos can change how you think about the Terraform code you write to build your infrastructure. Atmos is a framework that simplifies complex cloud architectures and DevOps workflows into intuitive CLI commands.
Expand Down
25 changes: 20 additions & 5 deletions cmd/help_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ func calculateMaxCommandWidth(commands []*cobra.Command) int {
}

// formatCommandLine formats a single command line with proper padding and styling.
func formatCommandLine(ctx *helpRenderContext, cmd *cobra.Command, maxWidth int) {
func formatCommandLine(ctx *helpRenderContext, cmd *cobra.Command, maxWidth int, mdRenderer *markdown.Renderer) {
cmdName := cmd.Name()
cmdTypePlain := ""
cmdTypeStyled := ""
Expand All @@ -545,20 +545,29 @@ func formatCommandLine(ctx *helpRenderContext, cmd *cobra.Command, maxWidth int)
fmt.Fprint(ctx.writer, strings.Repeat(spaceChar, padding))
fmt.Fprint(ctx.writer, strings.Repeat(spaceChar, commandDescriptionSpacing))

// Wrap and render description with proper indentation for continuation lines
// Wrap plain text first, then render markdown without additional wrapping.
// This matches the approach used by flag description rendering.
wrapped := wordwrap.String(cmd.Short, descWidth)

if mdRenderer != nil {
rendered, err := mdRenderer.RenderWithoutWordWrap(wrapped)
if err == nil {
wrapped = strings.TrimSpace(rendered)
}
}

lines := strings.Split(wrapped, "\n")

// Print first line (already positioned)
if len(lines) > 0 {
fmt.Fprintf(ctx.writer, "%s\n", ctx.styles.commandName.Render(lines[0]))
fmt.Fprintf(ctx.writer, "%s\n", ctx.styles.commandDesc.Render(lines[0]))
}

// Print continuation lines with proper indentation
if len(lines) > 1 {
indentStr := strings.Repeat(spaceChar, descColStart)
for i := 1; i < len(lines); i++ {
fmt.Fprintf(ctx.writer, "%s%s\n", indentStr, ctx.styles.commandName.Render(lines[i]))
fmt.Fprintf(ctx.writer, "%s%s\n", indentStr, ctx.styles.commandDesc.Render(lines[i]))
}
}
}
Expand All @@ -576,11 +585,17 @@ func printAvailableCommands(ctx *helpRenderContext, cmd *cobra.Command) {

maxCmdWidth := calculateMaxCommandWidth(cmd.Commands())

// Create markdown renderer for command descriptions (same approach as flag rendering).
var mdRenderer *markdown.Renderer
if ctx.atmosConfig != nil {
mdRenderer, _ = markdown.NewTerminalMarkdownRenderer(*ctx.atmosConfig)
}

for _, c := range cmd.Commands() {
if !isCommandAvailable(c) || isConfigAlias(c) {
continue
}
formatCommandLine(ctx, c, maxCmdWidth)
formatCommandLine(ctx, c, maxCmdWidth, mdRenderer)
}
fmt.Fprintln(ctx.writer)
}
Expand Down
135 changes: 134 additions & 1 deletion cmd/help_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/spf13/viper"

"github.com/cloudposse/atmos/pkg/schema"
"github.com/cloudposse/atmos/pkg/ui/markdown"
)

func TestIsTruthy(t *testing.T) {
Expand Down Expand Up @@ -1059,7 +1060,7 @@ func TestFormatCommandLine(t *testing.T) {
styles: &styles,
}

formatCommandLine(ctx, tt.cmd, tt.maxWidth)
formatCommandLine(ctx, tt.cmd, tt.maxWidth, nil)

output := buf.String()
for _, expected := range tt.contains {
Expand Down Expand Up @@ -1231,3 +1232,135 @@ func TestPrintFlags(t *testing.T) {
})
}
}

func TestFormatCommandLineWithMarkdownRenderer(t *testing.T) {
tests := []struct {
name string
cmd *cobra.Command
maxWidth int
contains []string
}{
{
name: "simple command with markdown renderer",
cmd: &cobra.Command{
Use: "apply",
Short: "Apply changes to infrastructure",
},
maxWidth: 20,
contains: []string{"apply", "Apply changes"},
},
{
name: "command with markdown in description",
cmd: &cobra.Command{
Use: "validate",
Short: "Validate `stack` configuration files",
},
maxWidth: 20,
contains: []string{"validate", "Validate"},
},
{
name: "command with bold markdown",
cmd: &cobra.Command{
Use: "plan",
Short: "Plan **infrastructure** changes",
},
maxWidth: 15,
contains: []string{"plan", "Plan"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var buf bytes.Buffer
renderer := lipgloss.NewRenderer(&buf)
styles := createHelpStyles(renderer)

ctx := &helpRenderContext{
writer: &buf,
styles: &styles,
}

// Create a markdown renderer.
atmosConfig := schema.AtmosConfiguration{}
mdRenderer, err := markdown.NewTerminalMarkdownRenderer(atmosConfig)
if err != nil {
t.Fatalf("Failed to create markdown renderer: %v", err)
}

formatCommandLine(ctx, tt.cmd, tt.maxWidth, mdRenderer)

output := buf.String()
for _, expected := range tt.contains {
if !strings.Contains(output, expected) {
t.Errorf("Expected output to contain %q, got: %q", expected, output)
}
}
})
}
}

func TestPrintAvailableCommandsWithAtmosConfig(t *testing.T) {
tests := []struct {
name string
subcommands []*cobra.Command
shouldPrint bool
contains []string
}{
{
name: "with available subcommands and atmosConfig",
subcommands: []*cobra.Command{
{Use: "apply", Short: "Apply changes", Run: func(cmd *cobra.Command, args []string) {}},
{Use: "plan", Short: "Plan changes", Run: func(cmd *cobra.Command, args []string) {}},
},
shouldPrint: true,
contains: []string{"AVAILABLE COMMANDS", "apply", "plan"},
},
{
name: "with markdown in command descriptions",
subcommands: []*cobra.Command{
{Use: "validate", Short: "Validate `stack` files", Run: func(cmd *cobra.Command, args []string) {}},
{Use: "describe", Short: "Describe **components**", Run: func(cmd *cobra.Command, args []string) {}},
},
shouldPrint: true,
contains: []string{"AVAILABLE COMMANDS", "validate", "describe"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var buf bytes.Buffer
renderer := lipgloss.NewRenderer(&buf)
styles := createHelpStyles(renderer)

cmd := &cobra.Command{
Use: "test",
Short: "Test command",
}

for _, subcmd := range tt.subcommands {
cmd.AddCommand(subcmd)
}

// Create context with atmosConfig to trigger markdown renderer creation.
atmosConfig := &schema.AtmosConfiguration{}
ctx := &helpRenderContext{
writer: &buf,
styles: &styles,
atmosConfig: atmosConfig,
}

printAvailableCommands(ctx, cmd)

output := buf.String()
if tt.shouldPrint && output == "" {
t.Error("Expected available commands to be printed")
}

for _, expected := range tt.contains {
if !strings.Contains(output, expected) {
t.Errorf("Expected output to contain %q, got: %q", expected, output)
}
}
})
}
}
4 changes: 2 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,8 @@ func processChdirFlag(cmd *cobra.Command) error {
// RootCmd represents the base command when called without any subcommands.
var RootCmd = &cobra.Command{
Use: "atmos",
Short: "Universal Tool for DevOps and Cloud Automation",
Long: `Atmos is a universal tool for DevOps and cloud automation used for provisioning, managing and orchestrating workflows across various toolchains`,
Short: "Framework for Infrastructure Orchestration",
Long: `Atmos is a framework for orchestrating and operating infrastructure workflows across multiple cloud and DevOps toolchains.`,
// Note: FParseErrWhitelist is NOT set on RootCmd to allow proper flag validation.
// Individual commands that need to pass through flags (terraform, helmfile, packer)
// set FParseErrWhitelist{UnknownFlags: true} explicitly.
Expand Down
4 changes: 2 additions & 2 deletions tests/snapshots/TestCLICommands_atmos_--help.stdout.golden

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading