Go package for programmatically managing CODEOWNERS files. Handles conflict detection and resolution, supports all pattern types (files, directories, globs, extensions), and provides a clean API for automated rule management and ownership queries across repositories.
- ποΈ Add, update, and remove rules
- β Validate rules and accuracy
- π Check who can review files & directories
- π Generate files
go get github.com/MoonMoon1919/mayiCreate a document and add a rule:
package samples
import (
"fmt"
"github.com/MoonMoon1919/mayi"
"github.com/MoonMoon1919/mayi/pkg/owners"
"github.com/MoonMoon1919/mayi/pkg/rules"
"github.com/MoonMoon1919/mayi/pkg/service"
)
func doItYourself() {
co := mayi.NewCodeOwners()
result, err := co.AddRule(
"docs/*",
owners.FromStrings("@MoonMoon1919"),
rules.INCLUDE,
)
if err != nil {
panic(err)
}
fmt.Print(result)
}
func usingServices() {
repo := service.NewFileRepository(mayi.RenderOptions{})
service := service.New(repo)
// create an empty file
if err := service.Init(".github/CODEOWNERS"); err != nil {
panic(err)
}
result, err := service.AddRule(".github/CODEOWNERS", "docs/*", []string{"@MoonMoon1919"}, rules.INCLUDE)
if err != nil {
panic(err)
}
fmt.Print(result)
}The type of rule to add. Mayi current supports file, extension, directory, and glob rules
The pattern you would like to match - uses gitignore like semantics Mayi automatically determines the rule type from the given pattern.
Determes if a pattern should be included or excluded (e.g., don't require review on a specific pattern) Mayi supports 'include' and 'exclude'
Individual or group that will be responsible for reviewing all code that matches a pattern Most providers support an individual or group alias (GitHub/Gitlab handle or group name) or email
A combination of a action, pattern, and owner This is eventually written to the destination (e.g., CODEOWNERS file)
Mayi automatically detects four types of conflicts:
Same pattern with different action OR same action and different owners
README.md @MoonMoon1919
README.md @someoneelse123Same pattern (or pattern subsumes) with same action and owners
README.md @MoonMoon1919
README.md @MoonMoon1919Broader rule makes specific on meaningless
app.log @MoonMoon1919
*.log @MoonMoon1919Rule subsumes another rule with different owners
build/important.txt @MoonMoon1919
build/** @Examplepackage samples
import (
"fmt"
"github.com/MoonMoon1919/mayi"
"github.com/MoonMoon1919/mayi/pkg/files"
)
func parseExisting() {
content := `*.md @MoonMoon1919
build/ @MoonMoon1919
docs/* @MoonMoon1919
`
var codeOwners mayi.CodeOwners
if err := files.Parse(content, &codeOwners); err != nil {
panic(err)
}
fmt.Print(codeOwners)
}package samples
import (
"fmt"
"github.com/MoonMoon1919/mayi"
"github.com/MoonMoon1919/mayi/pkg/service"
)
func reorder() {
repo := service.NewFileRepository(mayi.RenderOptions{})
svc := service.New(repo)
result, err := svc.MoveRule(
".github/CODEOWNERS",
"*.md",
"CONTRIBUTING.md",
mayi.BEFORE,
)
if err != nil {
panic(err)
}
fmt.Print(result)
}The library includes comprehensive test coverage. If you would like to write your own tests, the repository interface is simple to fake.
package samples
import (
"errors"
"fmt"
"strings"
"github.com/MoonMoon1919/mayi"
"github.com/MoonMoon1919/mayi/pkg/files"
"github.com/MoonMoon1919/mayi/pkg/service"
)
// Create a fake repository
type FakeRepository struct {
files map[string]string
}
func (f *FakeRepository) Load(path string, codeOwners *mayi.CodeOwners) error {
content, ok := f.files[path]
if !ok {
return errors.New("file read error")
}
return files.Load(strings.NewReader(content), codeOwners)
}
func (f *FakeRepository) Save(path string, codeOwners *mayi.CodeOwners) error {
content := codeOwners.Render(&mayi.RenderOptions{})
f.files[path] = content
return nil
}
func NewFakeRepository() FakeRepository {
return FakeRepository{
files: make(map[string]string),
}
}
func tester() {
repo := NewFakeRepository()
svc := service.New(&repo)
// Write a test here
fmt.Printf("svc: %v\n", svc)
}See CONTRIBUTING for details.
MIT License - see LICENSE for details.
This work does not represent the interests or technologies of any employer, past or present. It is a personal project only.