Skip to content

Latest commit

 

History

History
156 lines (121 loc) · 4.92 KB

File metadata and controls

156 lines (121 loc) · 4.92 KB

Contributing to Stacktower

Thanks for your interest in contributing!

Getting Started

git clone https://github.com/matzehuels/stacktower.git
cd stacktower
make install-tools  # Install golangci-lint, goimports, govulncheck
make check          # Run all CI checks locally

Development Workflow

  1. Fork the repository
  2. Create a feature branch (git checkout -b feat/amazing-feature)
  3. Make your changes
  4. Run checks: make check
  5. Commit with Conventional Commits format:
    • feat: add new feature
    • fix: resolve bug
    • docs: update readme
    • refactor: restructure code
    • test: add tests
    • ci: update workflows
  6. Push and open a Pull Request

Code Style

  • Run make fmt before committing
  • Run make lint to check for issues
  • Keep changes focused and minimal

Running Tests

make test       # Unit tests
make e2e        # End-to-end tests
make cover      # Tests with coverage

Architecture

Stacktower follows a clean layered architecture. See the pkg.go.dev documentation for detailed API docs.

internal/cli/          # Command-line interface
pkg/dag/               # Core DAG data structure
├── transform/         # Graph normalization (transitive reduction, subdivision)
└── perm/              # PQ-tree and permutation algorithms
pkg/deps/              # Dependency resolution from registries
├── python/            # Python: PyPI + poetry.lock + requirements.txt
├── rust/              # Rust: crates.io + Cargo.toml
├── javascript/        # JavaScript: npm + package.json
├── ruby/              # Ruby: RubyGems + Gemfile
├── php/               # PHP: Packagist + composer.json
├── java/              # Java: Maven Central + pom.xml
├── golang/            # Go: Go Module Proxy + go.mod
└── metadata/          # GitHub/GitLab enrichment providers
pkg/integrations/      # Registry API clients (npm, pypi, crates, etc.)
pkg/render/tower/      # Tower visualization
├── ordering/          # Barycentric and optimal ordering algorithms
├── layout/            # Block position computation
├── sink/              # Output formats (SVG, JSON, PDF, PNG)
└── styles/            # Visual styles (handdrawn, simple)
pkg/io/                # JSON import/export

Adding a New Language

  1. Create an integration client in pkg/integrations/<registry>/client.go:
type Client struct {
    *integrations.Client
    baseURL string
}

func NewClient(cacheTTL time.Duration) (*Client, error) {
    cache, err := integrations.NewCache(cacheTTL)
    if err != nil {
        return nil, err
    }
    return &Client{
        Client:  integrations.NewClient(cache, nil),
        baseURL: "https://registry.example.com",
    }, nil
}

func (c *Client) FetchPackage(ctx context.Context, name string, refresh bool) (*PackageInfo, error) {
    // Implement caching and fetching
}
  1. Create a language definition in pkg/deps/<lang>/<lang>.go:
var Language = &deps.Language{
    Name:            "mylang",
    DefaultRegistry: "myregistry",
    RegistryAliases: map[string]string{"alias": "myregistry"},
    ManifestTypes:   []string{"my.lock"},
    ManifestAliases: map[string]string{"my.lock": "mylock"},
    NewResolver:     newResolver,
    NewManifest:     newManifest,
    ManifestParsers: manifestParsers,
}
  1. Register in CLI in internal/cli/parse.go

See pkg/deps for detailed documentation.

Adding a Manifest Parser

Implement the ManifestParser interface:

type MyLockParser struct{}

func (p *MyLockParser) Type() string              { return "my.lock" }
func (p *MyLockParser) IncludesTransitive() bool  { return true }
func (p *MyLockParser) Supports(name string) bool { return name == "my.lock" }

func (p *MyLockParser) Parse(path string, opts deps.Options) (*deps.ManifestResult, error) {
    g := dag.New(nil)
    // ... populate nodes and edges
    return &deps.ManifestResult{Graph: g, Type: p.Type(), IncludesTransitive: true}, nil
}

Adding a New Output Format

Output formats are "sinks" in pkg/render/tower/sink/. Each sink takes a layout.Layout and renders it to bytes.

  1. Create a sink file in pkg/render/tower/sink/<format>.go:
func RenderMyFormat(l layout.Layout, opts ...MyFormatOption) ([]byte, error) {
    // Access layout data:
    // - l.FrameWidth, l.FrameHeight: canvas dimensions
    // - l.Blocks: map[string]Block with position data
    // - l.RowOrders: node ordering per row
    return []byte("..."), nil
}
  1. Register in CLI in internal/cli/render.go

See pkg/render/tower/sink for existing implementations.

Questions?

Open an issue — we're happy to help!