managers

package module
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Feb 12, 2026 License: MIT Imports: 13 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, vendor, resolve) 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.

Source exploration - Open the source code of any installed dependency in your editor. The path operation returns the filesystem location regardless of whether it's in node_modules, site-packages, or a cargo registry.

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, resolve. Some also support vendor and path. 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": {}}`),
})
Policies

PolicyRunner wraps a Runner and applies checks before commands execute. Use this to enforce security policies, license compliance, or package blocklists.

// Create a policy runner that wraps the real executor
runner := managers.NewPolicyRunner(
    managers.NewExecRunner(),
    managers.WithPolicyMode(managers.PolicyEnforce),
)

// Add policies
runner.AddPolicy(managers.PackageBlocklistPolicy{
    Blocked: map[string]string{
        "event-stream": "compromised in 2018",
    },
})

// Commands are checked before execution
result, err := runner.Run(ctx, "/path/to/project", "npm", "install", "event-stream")
// Returns ErrPolicyViolation

The Policy interface:

type Policy interface {
    Name() string
    Check(ctx context.Context, op *PolicyOperation) (*PolicyResult, error)
}

PolicyOperation contains the manager name, operation, packages, flags, and the full command. PolicyResult indicates whether to allow or deny, with an optional reason and warnings.

Three modes control enforcement:

  • PolicyEnforce - block operations that fail checks
  • PolicyWarn - log warnings but allow operations to proceed
  • PolicyDisabled - skip all policy checks

Built-in policies include AllowAllPolicy, DenyAllPolicy, and PackageBlocklistPolicy. Implement the Policy interface for custom checks like vulnerability scanning or license validation.

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
path Get filesystem path to installed package
vendor Copy dependencies into the project directory
resolve Produce dependency graph output from the local CLI
Common flags
Flag Description
dev Add as development dependency
frozen Fail if lockfile would change (CI mode)
json Output in JSON format (where supported)
Getting package paths

The path operation returns the filesystem path to an installed package, useful for source exploration or editor integration:

manager, _ := managers.Detect("/path/to/project")
result, _ := manager.Path(ctx, "lodash")
fmt.Println(result.Path) // "/path/to/project/node_modules/lodash"

The library handles extracting clean paths from various output formats (JSON, line-based, regex patterns). For managers with predictable locations (yarn, mix, shards), paths are computed from templates.

Managers with path support: npm, pnpm, yarn, bun, bundler, gem, pip, uv, poetry, conda, gomod, cargo, composer, brew, deno, nimble, opam, luarocks, conan, mix, shards, rebar3

Vendoring dependencies

The vendor operation copies dependencies into the project directory for offline builds or source inspection:

manager, _ := managers.Detect("/path/to/project")
result, _ := manager.Vendor(ctx)

Managers with vendor support: gomod, cargo, bundler, pip, rebar3

Resolving dependency graphs

The resolve operation runs the package manager's dependency graph command and returns raw output. Some managers produce JSON (npm, cargo, pip), others produce text trees (go, maven, poetry). Parsing and normalization is left to the caller.

manager, _ := managers.Detect("/path/to/project")
result, _ := manager.Resolve(ctx)
fmt.Println(result.Stdout) // raw CLI output (JSON tree, text tree, etc.)

Managers with resolve support: npm, pnpm, yarn, bun, bundler, cargo, gomod, pip, uv, poetry, conda, composer, maven, gradle, lein, swift, deno, stack, pub, mix, rebar3, nuget, conan, helm

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"]

Configuration files

This library builds and executes CLI commands. It doesn't read or modify package manager configuration files. When commands run, they inherit the environment and respect native config files:

  • npm/yarn/pnpm: .npmrc, ~/.npmrc
  • pip/poetry/uv: pip.conf, .pypirc, pyproject.toml
  • bundler: ~/.bundle/config, .bundle/config
  • cargo: ~/.cargo/config.toml, .cargo/config.toml
  • composer: auth.json, config.json
  • go: GOPROXY, GOPRIVATE environment variables

Private registries, proxy servers (like Artifactory), scoped registries, and credentials all work as configured for the underlying tool. The library just builds the right command; the CLI handles authentication and registry resolution.

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 ExtractPath added in v0.4.0

func ExtractPath(output string, extract *definitions.Extract, pkg string) (string, error)

func ValidatePackageName

func ValidatePackageName(validatorName, name string) error

Types

type AddOptions

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

type AllowAllPolicy added in v0.5.0

type AllowAllPolicy struct{}

AllowAllPolicy is a no-op policy that allows all operations. Useful as a placeholder or for testing.

func (AllowAllPolicy) Check added in v0.5.0

func (AllowAllPolicy) Name added in v0.5.0

func (AllowAllPolicy) Name() string

type Capability

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

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 DenyAllPolicy added in v0.5.0

type DenyAllPolicy struct {
	Reason string
}

DenyAllPolicy is a policy that denies all operations. Useful for testing or as a circuit breaker.

func (DenyAllPolicy) Check added in v0.5.0

func (DenyAllPolicy) Name added in v0.5.0

func (DenyAllPolicy) Name() string

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 ErrPolicyCheck added in v0.5.0

type ErrPolicyCheck struct {
	Policy string
	Err    error
}

func (ErrPolicyCheck) Error added in v0.5.0

func (e ErrPolicyCheck) Error() string

func (ErrPolicyCheck) Unwrap added in v0.5.0

func (e ErrPolicyCheck) Unwrap() error

type ErrPolicyViolation added in v0.5.0

type ErrPolicyViolation struct {
	Policy  string
	Reason  string
	Command []string
}

func (ErrPolicyViolation) Error added in v0.5.0

func (e ErrPolicyViolation) Error() string

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) Path added in v0.4.0

func (m *GenericManager) Path(ctx context.Context, pkg string) (*PathResult, error)

func (*GenericManager) Remove

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

func (*GenericManager) Resolve added in v0.6.0

func (m *GenericManager) Resolve(ctx context.Context) (*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) Vendor added in v0.5.0

func (m *GenericManager) Vendor(ctx context.Context) (*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)
	Path(ctx context.Context, pkg string) (*PathResult, error)
	Vendor(ctx context.Context) (*Result, error)
	Resolve(ctx context.Context) (*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 PackageBlocklistPolicy added in v0.5.0

type PackageBlocklistPolicy struct {
	Blocked map[string]string // package name -> reason
}

PackageBlocklistPolicy denies operations on specific packages.

func (PackageBlocklistPolicy) Check added in v0.5.0

func (PackageBlocklistPolicy) Name added in v0.5.0

type PathResult added in v0.4.0

type PathResult struct {
	Path   string  // extracted path to the package
	Result *Result // underlying command result
}

type Policy added in v0.5.0

type Policy interface {
	// Name returns a unique identifier for this policy.
	Name() string

	// Check evaluates the policy against the given operation.
	// Returns a PolicyResult indicating whether the operation should proceed.
	Check(ctx context.Context, op *PolicyOperation) (*PolicyResult, error)
}

Policy defines an interface for checks that run before package operations. Policies can inspect the operation details and either allow or deny execution.

type PolicyHandler added in v0.5.0

type PolicyHandler interface {
	OnPolicyResult(op *PolicyOperation, policy Policy, result *PolicyResult)
}

PolicyHandler receives policy check results for logging or custom handling.

type PolicyMode added in v0.5.0

type PolicyMode int

PolicyMode determines how policy violations are handled.

const (
	// PolicyEnforce blocks operations that fail policy checks.
	PolicyEnforce PolicyMode = iota

	// PolicyWarn logs warnings but allows operations to proceed.
	PolicyWarn

	// PolicyDisabled skips all policy checks.
	PolicyDisabled
)

func (PolicyMode) String added in v0.5.0

func (m PolicyMode) String() string

type PolicyOperation added in v0.5.0

type PolicyOperation struct {
	// Manager is the package manager name (e.g., "npm", "bundler").
	Manager string

	// Operation is the command being run (e.g., "add", "install", "update").
	Operation string

	// Packages is the list of packages being operated on.
	// Empty for operations like "install" that don't target specific packages.
	Packages []string

	// Args contains the raw arguments passed to the command.
	Args map[string]string

	// Flags contains the flags passed to the command.
	Flags map[string]any

	// WorkingDir is the directory where the operation will run.
	WorkingDir string

	// Command is the fully constructed command that will be executed.
	Command []string
}

PolicyOperation contains details about the operation being checked.

type PolicyResult added in v0.5.0

type PolicyResult struct {
	// Allowed indicates whether the operation should proceed.
	Allowed bool

	// Reason explains why the operation was allowed or denied.
	Reason string

	// Warnings contains non-blocking issues that should be reported.
	Warnings []string

	// Metadata contains policy-specific data for programmatic access.
	Metadata map[string]any
}

PolicyResult contains the outcome of a policy check.

type PolicyRunner added in v0.5.0

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

PolicyRunner wraps a Runner and applies policies before execution.

func NewPolicyRunner added in v0.5.0

func NewPolicyRunner(inner Runner, opts ...PolicyRunnerOption) *PolicyRunner

NewPolicyRunner creates a Runner that applies policies before execution.

func (*PolicyRunner) AddPolicy added in v0.5.0

func (pr *PolicyRunner) AddPolicy(p Policy)

AddPolicy registers a policy to be checked before operations.

func (*PolicyRunner) Run added in v0.5.0

func (pr *PolicyRunner) Run(ctx context.Context, dir string, args ...string) (*Result, error)

Run executes the command after checking all registered policies.

func (*PolicyRunner) RunWithContext added in v0.5.0

func (pr *PolicyRunner) RunWithContext(ctx context.Context, op *PolicyOperation) (*Result, error)

RunWithContext executes the command with additional operation context. Use this when you have more information about the operation than just the command.

type PolicyRunnerOption added in v0.5.0

type PolicyRunnerOption func(*PolicyRunner)

PolicyRunnerOption configures a PolicyRunner.

func WithPolicies added in v0.5.0

func WithPolicies(policies ...Policy) PolicyRunnerOption

WithPolicies adds policies to the runner.

func WithPolicyHandler added in v0.5.0

func WithPolicyHandler(handler PolicyHandler) PolicyRunnerOption

WithPolicyHandler sets a handler for policy results.

func WithPolicyMode added in v0.5.0

func WithPolicyMode(mode PolicyMode) PolicyRunnerOption

WithPolicyMode sets the enforcement mode.

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