managers

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 18, 2026 License: MIT Imports: 12 Imported by: 0

README

managers

A Go library that wraps package manager CLIs behind a common interface. Part of the git-pkgs project.

What it does

Translates generic operations (install, add, remove, list, outdated, update) into the correct CLI commands for each package manager. Define what you want to do once, and the library figures out the right command for npm, bundler, cargo, go, or any other supported manager.

translator := managers.NewTranslator()
// ... register definitions

// Same operation, different managers
cmd, _ := translator.BuildCommand("npm", "add", managers.CommandInput{
    Args: map[string]string{"package": "lodash"},
    Flags: map[string]any{"dev": true},
})
// Result: ["npm", "install", "lodash", "--save-dev"]

cmd, _ = translator.BuildCommand("bundler", "add", managers.CommandInput{
    Args: map[string]string{"package": "rails"},
    Flags: map[string]any{"dev": true},
})
// Result: ["bundle", "add", "rails", "--group", "development"]

Use cases

CI/CD tooling - A unified "install dependencies" step that works regardless of what language the repo uses. Detect the lockfile, run the right install command with the frozen flag.

Monorepo orchestration - Walk subdirectories, detect package managers, run operations in parallel. Update dependencies across many services without maintaining separate configs for each ecosystem.

Security scanners - After finding a vulnerability, automatically generate the update command or apply it. The scanner doesn't need to know npm from cargo.

IDE plugins - "Add package" dialog that works for any project type. User types a package name, plugin detects the manager and runs the right command.

Dependency updaters - Build your own Dependabot. Check for outdated packages, create branches, apply updates, open PRs. See the dependabot-cron example.

git-pkgs integration - Add install, update, add, remove commands to git-pkgs. See git-pkgs use cases.

Audit and compliance - "Show me all outdated packages across all our repos" for a fleet of projects in different languages. Normalize the output format.

Supported package managers

Manager Ecosystem Lockfile
npm npm package-lock.json
pnpm npm pnpm-lock.yaml
yarn npm yarn.lock
bun npm bun.lock
deno deno deno.lock
bundler gem Gemfile.lock
gem gem -
cargo cargo Cargo.lock
gomod go go.sum
pip pypi requirements.txt
uv pypi uv.lock
poetry pypi poetry.lock
conda conda conda-lock.yml
composer packagist composer.lock
mix hex mix.lock
rebar3 hex rebar.lock
pub pub pubspec.lock
cocoapods cocoapods Podfile.lock
swift swift Package.resolved
nuget nuget packages.lock.json
maven maven -
gradle maven gradle.lockfile
sbt maven -
cabal hackage cabal.project.freeze
stack hackage stack.yaml.lock
opam opam opam.locked
luarocks luarocks -
nimble nimble nimble.lock
shards shards shard.lock
cpanm cpan cpanfile.snapshot
lein clojars -
vcpkg vcpkg vcpkg.json
conan conan conan.lock
helm helm Chart.lock
brew homebrew -

Most managers support: install, add, remove, list, outdated, update. Some managers (maven, gradle, sbt, lein) have limited CLI support for add/remove operations.

Installation

go get github.com/git-pkgs/managers

Usage

Building commands
package main

import (
    "fmt"
    "github.com/git-pkgs/managers"
    "github.com/git-pkgs/managers/definitions"
)

func main() {
    // Load embedded definitions
    defs, _ := definitions.LoadEmbedded()

    // Create translator and register definitions
    translator := managers.NewTranslator()
    for _, def := range defs {
        translator.Register(def)
    }

    // Build a command
    cmd, err := translator.BuildCommand("npm", "add", managers.CommandInput{
        Args: map[string]string{
            "package": "lodash",
            "version": "4.17.21",
        },
        Flags: map[string]any{
            "dev": true,
        },
    })
    if err != nil {
        panic(err)
    }

    fmt.Println(cmd) // ["npm", "install", "lodash@4.17.21", "--save-dev"]
}
Command chaining

Some operations require multiple commands. Use BuildCommands to get all of them:

// Go's add operation runs "go get" then "go mod tidy"
cmds, _ := translator.BuildCommands("gomod", "add", managers.CommandInput{
    Args: map[string]string{"package": "github.com/pkg/errors"},
})
// cmds[0] = ["go", "get", "github.com/pkg/errors"]
// cmds[1] = ["go", "mod", "tidy"]
Executing commands

The library builds commands but doesn't execute them by default. Use the Runner interface:

runner := managers.NewExecRunner()
result, err := runner.Run(ctx, cmd, managers.RunOptions{
    Dir: "/path/to/project",
})

Or use MockRunner for testing:

mock := managers.NewMockRunner()
mock.AddResult(managers.Result{
    ExitCode: 0,
    Stdout:   []byte(`{"dependencies": {}}`),
})

Operations

Operation Description
install Install dependencies from lockfile
add Add a new dependency
remove Remove a dependency
list List installed packages
outdated Show packages with available updates
update Update dependencies
Common flags
Flag Description
dev Add as development dependency
frozen Fail if lockfile would change (CI mode)
json Output in JSON format (where supported)
Escape hatch

For manager-specific flags not covered by the common interface, use Extra:

cmd, _ := translator.BuildCommand("npm", "install", managers.CommandInput{
    Flags: map[string]any{"frozen": true},
    Extra: []string{"--legacy-peer-deps"},
})
// Result: ["npm", "install", "--ci", "--legacy-peer-deps"]

How it works

Package managers are defined in YAML files that describe their commands, flags, and arguments. The translator reads these definitions and builds the correct command array for each operation.

# definitions/npm.yaml
name: npm
binary: npm
commands:
  add:
    base: [install]
    args:
      package: {position: 0, required: true}
      version: {suffix: "@"}
    flags:
      dev: [--save-dev]

See CONTRIBUTING.md for details on adding new package managers.

Testing

go test ./...

Tests verify command construction without executing real CLIs. Each test compares the built command array against expected output.

This library is part of a toolkit for building dependency-aware tools:

  • manifests - Parse lockfiles and manifests to read dependency state
  • vers - Version range parsing and comparison
  • git-pkgs - CLI for tracking dependency history

The manifests library reads state (parse lockfiles, extract dependency trees) while managers runs CLI commands that modify state. The CLIs themselves update the lockfiles. Together they cover the full lifecycle:

// Read current state
deps := manifests.Parse("Gemfile.lock")

// Check what's outdated
cmd, _ := managers.BuildCommand("bundler", "outdated", input)
runner.Run(ctx, cmd, opts)

// Apply update (CLI modifies Gemfile.lock)
cmd, _ = managers.BuildCommand("bundler", "update", managers.CommandInput{
    Args: map[string]string{"package": "rails"},
})
runner.Run(ctx, cmd, opts)

// Read new state
deps = manifests.Parse("Gemfile.lock")

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNoCommand            = errors.New("no command provided")
	ErrUnsupportedOperation = errors.New("operation not supported by this manager")
	ErrUnsupportedOption    = errors.New("option not supported by this manager")
)

Functions

func ValidatePackageName

func ValidatePackageName(validatorName, name string) error

Types

type AddOptions

type AddOptions struct {
	Dev       bool
	Optional  bool
	Exact     bool
	Workspace string
}

type Capability

type Capability int
const (
	CapInstall Capability = iota
	CapInstallFrozen
	CapInstallClean
	CapAdd
	CapAddDev
	CapAddOptional
	CapRemove
	CapUpdate
	CapList
	CapOutdated
	CapAudit
	CapWorkspace
	CapJSONOutput
	CapSBOMCycloneDX
	CapSBOMSPDX
)

func CapabilityFromString

func CapabilityFromString(s string) (Capability, bool)

func (Capability) String

func (c Capability) String() string

type CommandInput

type CommandInput struct {
	Args  map[string]string
	Flags map[string]any
	Extra []string // Raw arguments appended to the command (escape hatch)
}

type ConflictBehavior

type ConflictBehavior int
const (
	ConflictError ConflictBehavior = iota
	ConflictUseFirst
	ConflictUseNewest
)

type DetectOptions

type DetectOptions struct {
	RequireCLI    bool
	OnConflict    ConflictBehavior
	SearchParents bool
	Manager       string
}

type Detector

type Detector struct {
	// contains filtered or unexported fields
}

func NewDetector

func NewDetector(translator *Translator, runner Runner) *Detector

func (*Detector) Detect

func (d *Detector) Detect(dir string, opts DetectOptions) (Manager, error)

func (*Detector) DetectVersion

func (d *Detector) DetectVersion(def *definitions.Definition) (string, error)

func (*Detector) Register

func (d *Detector) Register(def *definitions.Definition)

type ErrCLINotFound

type ErrCLINotFound struct {
	Manager string
	Binary  string
	Files   []string
}

func (ErrCLINotFound) Error

func (e ErrCLINotFound) Error() string

type ErrConflictingLockfiles

type ErrConflictingLockfiles struct {
	Dir       string
	Lockfiles []string
}

func (ErrConflictingLockfiles) Error

func (e ErrConflictingLockfiles) Error() string

type ErrInvalidPackageName

type ErrInvalidPackageName struct {
	Name   string
	Reason string
}

func (ErrInvalidPackageName) Error

func (e ErrInvalidPackageName) Error() string

type ErrManifestNotInRoot

type ErrManifestNotInRoot struct {
	Dir      string
	Found    string
	Expected string
}

func (ErrManifestNotInRoot) Error

func (e ErrManifestNotInRoot) Error() string

type ErrMissingArgument

type ErrMissingArgument struct {
	Argument string
}

func (ErrMissingArgument) Error

func (e ErrMissingArgument) Error() string

type ErrNoManifest

type ErrNoManifest struct {
	Dir string
}

func (ErrNoManifest) Error

func (e ErrNoManifest) Error() string

type ErrOrphanedWorkspaceMember

type ErrOrphanedWorkspaceMember struct {
	Dir        string
	MemberFile string
}

func (ErrOrphanedWorkspaceMember) Error

type ErrUnsupportedVersion

type ErrUnsupportedVersion struct {
	Manager string
	Version string
	Nearest string
}

func (ErrUnsupportedVersion) Error

func (e ErrUnsupportedVersion) Error() string

type ExecContext

type ExecContext int
const (
	ContextProject ExecContext = iota
	ContextGlobal
	ContextWorkspace
)

type ExecRunner

type ExecRunner struct{}

func NewExecRunner

func NewExecRunner() *ExecRunner

func (*ExecRunner) Run

func (r *ExecRunner) Run(ctx context.Context, dir string, args ...string) (*Result, error)

type GenericManager

type GenericManager struct {
	// contains filtered or unexported fields
}

func (*GenericManager) Add

func (m *GenericManager) Add(ctx context.Context, pkg string, opts AddOptions) (*Result, error)

func (*GenericManager) Capabilities

func (m *GenericManager) Capabilities() []Capability

func (*GenericManager) Dir

func (m *GenericManager) Dir() string

func (*GenericManager) Ecosystem

func (m *GenericManager) Ecosystem() string

func (*GenericManager) Install

func (m *GenericManager) Install(ctx context.Context, opts InstallOptions) (*Result, error)

func (*GenericManager) List

func (m *GenericManager) List(ctx context.Context) (*Result, error)

func (*GenericManager) Name

func (m *GenericManager) Name() string

func (*GenericManager) Outdated

func (m *GenericManager) Outdated(ctx context.Context) (*Result, error)

func (*GenericManager) Remove

func (m *GenericManager) Remove(ctx context.Context, pkg string) (*Result, error)

func (*GenericManager) Supports

func (m *GenericManager) Supports(cap Capability) bool

func (*GenericManager) Update

func (m *GenericManager) Update(ctx context.Context, pkg string) (*Result, error)

func (*GenericManager) Warnings

func (m *GenericManager) Warnings() []string

type InstallOptions

type InstallOptions struct {
	Frozen     bool
	Clean      bool
	Production bool
}

type Manager

type Manager interface {
	Name() string
	Ecosystem() string

	Install(ctx context.Context, opts InstallOptions) (*Result, error)
	Add(ctx context.Context, pkg string, opts AddOptions) (*Result, error)
	Remove(ctx context.Context, pkg string) (*Result, error)
	List(ctx context.Context) (*Result, error)
	Outdated(ctx context.Context) (*Result, error)
	Update(ctx context.Context, pkg string) (*Result, error)

	Supports(cap Capability) bool
	Capabilities() []Capability
}

type MockRunner

type MockRunner struct {
	Captured [][]string
	Results  []*Result
	Errors   []error
	// contains filtered or unexported fields
}

func NewMockRunner

func NewMockRunner() *MockRunner

func (*MockRunner) LastCaptured

func (m *MockRunner) LastCaptured() []string

func (*MockRunner) Run

func (m *MockRunner) Run(ctx context.Context, dir string, args ...string) (*Result, error)

type Result

type Result struct {
	Command  []string
	Stdout   string
	Stderr   string
	ExitCode int
	Duration time.Duration
	Cwd      string
	Context  ExecContext
}

func (*Result) Success

func (r *Result) Success() bool

type Runner

type Runner interface {
	Run(ctx context.Context, dir string, args ...string) (*Result, error)
}

type Translator

type Translator struct {
	// contains filtered or unexported fields
}

func NewTranslator

func NewTranslator() *Translator

func (*Translator) BuildCommand

func (t *Translator) BuildCommand(managerName, operation string, input CommandInput) ([]string, error)

func (*Translator) BuildCommands

func (t *Translator) BuildCommands(managerName, operation string, input CommandInput) ([][]string, error)

BuildCommands returns all commands for an operation (including "then" chains)

func (*Translator) Definition

func (t *Translator) Definition(name string) (*definitions.Definition, bool)

func (*Translator) Register

func (t *Translator) Register(def *definitions.Definition)

func (*Translator) RegisterValidator

func (t *Translator) RegisterValidator(name string, v *definitions.Validator)

Directories

Path Synopsis
docs

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL