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
156 changes: 141 additions & 15 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -457,16 +457,58 @@ Use fixtures in `tests/test-cases/` for integration tests. Each test case should
## Common Development Tasks

### Adding New CLI Command
1. Create `cmd/new_command.go` with Cobra command definition
2. **Create embedded markdown examples** in `cmd/markdown/atmos_command_subcommand_usage.md`
3. **Use `//go:embed` and `utils.PrintfMarkdown()`** for example rendering
4. **Register in `cmd/markdown_help.go`** examples map with suggestion URL
5. **Use markdown formatting** in Short/Long descriptions (supports **bold**, `code`, etc.)
6. Add business logic in appropriate `pkg/` or `internal/exec/` package
7. **Create Docusaurus documentation** in `website/docs/cli/commands/<command>/<subcommand>.mdx`
8. Add tests with fixtures
9. Add integration test in `tests/`
10. **Create pull request following template format**

**Atmos uses the command registry pattern** for organizing built-in commands. Follow these steps:

1. **Create command package directory**: `cmd/[command]/`
2. **Implement CommandProvider interface**:
```go
// cmd/mycommand/mycommand.go
package mycommand

import (
"github.com/spf13/cobra"
"github.com/cloudposse/atmos/cmd/internal"
e "github.com/cloudposse/atmos/internal/exec"
)

var mycommandCmd = &cobra.Command{
Use: "mycommand",
Short: "Brief description",
Long: `Detailed description.`,
RunE: func(cmd *cobra.Command, args []string) error {
return e.ExecuteMyCommand(cmd, args)
},
}

func init() {
internal.Register(&MyCommandProvider{})
}

type MyCommandProvider struct{}

func (m *MyCommandProvider) GetCommand() *cobra.Command {
return mycommandCmd
}

func (m *MyCommandProvider) GetName() string {
return "mycommand"
}

func (m *MyCommandProvider) GetGroup() string {
return "Other Commands" // See docs/developing-atmos-commands.md
}
```
3. **Add blank import to `cmd/root.go`**: `_ "github.com/cloudposse/atmos/cmd/mycommand"`
4. **Implement business logic** in `internal/exec/mycommand.go`
5. **Add tests** in `cmd/mycommand/mycommand_test.go`
6. **Create Docusaurus documentation** in `website/docs/cli/commands/<command>/<subcommand>.mdx`
7. **Build website to verify**: `cd website && npm run build`
8. **Create pull request following template format**

**See:**
- **[docs/developing-atmos-commands.md](docs/developing-atmos-commands.md)** - Complete guide with patterns and examples
- **[docs/prd/command-registry-pattern.md](docs/prd/command-registry-pattern.md)** - Architecture and design decisions

### Documentation Requirements (MANDATORY)
- **All new commands/flags/parameters MUST have Docusaurus documentation**
Expand Down Expand Up @@ -576,14 +618,98 @@ Use fixtures in `tests/test-cases/` for integration tests. Each test case should
- Link to supporting GitHub issues or documentation
- Use `closes #123` if PR closes an issue
```
- **Add changelog entry for feature releases**:
- **Add changelog blog post for feature releases**:
- PRs labeled `minor` or `major` MUST include a blog post in `website/blog/`
- Create a new file: `website/blog/YYYY-MM-DD-feature-name.mdx`
- Follow the format of existing blog posts (frontmatter with slug, title, authors, tags)
- Create a new file: `website/blog/YYYY-MM-DD-feature-name.md`
- Follow the format of existing blog posts (see template below)
- Include `<!--truncate-->` marker after the introduction paragraph
- Explain what the feature does, why it's useful, and provide examples
- Link to relevant documentation using `/cli/commands/` or `/core-concepts/` paths
- The CI workflow will fail and comment on the PR if this is missing

### Blog Post Guidelines (MANDATORY)

Blog posts serve different audiences and must be tagged appropriately:

#### Audience Types

**1. User-Facing Posts** (Features, Improvements, Bug Fixes)
- **Audience**: Teams using Atmos to manage infrastructure
- **Focus**: How the change benefits users, usage examples, migration guides
- **Required tags**: Choose one or more:
- `feature` - New user-facing capabilities
- `enhancement` - Improvements to existing features
- `bugfix` - Important bug fixes that affect users
- **Example tags**: `[feature, terraform, workflows]`

**2. Contributor-Facing Posts** (Refactoring, Internal Changes, Developer Tools)
- **Audience**: Atmos contributors and core developers
- **Focus**: Internal code structure, refactoring, developer experience
- **Required tag**: `contributors`
- **Additional tags**: Describe the technical area
- **Example tags**: `[contributors, atmos-core, refactoring]`

#### Blog Post Template

```markdown
---
slug: descriptive-slug
title: "Clear, Descriptive Title"
authors: [atmos]
tags: [primary-tag, secondary-tag, ...] # See audience types above
---

Brief introduction paragraph explaining what changed and why it matters.

<!--truncate-->

## What Changed

Describe the change with code examples or visuals.

## Why This Matters / Impact on Users

Explain the benefits or reasoning.

## [For User Posts] How to Use It

Provide practical examples and usage instructions.

## [For Contributor Posts] For Atmos Contributors

Clarify this is internal with zero user impact, link to technical docs.

## Get Involved

- Link to relevant documentation
- Encourage discussion/contributions
```

#### Tag Reference

**Primary Audience Tags:**
- `feature` - New user-facing feature
- `enhancement` - Improvement to existing feature
- `bugfix` - Important bug fix
- `contributors` - For Atmos core contributors (internal changes)

**Secondary Technical Tags (for contributor posts):**
- `atmos-core` - Changes to Atmos codebase/internals
- `refactoring` - Code refactoring and restructuring
- `testing` - Test infrastructure improvements
- `ci-cd` - CI/CD pipeline changes
- `developer-experience` - Developer tooling improvements

**Secondary Technical Tags (for user posts):**
- `terraform` - Terraform-specific features
- `helmfile` - Helmfile-specific features
- `workflows` - Workflow features
- `validation` - Validation features
- `performance` - Performance improvements
- `cloud-architecture` - Cloud architecture patterns (user-facing)

**General Tags:**
- `announcements` - Major announcements
- `breaking-changes` - Breaking changes requiring migration

- **Use `no-release` label for documentation-only changes**
- **Ensure all CI checks pass** before requesting review

Expand Down
28 changes: 0 additions & 28 deletions cmd/about.go

This file was deleted.

45 changes: 45 additions & 0 deletions cmd/about/about.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package about

import (
"github.com/spf13/cobra"

"github.com/cloudposse/atmos/cmd/internal"
"github.com/cloudposse/atmos/cmd/markdown"
"github.com/cloudposse/atmos/pkg/utils"
)

// aboutCmd represents the about command.
var aboutCmd = &cobra.Command{
Use: "about",
Short: "Learn about Atmos",
Long: `Display information about Atmos, its features, and benefits.`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
utils.PrintfMarkdown("%s", markdown.AboutMarkdown)
return nil
},
}

func init() {
// Register this command with the registry.
// This happens during package initialization via blank import in cmd/root.go.
internal.Register(&AboutCommandProvider{})
}

// AboutCommandProvider implements the CommandProvider interface.
type AboutCommandProvider struct{}

// GetCommand returns the about command.
func (a *AboutCommandProvider) GetCommand() *cobra.Command {
return aboutCmd
}

// GetName returns the command name.
func (a *AboutCommandProvider) GetName() string {
return "about"
}

// GetGroup returns the command group for help organization.
func (a *AboutCommandProvider) GetGroup() string {
return "Other Commands"
}
52 changes: 52 additions & 0 deletions cmd/about/about_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package about

import (
"bytes"
"io"
"os"
"testing"

"github.com/stretchr/testify/assert"

"github.com/cloudposse/atmos/cmd/markdown"
)

func TestAboutCmd(t *testing.T) {
// Capture stdout since utils.PrintfMarkdown writes directly to os.Stdout.
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w

// Execute the command.
err := aboutCmd.RunE(aboutCmd, []string{})
assert.NoError(t, err, "'atmos about' command should execute without error")

// Close the writer and restore stdout.
err = w.Close()
assert.NoError(t, err, "'atmos about' command should execute without error")

os.Stdout = oldStdout

// Read captured output.
var output bytes.Buffer
_, err = io.Copy(&output, r)
assert.NoError(t, err, "'atmos about' command should execute without error")

// Check if the output contains expected markdown content.
assert.Contains(t, output.String(), markdown.AboutMarkdown, "'atmos about' output should contain information about Atmos")
}

func TestAboutCommandProvider(t *testing.T) {
provider := &AboutCommandProvider{}

// Test GetCommand.
cmd := provider.GetCommand()
assert.NotNil(t, cmd)
assert.Equal(t, "about", cmd.Use)

// Test GetName.
assert.Equal(t, "about", provider.GetName())

// Test GetGroup.
assert.Equal(t, "Other Commands", provider.GetGroup())
}
35 changes: 0 additions & 35 deletions cmd/about_test.go

This file was deleted.

50 changes: 50 additions & 0 deletions cmd/internal/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package internal

import "github.com/spf13/cobra"

// CommandProvider is the interface that built-in command packages implement
// to register themselves with the Atmos command registry.
//
// Commands implementing this interface can be automatically discovered and
// registered with the root command during application initialization.
//
// Example usage:
//
// type AboutCommandProvider struct{}
//
// func (a *AboutCommandProvider) GetCommand() *cobra.Command {
// return aboutCmd
// }
//
// func (a *AboutCommandProvider) GetName() string {
// return "about"
// }
//
// func (a *AboutCommandProvider) GetGroup() string {
// return "Other Commands"
// }
//
// func init() {
// internal.Register(&AboutCommandProvider{})
// }
type CommandProvider interface {
// GetCommand returns the cobra.Command for this provider.
// This is the command that will be registered with the root command.
// For commands with subcommands, this should return the parent command
// with all subcommands already attached.
GetCommand() *cobra.Command

// GetName returns the command name (e.g., "about", "terraform").
// This name is used for registry lookups and must be unique.
GetName() string

// GetGroup returns the command group for help organization.
// Standard groups are:
// - "Core Stack Commands" (terraform, helmfile, workflow, packer)
// - "Stack Introspection" (describe, list, validate)
// - "Configuration Management" (vendor, docs)
// - "Cloud Integration" (aws, atlantis)
// - "Pro Features" (auth, pro)
// - "Other Commands" (about, completion, version, support)
GetGroup() string
}
Loading
Loading