First off, thank you for considering contributing to dtvem! It's people like you that make dtvem such a great tool.
This project and everyone participating in it is governed by the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior by opening a GitHub issue or contacting the project maintainers.
Before creating bug reports, please check the existing issues to avoid duplicates. When creating a bug report, include as many details as possible:
- Use a clear and descriptive title
- Describe the exact steps to reproduce the problem
- Provide specific examples (commands run, expected vs actual behavior)
- Describe the environment (OS, Go version, dtvem version)
- Include logs or error messages if applicable
Enhancement suggestions are tracked as GitHub issues. When creating an enhancement suggestion:
- Use a clear and descriptive title
- Provide a detailed description of the suggested enhancement
- Explain why this enhancement would be useful to most dtvem users
- List any alternatives you've considered
- Fork the repo and create your branch from
main - If you've added code that should be tested, add tests
- Ensure your code follows the existing style
- Use a conventional commit format for your PR title (e.g.,
feat(node): add version caching) - Submit your pull request!
Note: We use squash merges, so your PR title becomes the commit message on main. Make sure it follows the Conventional Commits specification.
- Go 1.23 or higher
- Git
- A code editor (VS Code, GoLand, etc.)
# Clone your fork
git clone https://github.com/YOUR_USERNAME/dtvem.git
cd dtvem
# Build the main executable
go build -o dist/dtvem.exe ./src
# Build the shim executable
go build -o dist/dtvem-shim.exe ./src/cmd/shim
# Run the executable
./dist/dtvem.exe help# Run all tests (when implemented)
go test ./...
# Run tests with coverage
go test -cover ./...
# Run tests for a specific package
go test ./src/internal/runtime/This project follows the Google Go Style Guide as documented in .claude/GO_STYLEGUIDE.md.
Required before submitting:
- Run
golangci-lint runto check formatting, style, and correctness (includes gofmt, govet, and 40+ linters) - Ensure
go mod tidyhas been run
Code quality tools:
- golangci-lint v1.62+: Comprehensive linting (includes gofmt, goimports, govet, staticcheck, revive, and 40+ more linters)
- All checks run automatically on every PR
macOS:
# Using Homebrew (recommended)
brew install golangci-lint
# Update to latest version
brew upgrade golangci-lintLinux:
# Using Homebrew
brew install golangci-lint
# Or using install script
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
# Or using binary (replace VERSION and OS/ARCH)
curl -sSfL https://github.com/golangci/golangci-lint/releases/download/v1.55.2/golangci-lint-1.55.2-linux-amd64.tar.gz | tar xz -C $(go env GOPATH)/bin --strip-components=1 golangci-lint-1.55.2-linux-amd64/golangci-lintWindows:
# Using WinGet (recommended - pre-installed on Windows 10/11)
winget install golangci.golangci-lint
# Update to latest version
winget upgrade golangci.golangci-lint
# Using Scoop
scoop install golangci-lint
# Using Chocolatey
choco install golangci-lint
# Or download binary from releases page
# https://github.com/golangci/golangci-lint/releasesAny Platform (using Go):
# Install latest version (v1.62+ required for Go 1.23)
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# Or install specific version
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
# Verify installation (should be v1.62+)
golangci-lint --versionNote: If using go install, make sure $(go env GOPATH)/bin is in your PATH.
# Run all linters (what CI runs) - includes gofmt, govet, and 40+ more
golangci-lint run
# Auto-fix issues where possible (including formatting)
golangci-lint run --fix
# Run all checks before committing
golangci-lint run && go mod tidy && go test ./src/...
# Run specific linters only
golangci-lint run --disable-all --enable=errcheck,govet,gofmtNote: golangci-lint automatically includes gofmt, goimports, and govet, so you don't need to run them separately!
src/
├── cmd/ # CLI commands (one file per command)
├── internal/ # Internal packages (not importable by other projects)
│ ├── config/ # Configuration and path management
│ ├── runtime/ # Core plugin system
│ └── shim/ # Shim management
└── runtimes/ # Runtime provider implementations
├── node/
├── python/
└── ruby/
This project follows the Conventional Commits specification. All commits must follow this format:
<type>(<scope>): <subject>
Examples:
feat(python): add Python 3.12 detection support
fix(node): correct version detection on Windows
docs: update installation instructions
test(migrate): add tests for package preservation
Common types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
Common scopes: node, python, cli, install, migrate, shim, config, path, ui, test, docs
For detailed guidelines, see the Conventional Commits specification.
Note: PR titles are automatically validated for conventional commit compliance. Non-conforming titles will fail CI checks. Since we use squash merges, your PR title becomes the final commit message.
To add support for a new runtime (e.g., Ruby):
- Create
src/runtimes/ruby/provider.go - Implement the
runtime.Providerinterface (which embedsruntime.ShimProvider):type Provider struct {} // ShimProvider methods (used by the shim binary) func (p *Provider) Name() string { return "ruby" } func (p *Provider) DisplayName() string { return "Ruby" } func (p *Provider) Shims() []string { return []string{"ruby", "gem", "bundle"} } func (p *Provider) ExecutablePath(version string) (string, error) { ... } func (p *Provider) IsInstalled(version string) (bool, error) { ... } func (p *Provider) ShouldReshimAfter(shimName string, args []string) bool { ... } // Provider-only methods (used by CLI, can include net/http) func (p *Provider) Install(version string) error { ... } func (p *Provider) ListAvailable() ([]runtime.AvailableVersion, error) { ... } // ... implement remaining methods
- Register the provider in the
init()function:func init() { runtime.Register(NewProvider()) }
- Import the provider in both files:
src/main.go(for the CLI)src/cmd/shim/main.go(for the shim)
_ "github.com/dtvem/dtvem/src/runtimes/ruby"
- Add tests using the provider contract harness
- Update README.md to reflect the new runtime support
The Provider interface embeds ShimProvider. This separation exists for performance:
- ShimProvider: Minimal interface used by the shim binary. Methods here should NOT import
net/httpor other heavy dependencies. - Provider: Full interface for CLI operations. Can use any dependencies.
Go's linker eliminates unused code, so keeping ShimProvider methods dependency-free results in a smaller, faster shim binary (~4.5MB vs ~10MB).
When writing tests:
- Place test files next to the code they test (
provider_test.gonext toprovider.go) - Use table-driven tests where appropriate
- Test both success and error cases
- Mock external dependencies (network calls, file system)
- Use descriptive test names:
TestNodeProvider_DetectInstalled_WithNVM
Example test structure:
func TestNodeProvider_DetectInstalled(t *testing.T) {
tests := []struct {
name string
setup func()
want int
wantErr bool
}{
{
name: "detects system node",
setup: func() { /* setup test environment */ },
want: 1,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// test implementation
})
}
}- Update README.md when adding new features
- Close related GitHub Issues when completing work
- Add inline comments for complex logic
- Update command help text when modifying commands
- Add examples for new functionality
Feel free to open an issue with the label question if you need help or clarification on anything!
Contributors will be recognized in the README.md and release notes. Thank you for making dtvem better!
Happy Contributing! 🚀