This guide provides detailed information for developers who want to contribute to or understand the MayR Labs CLI codebase.
- Project Architecture
- Command Structure
- Adding New Commands
- Testing Strategy
- Development Workflow
- Common Tasks
.
├── .github/
│ ├── agents/ # Copilot agent configuration
│ └── workflows/ # CI/CD workflows
├── cmd/ # CLI entry point and root command
│ └── root.go # Cobra root command setup
├── internal/ # Private application code
│ ├── commands/ # Command implementations
│ │ ├── changelog.go # Changelog commands
│ │ ├── ci.go # CI/CD generation
│ │ ├── env.go # Environment file management
│ │ ├── flutter.go # Flutter-specific commands
│ │ ├── format.go # Code formatting
│ │ ├── general.go # General utility commands
│ │ ├── git.go # Git operations
│ │ ├── js.go # JavaScript commands
│ │ ├── license.go # License generation
│ │ ├── php.go # PHP commands
│ │ ├── quote.go # Motivational quotes
│ │ └── utils.go # Utility commands (hash, uuid, password)
│ └── utils/ # Shared utility functions
│ └── helpers.go # Common helper functions
├── main.go # Application entry point
├── go.mod # Go module definition
├── Makefile # Build automation
└── README.md # User documentation
- cobra (
github.com/spf13/cobra): CLI framework for building commands - uuid (
github.com/google/uuid): UUID generation
All commands follow the Cobra command pattern:
var MyCmd = &cobra.Command{
Use: "command-name [args]",
Short: "Brief description",
Long: "Detailed description of what this command does",
Args: cobra.ExactArgs(1), // Argument validation
RunE: func(cmd *cobra.Command, args []string) error {
// Command implementation
return nil
},
}
func init() {
// Add flags
MyCmd.Flags().StringP("flag", "f", "default", "Flag description")
}- General Commands: Standalone utilities (hash, uuid, password, etc.)
- Group Commands: Parent commands with subcommands (git, env, changelog, etc.)
Create a new file in internal/commands/:
// internal/commands/myfeature.go
package commands
import (
"fmt"
"github.com/spf13/cobra"
)
var MyFeatureCmd = &cobra.Command{
Use: "myfeature",
Short: "Short description",
Long: "Long description",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("My feature works!")
return nil
},
}Add it to cmd/root.go:
func init() {
// ... existing commands ...
rootCmd.AddCommand(commands.MyFeatureCmd)
}Create internal/commands/myfeature_test.go:
package commands
import (
"testing"
)
func TestMyFeature(t *testing.T) {
// Test implementation
}Update the README.md with the new command documentation.
- Test individual functions in isolation
- Use table-driven tests for multiple scenarios
- Mock external dependencies when needed
Example:
func TestHashString(t *testing.T) {
tests := []struct {
name string
input string
algorithm string
want string
wantErr bool
}{
{"MD5", "test", "md5", "098f6bcd4621d373cade4e832627b4f6", false},
{"SHA256", "test", "sha256", "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := HashString(tt.input, tt.algorithm)
if (err != nil) != tt.wantErr {
t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
}
if got != tt.want {
t.Errorf("got %v, want %v", got, tt.want)
}
})
}
}Integration tests should go in the tests/ directory and test command execution end-to-end.
# Run all tests
make test
# Run with coverage
make coverage
# Run specific package tests
go test -v ./internal/commands/# Clone the repository
git clone https://github.com/MayR-Labs/mayrlabs-go.git
cd mayrlabs-go
# Install dependencies
make deps# Create a feature branch
git checkout -b feature/my-feature
# Make your changes
# ...
# Format code
make fmt
# Run linter
make lint
# Run tests
make test# Build the binary
make build
# Test the binary
./mayrlabs --help
./mayrlabs your-command# Commit your changes
git add .
git commit -m "Add feature: description"
# Push to your fork
git push origin feature/my-feature
# Create a pull request on GitHub# Build for current platform
make build
# Build for all platforms
make build-all
# Install to GOPATH
make install# Run tests
make test
# Run tests with race detection
go test -race ./...
# Run specific test
go test -v -run TestHashString ./internal/utils/# Format code
make fmt
# Run linter
make lint
# Run go vet
make vet# Build with debug symbols
go build -gcflags="all=-N -l" -o mayrlabs main.go
# Use delve for debugging
dlv debug main.go -- your-command args# Add a new dependency
go get github.com/some/package
# Update dependencies
go get -u ./...
# Tidy up go.mod
go mod tidy- Commands: Use verb-noun format (e.g.,
CreateKeystore,ClearDNS) - Functions: Use camelCase, exported functions start with uppercase
- Variables: Use descriptive names, avoid abbreviations unless common
- Files: Use snake_case for filenames (e.g.,
editor_config.go)
// Good: Return errors
func DoSomething() error {
if err := someOperation(); err != nil {
return fmt.Errorf("failed to do something: %w", err)
}
return nil
}
// Bad: Panic
func DoSomething() {
if err := someOperation(); err != nil {
panic(err) // Don't do this
}
}// Good: Document exported functions
// HashString generates a hash of the input string using the specified algorithm.
// Supported algorithms are: md5, sha1, sha256.
func HashString(input, algorithm string) (string, error) {
// ...
}
// Good: Explain complex logic
// Calculate the checksum using a rolling hash algorithm
// to detect duplicate content efficiently
checksum := calculateRollingHash(data)- Avoid unnecessary allocations in hot paths
- Use
strings.Builderfor string concatenation in loops - Close file handles and resources properly
- Use buffered I/O for large files
- Never log sensitive information (passwords, tokens, keys)
- Validate all user inputs
- Use secure random generation for passwords/tokens
- Be careful with file operations (path traversal attacks)
- Don't execute arbitrary shell commands without validation
- Check existing issues on GitHub
- Ask questions in discussions
- Contact maintainers
Happy coding! 🚀