Documentation
¶
Overview ¶
Package deps provides dependency resolution from package registries and manifest files.
Overview ¶
Package deps is the core abstraction layer for fetching dependency trees from multiple sources:
- Package registries: PyPI, npm, crates.io, RubyGems, Packagist, Maven, Go Proxy
- Manifest files: requirements.txt, package.json, Cargo.toml, poetry.lock, etc.
It provides a concurrent resolver that crawls dependencies in parallel while respecting depth and node limits. The resulting dependency graphs are returned as dag.DAG structures suitable for visualization and analysis.
This package powers the `stacktower parse` command and is language-agnostic, delegating language-specific details to subpackages (python, rust, javascript, etc.).
Architecture ¶
The dependency resolution system has three layers:
- Registry integrations ([integrations]): HTTP clients for each registry API
- Language definitions (this package): Language values that map registries and manifests
- CLI (internal/cli): User commands like `stacktower parse`
Resolving Dependencies ¶
Use a Language's resolver to fetch a complete dependency tree from a registry:
import "github.com/stacktower-io/stacktower/pkg/core/deps/python"
resolver, _ := python.Language.Resolver()
g, _ := resolver.Resolve(ctx, "fastapi", deps.Options{
MaxDepth: 10,
MaxNodes: 1000,
})
The resolver crawls dependencies concurrently:
- Fetches the root package from the registry
- Recursively fetches dependencies up to MaxDepth levels
- Builds a dag.DAG with nodes for packages and edges for dependencies
- Optionally enriches nodes with metadata from MetadataProvider sources
Options ¶
Options configures resolution behavior. All fields are optional and have sensible defaults via Options.WithDefaults:
- MaxDepth: Maximum dependency depth (default 50)
- MaxNodes: Maximum packages to fetch (default 5000)
- CacheTTL: HTTP cache duration (default 24h)
- Refresh: Bypass cache to force fresh data
- MetadataProviders: External enrichment sources (e.g., GitHub, GitLab)
- Logger: Progress and error callback (must be goroutine-safe)
Package Data ¶
Each resolved package is represented by a Package struct containing:
- Name, Version: Package identity from the registry
- Dependencies: Direct dependency names (recursively fetched)
- Description, License, Author: Registry metadata (availability varies)
- Repository, HomePage: Source code and documentation URLs
- Downloads: Popularity metric (when available from the registry)
Use Package.Metadata to convert package fields to a map suitable for dag.Node metadata. Use Package.Ref to create a PackageRef for metadata provider lookups.
Manifest Parsing ¶
For local projects, parse dependency information directly from manifest files:
import "github.com/stacktower-io/stacktower/pkg/core/deps/python"
parsers := python.Language.ManifestParsers(nil)
parser, _ := deps.DetectManifest("poetry.lock", parsers...)
result, _ := parser.Parse("poetry.lock", opts)
g := result.Graph
Manifest parsers implement ManifestParser and vary in completeness:
- Direct dependencies only: requirements.txt, package.json (base)
- Full transitive closure: poetry.lock, Cargo.lock, package-lock.json
Use [ManifestParser.IncludesTransitive] to check if additional resolution is needed. Use DetectManifest to find the right parser for a file.
Metadata Enrichment ¶
MetadataProvider implementations add supplementary data from external sources:
import "github.com/stacktower-io/stacktower/pkg/core/deps/metadata"
providers := []deps.MetadataProvider{
metadata.NewGitHub(cache, token, 24*time.Hour),
}
opts := deps.Options{MetadataProviders: providers}
The GitHub provider (see [metadata.GitHub]) adds:
- repo_stars: GitHub star count
- repo_owner, repo_maintainers: Maintainer information
- repo_last_commit: Last commit timestamp
- repo_archived: Whether the repository is archived
These fields power Nebraska ranking and brittle package detection.
Supported Languages ¶
Each language has a subpackage exporting a Language definition:
- [python]: PyPI registry, poetry.lock, requirements.txt, pyproject.toml
- [rust]: crates.io registry, Cargo.toml, Cargo.lock
- [javascript]: npm registry, package.json, package-lock.json
- [ruby]: RubyGems registry, Gemfile, Gemfile.lock
- [php]: Packagist registry, composer.json, composer.lock
- [java]: Maven Central registry, pom.xml
- [golang]: Go Module Proxy, go.mod
Language subpackages also provide registry-specific Fetcher implementations that wrap HTTP clients from the [integrations] package.
Concurrency ¶
The resolver uses a worker pool (20 concurrent goroutines by default) to fetch packages in parallel. All public types are safe for concurrent use:
- [Fetcher.Fetch] must be goroutine-safe (called by multiple workers)
- [MetadataProvider.Enrich] must be goroutine-safe (called concurrently per package)
- [Options.Logger] must be goroutine-safe (called from multiple workers)
Manifest parsers are not required to be goroutine-safe as they are typically called once per file.
Error Handling ¶
Resolution errors fall into two categories:
- Fatal: Root package not found or unreachable. [Resolver.Resolve] returns an error.
- Non-fatal: Transitive dependency failures. Logged via [Options.Logger] but don't fail resolution.
Manifest parsing errors are always fatal and returned by [ManifestParser.Parse]. Metadata enrichment errors are non-fatal and logged.
[integrations]: github.com/stacktower-io/stacktower/pkg/integrations internal/cli: github.com/stacktower-io/stacktower/internal/cli dag.DAG: github.com/stacktower-io/stacktower/pkg/core/dag.DAG dag.Node: github.com/stacktower-io/stacktower/pkg/core/dag.Node [python]: github.com/stacktower-io/stacktower/pkg/core/deps/python [rust]: github.com/stacktower-io/stacktower/pkg/core/deps/rust [javascript]: github.com/stacktower-io/stacktower/pkg/core/deps/javascript [ruby]: github.com/stacktower-io/stacktower/pkg/core/deps/ruby [php]: github.com/stacktower-io/stacktower/pkg/core/deps/php [java]: github.com/stacktower-io/stacktower/pkg/core/deps/java [golang]: github.com/stacktower-io/stacktower/pkg/core/deps/golang [metadata.GitHub]: github.com/stacktower-io/stacktower/pkg/core/deps/metadata.GitHub
Example (Constants) ¶
package main
import (
"fmt"
"github.com/stacktower-io/stacktower/pkg/core/deps"
)
func main() {
// Default values for dependency resolution
fmt.Println("DefaultMaxDepth:", deps.DefaultMaxDepth)
fmt.Println("DefaultMaxNodes:", deps.DefaultMaxNodes)
fmt.Println("DefaultCacheTTL:", deps.DefaultCacheTTL)
}
Output: DefaultMaxDepth: 50 DefaultMaxNodes: 5000 DefaultCacheTTL: 24h0m0s
Index ¶
- Constants
- func BuildPackageID(name, version, commit string) string
- func FilterPrereleaseNodes(g *dag.DAG, includePrerelease bool) *dag.DAG
- func GetManifestLanguage(filename string, languages []*Language) string
- func IsManifestSupported(filename string, languages []*Language) bool
- func IsPrereleaseVersion(version string) bool
- func NormalizeLanguageName(name string, languages []*Language) string
- func ParallelMapOrdered[I any, O any](ctx context.Context, workers int, items []I, fn func(context.Context, I) O) ([]O, error)
- func ParsePackageID(id string) (name, version, commit string)
- func ResolveAndMerge(ctx context.Context, resolver Resolver, dependencies []Dependency, ...) (*dag.DAG, error)
- func ShallowGraphFromDeps(dependencies []Dependency) *dag.DAG
- func SupportedManifests(languages []*Language) map[string]string
- type BatchMetadataProvider
- type CargoDetector
- type CargoLockDetector
- type ComposerDetector
- type ConstraintParser
- type CratesURLProvider
- type Dependency
- type DiamondDependencyError
- type EnrichStats
- type Fetcher
- type GemfileDetector
- type GoModDetector
- type GoProxyURLProvider
- type IncompatibleRuntimeError
- type Language
- type ManifestInfo
- type ManifestParser
- type ManifestResult
- type MavenURLProvider
- type MetadataProvider
- type NpmURLProvider
- type Options
- type Package
- type PackageJSONDetector
- type PackageLockJSONDetector
- type PackageRef
- type PackageURLs
- type PackagistURLProvider
- type PnpmLockDetector
- type PoetryLockDetector
- type PubGrubResolver
- func (r *PubGrubResolver) Fetch(ctx context.Context, name string, refresh bool) (*Package, error)
- func (r *PubGrubResolver) FetchVersion(ctx context.Context, name, version string, refresh bool) (*Package, error)
- func (r *PubGrubResolver) ListVersions(ctx context.Context, name string, refresh bool) ([]string, error)
- func (r *PubGrubResolver) ListVersionsWithConstraints(ctx context.Context, name string, refresh bool) (map[string]string, error)
- func (r *PubGrubResolver) Name() string
- func (r *PubGrubResolver) ProbeRuntimeConstraint(ctx context.Context, name, version string, refresh bool) (RuntimeConstraintProbe, error)
- func (r *PubGrubResolver) Resolve(ctx context.Context, pkg string, opts Options) (*dag.DAG, error)
- type PyPIURLProvider
- type PyprojectDetector
- type RequirementsDetector
- type Resolver
- type RubyGemsURLProvider
- type RuntimeConstraintLister
- type RuntimeConstraintProbe
- type RuntimeConstraintProber
- type SectionDetector
- type SourceRange
- type URLProvider
- type UvLockDetector
- type VersionHinter
- type VersionLister
- type YarnLockDetector
Examples ¶
Constants ¶
const ( // DefaultMaxDepth is the default maximum dependency depth (50 levels). // This prevents infinite recursion in circular or very deep dependency trees. // Note: pipeline.DefaultMaxDepth (10) is more conservative for CLI/API UX, // but this higher limit is appropriate for the low-level resolver. DefaultMaxDepth = 50 // DefaultMaxNodes is the default maximum number of packages to fetch (5000 nodes). // This caps memory usage and prevents unbounded crawling of large ecosystems. // This value is shared with pipeline.DefaultMaxNodes. DefaultMaxNodes = 5000 // DefaultCacheTTL is the default HTTP cache duration (24 hours). // Cached registry responses are reused within this window unless Refresh is true. DefaultCacheTTL = 24 * time.Hour // DependencyScopeProdOnly keeps only production/runtime dependencies. DependencyScopeProdOnly = "prod_only" // DependencyScopeAll keeps all dependencies including dev/test. DependencyScopeAll = "all" )
const DefaultWorkers = 20
DefaultWorkers is the default number of concurrent goroutines for fetching packages. This limits parallelism to prevent overwhelming registries and to bound memory usage. Set to 20 to match the burst limit of most registry rate limiters.
const ProjectRootNodeID = dag.ProjectRootNodeID
ProjectRootNodeID is an alias for dag.ProjectRootNodeID so that callers within the deps package (and its sub-packages) can use the short name.
Variables ¶
This section is empty.
Functions ¶
func BuildPackageID ¶
BuildPackageID constructs a versioned package identifier.
The format depends on the inputs:
- With commit: "name@commit:sha"
- With version: "name@version"
- Neither: "name"
Commit takes precedence over version if both are provided. Empty name returns an empty string.
func FilterPrereleaseNodes ¶
FilterPrereleaseNodes removes prerelease versions from the graph unless includePrerelease is true.
func GetManifestLanguage ¶
GetManifestLanguage returns the language name for a manifest file, if supported. Returns empty string if the manifest is not supported.
func IsManifestSupported ¶
IsManifestSupported checks if a manifest filename is supported by any of the languages.
func IsPrereleaseVersion ¶
IsPrereleaseVersion checks if a version string represents a prerelease. It detects common prerelease markers like alpha, beta, rc, dev, canary, nightly, next, etc., as well as PEP 440 style markers (e.g., 1.0.0a1).
func NormalizeLanguageName ¶
NormalizeLanguageName maps external language names (e.g., from GitHub API) to our standard internal names. Returns the original (lowercased) if no mapping exists.
func ParallelMapOrdered ¶
func ParallelMapOrdered[I any, O any]( ctx context.Context, workers int, items []I, fn func(context.Context, I) O, ) ([]O, error)
ParallelMapOrdered applies fn to items with bounded concurrency and returns results in the same order as the input slice.
Context cancellation is respected: if the context is cancelled before or during execution, the function returns nil and ctx.Err(). Workers check context before processing each item and stop early on cancellation.
func ParsePackageID ¶
ParsePackageID parses a versioned package identifier into its components.
Scoped npm names (starting with "@") are handled correctly by splitting on the last "@" separator rather than the first.
Examples:
- "requests@2.31.0" → ("requests", "2.31.0", "")
- "lib@commit:abc1234" → ("lib", "", "abc1234")
- "requests" → ("requests", "", "")
- "@types/node@20.1.0" → ("@types/node", "20.1.0", "")
- "@types/node" → ("@types/node", "", "")
Returns the package name, version, and commit. At most one of version or commit will be non-empty.
func ResolveAndMerge ¶
func ResolveAndMerge(ctx context.Context, resolver Resolver, dependencies []Dependency, opts Options) (*dag.DAG, error)
ResolveAndMerge resolves each dependency via the resolver and merges the results into a single DAG with a virtual project root. Dependencies that fail to resolve are added as leaf nodes with a direct edge from the root.
This is the shared implementation used by all manifest parsers that support transitive resolution (e.g., requirements.txt + resolver, Cargo.toml + resolver).
When a dependency has a Pinned version, the resolver will fetch that exact version rather than the latest. This is important for lock files and go.mod where versions are explicitly specified.
func ShallowGraphFromDeps ¶
func ShallowGraphFromDeps(dependencies []Dependency) *dag.DAG
ShallowGraphFromDeps creates a shallow dependency graph with only direct dependencies. The graph has a virtual project root (ProjectRootNodeID) connected to each dependency. This is the standard pattern for manifest parsers that don't resolve transitive dependencies.
func SupportedManifests ¶
SupportedManifests returns a map of filename -> language for all supported manifests. This is a convenience function for quick lookups.
Types ¶
type BatchMetadataProvider ¶
type BatchMetadataProvider interface {
MetadataProvider
// EnrichBatch fetches metadata for multiple packages in one operation.
// Returns a map keyed by package name to its metadata map.
// Packages that fail enrichment are silently omitted from the result.
EnrichBatch(ctx context.Context, pkgs []*PackageRef, refresh bool) (map[string]map[string]any, error)
}
BatchMetadataProvider extends MetadataProvider with bulk enrichment. Instead of N individual Enrich calls (each hitting rate limits), a single EnrichBatch call fetches metadata for all packages at once -- typically via a batched API like GitHub GraphQL.
The resolver checks for this interface and uses it when available, falling back to per-package Enrich calls otherwise.
type CargoDetector ¶
type CargoDetector struct{}
CargoDetector detects dependency sections in Cargo.toml files.
func (*CargoDetector) DetectSections ¶
func (d *CargoDetector) DetectSections(content string) []SourceRange
func (*CargoDetector) Supports ¶
func (d *CargoDetector) Supports(filename string) bool
type CargoLockDetector ¶
type CargoLockDetector struct{}
CargoLockDetector detects package sections in Cargo.lock files.
func (*CargoLockDetector) DetectSections ¶
func (d *CargoLockDetector) DetectSections(content string) []SourceRange
func (*CargoLockDetector) Supports ¶
func (d *CargoLockDetector) Supports(filename string) bool
type ComposerDetector ¶
type ComposerDetector struct{}
ComposerDetector detects dependency sections in composer.json files.
func (*ComposerDetector) DetectSections ¶
func (d *ComposerDetector) DetectSections(content string) []SourceRange
func (*ComposerDetector) Supports ¶
func (d *ComposerDetector) Supports(filename string) bool
type ConstraintParser ¶
type ConstraintParser interface {
// ParseConstraint converts an ecosystem-specific constraint string to a
// PubGrub Condition. Returns nil if the constraint is empty or invalid.
ParseConstraint(constraint string) pubgrub.Condition
// ParseVersion converts a version string to a PubGrub Version.
ParseVersion(version string) pubgrub.Version
}
ConstraintParser converts ecosystem-specific version constraints to PubGrub format. Different ecosystems use different constraint syntaxes (PEP 440, semver, etc.), so each provides its own parser implementation.
type CratesURLProvider ¶
type CratesURLProvider struct {
// contains filtered or unexported fields
}
CratesURLProvider fetches package URLs from crates.io in parallel. The underlying crates.Client handles rate limiting (5 req/s, burst 10).
func NewCratesURLProvider ¶
func NewCratesURLProvider(c cache.Cache, cacheTTL time.Duration) *CratesURLProvider
NewCratesURLProvider creates a new crates.io URL provider.
func (*CratesURLProvider) FetchURLs ¶
func (p *CratesURLProvider) FetchURLs(ctx context.Context, names []string, refresh bool) (map[string]PackageURLs, error)
FetchURLs fetches repository URLs for the given crate names from crates.io. Packages are processed in chunks of 50 for better progress feedback.
type Dependency ¶
type Dependency struct {
// Name is the normalized package name (e.g., "requests", "lodash").
Name string
// Constraint is the version constraint from the manifest file (e.g., "^4.17.0",
// ">=2.0", "~1.0"). Empty if no constraint was specified (meaning "latest").
Constraint string
// Pinned is the resolved/pinned version (e.g., "4.17.21"). This is set when
// the exact version is known, either from a lock file or after resolution.
// Empty if the version hasn't been resolved yet.
Pinned string
// Commit is the git commit SHA for private repos or packages without semantic
// versions. Takes precedence over Pinned when building the versioned ID.
// Format: full SHA or abbreviated (e.g., "abc1234").
Commit string
}
Dependency represents a package dependency with version information.
This struct captures both the declared constraint (from manifest files) and the resolved version (from lock files or registry lookups). For private repos without semantic versions, the Commit field tracks the git commit SHA.
func DependencyFromName ¶
func DependencyFromName(name string) Dependency
DependencyFromName creates a Dependency with only the name set. This is a convenience function for backward compatibility when only the package name is known.
func DependencyFromNameConstraint ¶
func DependencyFromNameConstraint(name, constraint string) Dependency
DependencyFromNameConstraint creates a Dependency with name and constraint.
func DependencyFromNameVersion ¶
func DependencyFromNameVersion(name, version string) Dependency
DependencyFromNameVersion creates a Dependency with name and pinned version.
func (Dependency) ID ¶
func (d Dependency) ID() string
ID returns the unique identifier for this dependency.
The format depends on what version information is available:
- With commit: "name@commit:sha" (e.g., "my-lib@commit:abc1234")
- With pinned version: "name@version" (e.g., "requests@2.31.0")
- Unresolved: "name" (e.g., "requests")
This ID is suitable for use as a dag.Node.ID and uniquely identifies the specific version of the package in the dependency graph.
func (Dependency) IsResolved ¶
func (d Dependency) IsResolved() bool
IsResolved reports whether this dependency has a resolved version or commit.
func (Dependency) NameOnly ¶
func (d Dependency) NameOnly() string
NameOnly returns just the package name without version information. This is useful for backward compatibility with name-only lookups.
func (Dependency) VersionConstraint ¶
func (d Dependency) VersionConstraint() string
VersionConstraint returns the version constraint string for edge metadata. Returns Constraint if set, otherwise Pinned.
type DiamondDependencyError ¶
type DiamondDependencyError struct {
Package string // The package with conflicting requirements
Dependents []string // Packages that depend on conflicting versions
Language string // Ecosystem (e.g., "javascript")
OriginalErr string // Original resolver error message
}
DiamondDependencyError is returned when dependency resolution fails due to conflicting version requirements for the same package from different dependents. This is common in npm where packages allow nested/duplicate dependencies, but our SAT solver requires a single version per package.
func (*DiamondDependencyError) Error ¶
func (e *DiamondDependencyError) Error() string
type EnrichStats ¶
type EnrichStats struct {
// Total is the number of packages that were candidates for enrichment.
Total int
// Succeeded is the number of packages that were successfully enriched
// (metadata was added to the node).
Succeeded int
// Failed is the number of packages where enrichment failed.
Failed int
// AuthError indicates if an authentication error was encountered.
// This typically means a GitHub token is missing, expired, or invalid.
AuthError bool
// UsedBatch indicates if batch enrichment was used (vs per-package fallback).
UsedBatch bool
}
EnrichStats contains statistics about the enrichment process. This is useful for observability and debugging.
func EnrichGraph ¶
EnrichGraph adds external metadata (e.g. GitHub stars) to every non-root graph node. It prefers batch providers (one API call for all packages) and falls back to parallel per-package enrichment using ParallelMapOrdered.
The manifestFile parameter specifies the manifest file name for PackageRef (e.g., "package.json", "Cargo.toml", "pyproject.toml").
If opts.URLProvider is set, it fetches package URLs from the registry before enrichment. This enables GitHub enrichment for manifest files that don't include repository URLs directly (e.g., lock files).
Returns EnrichStats with counts of successful/failed enrichments for observability. This is the standard enrichment pattern for all lock file parsers.
type Fetcher ¶
type Fetcher interface {
// Fetch retrieves package information by name (latest version).
//
// The name is the package identifier in the registry (e.g., "requests", "serde").
// If refresh is true, cached HTTP responses are bypassed and fresh data is fetched.
//
// Returns an error if:
// - The package does not exist in the registry
// - The registry API is unreachable or returns an error
// - The response cannot be parsed
//
// Implementations should respect context cancellation and return ctx.Err()
// when the context is canceled.
//
// Fetch must be safe for concurrent use by multiple goroutines.
Fetch(ctx context.Context, name string, refresh bool) (*Package, error)
// FetchVersion retrieves package information for a specific version.
//
// The name is the package identifier, and version is the exact version to fetch.
// If refresh is true, cached HTTP responses are bypassed.
//
// Returns an error if:
// - The package or version does not exist in the registry
// - The registry API is unreachable or returns an error
// - The response cannot be parsed
//
// FetchVersion must be safe for concurrent use by multiple goroutines.
FetchVersion(ctx context.Context, name, version string, refresh bool) (*Package, error)
}
Fetcher retrieves package metadata from a registry.
Implementations wrap HTTP clients for specific registries (PyPI, npm, crates.io). The Fetcher is responsible for HTTP caching, rate limiting, and error handling.
Fetchers are found in the integrations subpackages (e.g., integrations/pypi).
type GemfileDetector ¶
type GemfileDetector struct{}
GemfileDetector detects dependency sections in Gemfile files.
func (*GemfileDetector) DetectSections ¶
func (d *GemfileDetector) DetectSections(content string) []SourceRange
func (*GemfileDetector) Supports ¶
func (d *GemfileDetector) Supports(filename string) bool
type GoModDetector ¶
type GoModDetector struct{}
GoModDetector detects dependency sections in go.mod files.
func (*GoModDetector) DetectSections ¶
func (d *GoModDetector) DetectSections(content string) []SourceRange
func (*GoModDetector) Supports ¶
func (d *GoModDetector) Supports(filename string) bool
type GoProxyURLProvider ¶
type GoProxyURLProvider struct {
// contains filtered or unexported fields
}
GoProxyURLProvider fetches module URLs from Go module proxy in parallel. The underlying goproxy.Client handles rate limiting.
func NewGoProxyURLProvider ¶
func NewGoProxyURLProvider(c cache.Cache, cacheTTL time.Duration) *GoProxyURLProvider
NewGoProxyURLProvider creates a new Go module proxy URL provider.
func (*GoProxyURLProvider) FetchURLs ¶
func (p *GoProxyURLProvider) FetchURLs(ctx context.Context, names []string, refresh bool) (map[string]PackageURLs, error)
FetchURLs fetches repository URLs for the given module paths from Go proxy. Packages are processed in chunks of 50 for better progress feedback.
type IncompatibleRuntimeError ¶
type IncompatibleRuntimeError struct {
Package string // Package name
Version string // Package version
RuntimeConstraint string // Package's runtime requirement (e.g., ">=3.8")
TargetRuntime string // Target runtime version (e.g., "2.7")
}
IncompatibleRuntimeError is returned when a package requires a runtime version that is incompatible with the target runtime version.
func (*IncompatibleRuntimeError) Error ¶
func (e *IncompatibleRuntimeError) Error() string
type Language ¶
type Language struct {
// Name is the language identifier (e.g., "python", "rust", "javascript").
Name string
// DefaultRegistry is the primary registry name for this language
// (e.g., "pypi" for Python, "crates" for Rust). Used when no registry
// is explicitly specified.
DefaultRegistry string
// DefaultRuntimeVersion is the default runtime version to use when none
// is specified via CLI or manifest. For example, "3.11" for Python,
// "22.12.0" for Node.js, "1.75" for Rust. Empty string means no default.
DefaultRuntimeVersion string
// RegistryAliases maps alternative registry names to canonical names.
// For example, {"npm": "npm", "npmjs": "npm"}. May be nil or empty.
RegistryAliases map[string]string
// ManifestTypes lists supported manifest type identifiers
// (e.g., ["poetry", "requirements", "pipfile"] for Python). These are
// the canonical names passed to NewManifest. May be nil or empty.
ManifestTypes []string
// ManifestAliases maps filenames to manifest types. For example,
// {"poetry.lock": "poetry", "requirements.txt": "requirements"}.
// Used by DetectManifest to match file paths. May be nil or empty.
ManifestAliases map[string]string
// NewResolver creates a registry Resolver with the given cache and options.
// The cache is used for HTTP response caching (use cache.NullCache{} for no caching).
// Options provides language-specific configuration (e.g., RuntimeVersion for Python).
// Returns an error if resolver construction fails (e.g., missing
// configuration). May be nil if the language has no registry support.
NewResolver func(c cache.Cache, opts Options) (Resolver, error)
// NewManifest creates a ManifestParser for the given type name and resolver.
// The name is typically a value from ManifestTypes or ManifestAliases.
// Returns nil if the type is unrecognized. May be nil if the language
// has no manifest support. The resolver may be nil for parsers that don't
// need to fetch additional data.
NewManifest func(name string, res Resolver) ManifestParser
// ManifestParsers returns all available ManifestParser implementations
// for this language. The resolver is passed to each parser and may be nil.
// Returns nil or an empty slice if the language has no manifest support.
ManifestParsers func(res Resolver) []ManifestParser
// NormalizeName transforms a package name to its canonical form.
// For example, Maven coordinates may use underscores as a filesystem-safe
// alternative to colons: "com.google.guava_guava" -> "com.google.guava:guava".
// May be nil if the language doesn't require name normalization.
NormalizeName func(name string) string
}
Language defines how to resolve dependencies for a programming language.
Each language subpackage (python, rust, javascript, etc.) exports a Language value that describes its registry API, manifest formats, and how to construct resolvers and parsers.
Language values are typically used by the CLI to dispatch commands like "stacktower parse" based on file type or registry name.
func FindLanguage ¶
FindLanguage returns the Language with the given name from the provided list, or nil if not found.
func (*Language) HasManifests ¶
HasManifests reports whether this language supports manifest file parsing.
Returns true if NewManifest is non-nil, meaning at least one manifest format is supported. This does not guarantee that a specific manifest type is recognized—use Manifest to check.
func (*Language) Manifest ¶
func (l *Language) Manifest(name string, res Resolver) (ManifestParser, bool)
Manifest returns a parser for the named manifest type, resolving aliases.
The name is first resolved through ManifestAliases (e.g., "poetry.lock" -> "poetry"), then passed to NewManifest. The resolver may be nil for parsers that don't fetch additional data.
Returns (parser, true) if successful, or (nil, false) if:
- NewManifest is nil (language has no manifest support)
- The manifest type is unrecognized by NewManifest
Safe to call on a zero Language value.
func (*Language) Registry ¶
Registry returns a Resolver for the named registry, resolving aliases.
The name is first resolved through RegistryAliases. If it doesn't match DefaultRegistry, an error is returned. This method currently only supports a single registry per language.
The resolver is created with the given backend and options. Use DefaultCacheTTL in opts.CacheTTL if not specified.
Returns an error if the registry name is unknown or if NewResolver fails.
type ManifestInfo ¶
type ManifestInfo struct {
// Filename is the manifest file name (e.g., "package.json", "go.mod").
Filename string
// Language is the language name (e.g., "python", "javascript").
Language string
// ManifestType is the internal type identifier (e.g., "poetry", "cargo").
ManifestType string
// Supported indicates whether stacktower can parse this manifest.
Supported bool
}
ManifestInfo describes a manifest file and its support status.
func KnownManifests ¶
func KnownManifests(languages []*Language, extraUnsupported map[string]string) []ManifestInfo
KnownManifests lists all manifest files that stacktower knows about. This includes both supported manifests (from Language.ManifestAliases) and commonly encountered manifests that are not yet supported.
The languages parameter should contain all Language definitions to aggregate. Additional unsupported manifests can be added via extraUnsupported.
type ManifestParser ¶
type ManifestParser interface {
// Parse reads the manifest file at path and returns the dependency graph.
//
// The path is typically a local file system path. Options may influence
// parsing behavior (e.g., MaxDepth for resolvers that fetch additional data).
//
// Returns an error if the file cannot be read, is malformed, or if
// dependency resolution fails. Common errors:
// - File not found or unreadable
// - Invalid JSON/TOML/YAML syntax
// - Missing required fields
// - Dependency fetching failures (if the parser resolves transitive deps)
Parse(path string, opts Options) (*ManifestResult, error)
// Supports reports whether this parser handles the given filename.
//
// The filename is typically the basename of a path (e.g., "package.json").
// Returns true if this parser recognizes the file format.
Supports(filename string) bool
// Type returns the manifest type identifier (e.g., "poetry", "cargo", "npm").
//
// This identifier appears in ManifestResult.Type and is used for
// logging and error messages.
Type() string
// IncludesTransitive reports whether this parser produces transitive deps.
//
// Returns true for lock files (poetry.lock, Cargo.lock) that contain the
// full dependency closure. Returns false for requirement files (requirements.txt,
// package.json) that only list direct dependencies.
//
// This is used by the CLI to decide whether additional resolution is needed.
IncludesTransitive() bool
}
ManifestParser reads dependency information from local manifest files.
Manifest files describe a project's dependencies and may be either:
- Requirement files (package.json, requirements.txt) with direct deps only
- Lock files (poetry.lock, Cargo.lock) with full transitive closures
Implementations are found in language subpackages (e.g., python.PoetryParser).
func DetectManifest ¶
func DetectManifest(path string, parsers ...ManifestParser) (ManifestParser, error)
DetectManifest finds a parser that supports the given file path.
The path is matched against each parser's Supports method using the basename. Parsers are checked in order, and the first match is returned.
Typical usage:
lang := python.Language
parsers := lang.ManifestParsers(nil)
parser, err := deps.DetectManifest("poetry.lock", parsers...)
if err != nil {
// No parser supports poetry.lock
}
result, err := parser.Parse("poetry.lock", opts)
Returns an error if no parser in the list supports the file. An empty parsers list always returns an error.
Example ¶
package main
import (
"fmt"
)
func main() {
// DetectManifest finds the right parser for a manifest file.
// In real usage, you would get parsers from a Language definition:
//
// import "github.com/stacktower-io/stacktower/pkg/core/deps/python"
//
// parsers := python.Language.ManifestParsers(nil)
// parser, err := deps.DetectManifest("poetry.lock", parsers...)
// if err != nil {
// log.Fatal(err)
// }
// result, err := parser.Parse("poetry.lock", opts)
//
// The function matches filename patterns to find a suitable parser.
// Returns an error if no parser recognizes the file.
fmt.Println("DetectManifest matches filename to parser type")
}
Output: DetectManifest matches filename to parser type
type ManifestResult ¶
type ManifestResult struct {
// Graph is the dependency graph with nodes for packages and edges
// for dependencies.
Graph *dag.DAG
// Type is the manifest type identifier (from ManifestParser.Type).
// Examples: "poetry", "cargo", "npm", "requirements".
Type string
// IncludesTransitive indicates whether Graph contains the full transitive
// closure (true for lock files) or just direct dependencies (false).
IncludesTransitive bool
// RootPackage is the name of the root package, if determinable from the
// manifest. Empty if the manifest doesn't specify a package name (e.g.,
// requirements.txt has no root package).
RootPackage string
// RuntimeVersion is the target runtime version detected from the manifest.
// For Python: extracted from requires-python (e.g., ">=3.9" → "3.9")
// For Node.js: extracted from engines.node
// For Ruby: extracted from ruby version directive
// Empty if not specified; callers should use a language-specific default.
RuntimeVersion string
// RuntimeConstraint is the raw runtime constraint from the manifest.
// For Python: ">=3.9,<4.0"
// For Node.js: ">=18"
// Empty if not specified.
RuntimeConstraint string
}
ManifestResult holds the parsed dependency data from a manifest file.
Returned by [ManifestParser.Parse] after successfully reading a manifest.
type MavenURLProvider ¶
type MavenURLProvider struct {
// contains filtered or unexported fields
}
MavenURLProvider fetches artifact URLs from Maven Central in parallel. The underlying maven.Client handles rate limiting.
func NewMavenURLProvider ¶
func NewMavenURLProvider(c cache.Cache, cacheTTL time.Duration) *MavenURLProvider
NewMavenURLProvider creates a new Maven URL provider.
func (*MavenURLProvider) FetchURLs ¶
func (p *MavenURLProvider) FetchURLs(ctx context.Context, names []string, refresh bool) (map[string]PackageURLs, error)
FetchURLs fetches repository URLs for the given artifact coordinates from Maven. Coordinates should be in the format "groupId:artifactId". Packages are processed in chunks of 50 for better progress feedback.
type MetadataProvider ¶
type MetadataProvider interface {
// Name returns the provider identifier (e.g., "github", "gitlab").
// This is used for logging and error messages.
Name() string
// Enrich fetches additional metadata for the package.
//
// The pkg parameter contains registry information and URLs for lookup.
// If refresh is true, the provider should bypass its cache.
//
// Returns a map of metadata keys to values, which are merged into the
// package node's metadata. Keys should be provider-specific (e.g.,
// "github_stars") to avoid conflicts with other providers.
//
// Returns an error if enrichment fails. The resolver logs the error
// but continues without failing the entire resolution.
Enrich(ctx context.Context, pkg *PackageRef, refresh bool) (map[string]any, error)
}
MetadataProvider enriches package nodes with external data (e.g., GitHub stars).
Implementations fetch supplementary information that is not available in package registries, such as repository activity, maintainer counts, or security metrics. Providers are called concurrently after fetching each package during resolution.
type NpmURLProvider ¶
type NpmURLProvider struct {
// contains filtered or unexported fields
}
NpmURLProvider fetches package URLs from npm in parallel. The underlying npm.Client handles rate limiting (50 req/s, burst 30).
func NewNpmURLProvider ¶
func NewNpmURLProvider(c cache.Cache, cacheTTL time.Duration) *NpmURLProvider
NewNpmURLProvider creates a new npm URL provider.
func (*NpmURLProvider) FetchURLs ¶
func (p *NpmURLProvider) FetchURLs(ctx context.Context, names []string, refresh bool) (map[string]PackageURLs, error)
FetchURLs fetches repository URLs for the given package names from npm. Packages are processed in chunks of 50 for better progress feedback.
type Options ¶
type Options struct {
// Ctx is the context for cancellation and timeouts. If nil, WithDefaults
// replaces it with context.Background(). Manifest parsers and resolvers
// use this context for network operations and enrichment so that callers
// can cancel long-running parses.
Ctx context.Context
// Version constrains the root package to a specific version. If empty,
// the latest version is fetched. This only applies to the root package;
// transitive dependencies are resolved normally.
// Example: "2.31.0" for requests@2.31.0
Version string
// Constraint constrains the root package to a version range (ecosystem
// syntax), such as "^4.18.0" (npm) or ">=1.0, <2.0". If both Version and
// Constraint are set, Version takes precedence.
Constraint string
// DependencyScope controls which dependency groups parsers include.
// Supported values:
// - "prod_only" (default): runtime/production dependencies only
// - "all": include dev/test dependencies too
DependencyScope string
// MaxDepth limits how many levels deep to traverse. A value of 1 fetches
// only direct dependencies. Zero or negative values use DefaultMaxDepth (50).
MaxDepth int
// MaxNodes limits the total number of packages to fetch. When this limit
// is reached, deeper dependencies are ignored but already-queued packages
// may still be fetched. Zero or negative values use DefaultMaxNodes (5000).
MaxNodes int
// Workers is the number of concurrent goroutines for fetching packages.
// Higher values increase parallelism but may trigger rate limits.
// Zero or negative values use DefaultWorkers (20).
Workers int
// CacheTTL controls how long HTTP responses are cached. Registry clients
// will reuse cached data within this duration. Zero or negative values use
// DefaultCacheTTL (24 hours).
CacheTTL time.Duration
// Refresh bypasses the HTTP cache when true, forcing fresh registry fetches.
// This is useful for getting the latest package versions but increases latency.
Refresh bool
// MetadataProviders is an optional list of enrichment sources (e.g., GitHub)
// that add extra metadata to package nodes. Providers are called concurrently
// after fetching each package. Nil or empty is safe.
MetadataProviders []MetadataProvider
// Logger is an optional callback for progress and error messages. If nil,
// WithDefaults replaces it with a no-op logger. The format string follows
// fmt.Printf conventions. Logger may be called concurrently from multiple
// goroutines and must be safe for concurrent use.
Logger func(string, ...any)
// IncludePrerelease controls whether prerelease versions (alpha, beta, rc,
// dev, etc.) are considered during resolution. When false (the default), the
// resolver filters out prerelease versions before version selection.
IncludePrerelease bool
// RuntimeVersion is the target runtime version for environment marker evaluation.
// For Python, this filters deps with markers like `python_version < "3.11"`.
// For Node.js, this would filter based on `engines.node` constraints.
// Empty means use the language-specific default (e.g., "3.11" for Python).
RuntimeVersion string
// URLProvider fetches repository URLs from the package registry.
// Used by EnrichGraph to populate PackageRef.ProjectURLs for manifest parsing.
// If nil, packages without URLs in node metadata won't be enriched with
// external data like GitHub stars.
URLProvider URLProvider
}
Options configures dependency resolution behavior.
All fields are optional. Zero values are replaced by defaults when passed to WithDefaults. Options is safe to copy and does not modify any inputs.
Example (Limits) ¶
package main
import (
"fmt"
"time"
"github.com/stacktower-io/stacktower/pkg/core/deps"
)
func main() {
// Configure resolution limits for large dependency trees
opts := deps.Options{
MaxDepth: 10, // Stop at 10 levels deep
MaxNodes: 100, // Fetch at most 100 packages
CacheTTL: time.Hour,
Refresh: true, // Bypass cache for fresh data
}
fmt.Println("MaxDepth:", opts.MaxDepth)
fmt.Println("MaxNodes:", opts.MaxNodes)
fmt.Println("Refresh:", opts.Refresh)
}
Output: MaxDepth: 10 MaxNodes: 100 Refresh: true
Example (WithLogger) ¶
package main
import (
"fmt"
"github.com/stacktower-io/stacktower/pkg/core/deps"
)
func main() {
// Custom logger for progress tracking
var logs []string
opts := deps.Options{
MaxDepth: 5,
Logger: func(format string, args ...any) {
logs = append(logs, fmt.Sprintf(format, args...))
},
}.WithDefaults()
// Logger is preserved through WithDefaults
opts.Logger("Fetching %s", "fastapi")
opts.Logger("Found %d dependencies", 12)
fmt.Println("Log count:", len(logs))
}
Output: Log count: 2
func (Options) WithDefaults ¶
WithDefaults returns a copy of Options with zero values replaced by defaults.
This method is safe to call on a zero Options value. It fills in:
- MaxDepth: DefaultMaxDepth (50)
- MaxNodes: DefaultMaxNodes (5000)
- CacheTTL: DefaultCacheTTL (24h)
- Logger: no-op function if nil
All other fields (Refresh, MetadataProviders) are preserved as-is, including nil slices. The original Options value is not modified.
Example ¶
package main
import (
"fmt"
"github.com/stacktower-io/stacktower/pkg/core/deps"
)
func main() {
// Create options with some custom values
opts := deps.Options{
MaxDepth: 10,
// MaxNodes and CacheTTL left as zero - will get defaults
}
// Apply defaults to fill in missing values
opts = opts.WithDefaults()
fmt.Println("MaxDepth:", opts.MaxDepth)
fmt.Println("MaxNodes:", opts.MaxNodes)
fmt.Println("CacheTTL:", opts.CacheTTL)
}
Output: MaxDepth: 10 MaxNodes: 5000 CacheTTL: 24h0m0s
type Package ¶
type Package struct {
// Name is the package identifier in the registry (e.g., "requests", "serde").
Name string
// Version is the package version (e.g., "2.31.0"). For registry lookups
// without a version constraint, this is typically the latest stable version.
Version string
// Commit is the git commit SHA for private repos or packages without semantic
// versions. When set, this takes precedence over Version for building the
// package's versioned ID.
Commit string
// Dependencies lists direct dependencies with their version constraints.
// The resolver recursively fetches these to build the dependency tree.
// Nil and empty slices are equivalent.
Dependencies []Dependency
// Description is a short summary of the package purpose. May be empty.
Description string
// License is the package license identifier (e.g., "MIT", "Apache-2.0").
// May be empty or unknown.
License string
// LicenseText is the full raw license text for custom/proprietary licenses.
// This is populated when the license field contains a long custom license
// (e.g., custom commercial terms) rather than a standard SPDX identifier.
// Intended for downstream LLM analysis of non-standard licenses.
// May be empty for standard licenses where the text is well-known.
LicenseText string
// Author is the primary package author or maintainer. May be empty.
Author string
// Downloads is the total download count or recent download rate, depending
// on the registry. Zero if unavailable. Not all registries provide this.
Downloads int
// Repository is the source code repository URL (e.g., GitHub, GitLab).
// May be empty if not specified in registry metadata.
Repository string
// HomePage is the project home page URL. May be empty or identical to Repository.
HomePage string
// ProjectURLs contains additional URLs from the registry (docs, issues, etc.).
// Keys and availability vary by registry. May be nil.
ProjectURLs map[string]string
// ManifestFile identifies the manifest type when this Package comes from
// manifest parsing (e.g., "poetry", "cargo"). Empty for registry packages.
ManifestFile string
// RuntimeConstraint is the runtime version constraint (e.g., ">=3.8" for Python).
// Used to check compatibility with target runtime versions. May be empty.
RuntimeConstraint string
}
Package holds metadata fetched from a package registry.
This is the core data structure returned by [Fetcher.Fetch] and used throughout the resolution process. Not all fields are populated by every registry—consult the specific integration documentation for field availability.
func (*Package) DependencyNames ¶
DependencyNames returns the dependency names as a slice of strings. This is a backward compatibility helper for code that only needs names.
func (*Package) ID ¶
ID returns the versioned identifier for this package.
The format depends on what version information is available:
- With commit: "name@commit:sha"
- With version: "name@version"
- Neither: "name"
func (*Package) Metadata ¶
Metadata converts Package fields to a map for node metadata.
The returned map always contains "version". Optional fields (description, license, author, downloads, commit) are included only if non-empty/non-zero.
This map is suitable for use as dag.Node.Meta and can be further enriched by MetadataProvider implementations. The map is newly allocated and safe to modify. Returns a non-nil map even if the Package has no optional fields.
Example ¶
package main
import (
"fmt"
"github.com/stacktower-io/stacktower/pkg/core/deps"
)
func main() {
// Package metadata from a registry
pkg := deps.Package{
Name: "fastapi",
Version: "0.100.0",
Description: "FastAPI framework, high performance",
License: "MIT",
Author: "Sebastián Ramírez",
Downloads: 1000000,
}
// Convert to node metadata map
meta := pkg.Metadata()
fmt.Println("version:", meta["version"])
fmt.Println("license:", meta["license"])
fmt.Println("has description:", meta["description"] != nil)
}
Output: version: 0.100.0 license: MIT has description: true
func (*Package) Ref ¶
func (p *Package) Ref() *PackageRef
Ref creates a PackageRef for metadata provider lookups.
The returned PackageRef consolidates URL information from multiple Package fields (ProjectURLs, Repository, HomePage) into a single ProjectURLs map for convenient provider lookups.
The ProjectURLs map is a clone of the original, so modifying it does not affect the Package. If the Package has nil ProjectURLs, an empty map is allocated. Repository and HomePage are added to the map under "repository" and "homepage" keys if non-empty.
This method never returns nil. Safe to call on a zero Package value.
Example ¶
package main
import (
"fmt"
"github.com/stacktower-io/stacktower/pkg/core/deps"
)
func main() {
// Package with repository information
pkg := deps.Package{
Name: "requests",
Version: "2.31.0",
Repository: "https://github.com/psf/requests",
HomePage: "https://requests.readthedocs.io",
}
// Create a reference for metadata enrichment lookups
ref := pkg.Ref()
fmt.Println("Name:", ref.Name)
fmt.Println("Repository URL:", ref.ProjectURLs["repository"])
}
Output: Name: requests Repository URL: https://github.com/psf/requests
func (*Package) SetDependenciesFromNames ¶
SetDependenciesFromNames sets dependencies from a slice of package names. This is a backward compatibility helper for code that only has names.
type PackageJSONDetector ¶
type PackageJSONDetector struct{}
PackageJSONDetector detects dependency sections in package.json files.
func (*PackageJSONDetector) DetectSections ¶
func (d *PackageJSONDetector) DetectSections(content string) []SourceRange
func (*PackageJSONDetector) Supports ¶
func (d *PackageJSONDetector) Supports(filename string) bool
type PackageLockJSONDetector ¶
type PackageLockJSONDetector struct{}
PackageLockJSONDetector detects package entries in package-lock.json files.
func (*PackageLockJSONDetector) DetectSections ¶
func (d *PackageLockJSONDetector) DetectSections(content string) []SourceRange
func (*PackageLockJSONDetector) Supports ¶
func (d *PackageLockJSONDetector) Supports(filename string) bool
type PackageRef ¶
type PackageRef struct {
// Name is the package name as it appears in the registry.
Name string
// Version is the specific version being referenced.
Version string
// ProjectURLs contains URL mappings from the package registry, typically
// including "repository", "homepage", "documentation", etc. The keys
// depend on the registry. May be nil or empty.
ProjectURLs map[string]string
// HomePage is the project's home page URL, if available. May be empty.
HomePage string
// ManifestFile is the associated manifest type (e.g., "poetry", "cargo")
// when the package comes from manifest parsing. Empty for registry-only packages.
ManifestFile string
}
PackageRef identifies a package for metadata enrichment lookups.
It contains the information metadata providers need to look up external data like GitHub repository statistics. Created by Package.Ref.
type PackageURLs ¶
type PackageURLs struct {
// ProjectURLs contains URL mappings from the package registry, typically
// including "repository", "homepage", "documentation", etc.
ProjectURLs map[string]string
// HomePage is the project's home page URL, if available.
HomePage string
}
PackageURLs contains repository URL information for a package. Used by URLProvider to return URL data for manifest enrichment.
type PackagistURLProvider ¶
type PackagistURLProvider struct {
// contains filtered or unexported fields
}
PackagistURLProvider fetches package URLs from Packagist (PHP) in parallel. The underlying packagist.Client handles rate limiting.
func NewPackagistURLProvider ¶
func NewPackagistURLProvider(c cache.Cache, cacheTTL time.Duration) *PackagistURLProvider
NewPackagistURLProvider creates a new Packagist URL provider.
func (*PackagistURLProvider) FetchURLs ¶
func (p *PackagistURLProvider) FetchURLs(ctx context.Context, names []string, refresh bool) (map[string]PackageURLs, error)
FetchURLs fetches repository URLs for the given package names from Packagist. Packages are processed in chunks of 50 for better progress feedback.
type PnpmLockDetector ¶
type PnpmLockDetector struct{}
PnpmLockDetector detects package entries in pnpm-lock.yaml files.
func (*PnpmLockDetector) DetectSections ¶
func (d *PnpmLockDetector) DetectSections(content string) []SourceRange
func (*PnpmLockDetector) Supports ¶
func (d *PnpmLockDetector) Supports(filename string) bool
type PoetryLockDetector ¶
type PoetryLockDetector struct{}
PoetryLockDetector detects package sections in poetry.lock files.
func (*PoetryLockDetector) DetectSections ¶
func (d *PoetryLockDetector) DetectSections(content string) []SourceRange
func (*PoetryLockDetector) Supports ¶
func (d *PoetryLockDetector) Supports(filename string) bool
type PubGrubResolver ¶
type PubGrubResolver struct {
// contains filtered or unexported fields
}
PubGrubResolver implements dependency resolution using the PubGrub algorithm. This provides proper SAT-solver-based resolution with conflict detection and backtracking, producing accurate dependency graphs.
func NewPubGrubResolver ¶
func NewPubGrubResolver(name string, fetcher Fetcher, parser ConstraintParser) (*PubGrubResolver, error)
NewPubGrubResolver creates a resolver using the PubGrub algorithm.
The fetcher must implement VersionLister for listing available versions. The parser converts ecosystem-specific constraints to PubGrub format.
Returns an error if the fetcher doesn't support version listing.
Example ¶
package main
import (
"fmt"
)
func main() {
// NewPubGrubResolver wraps a Fetcher with SAT-solver-based resolution.
// In real usage, you would pass a registry-specific fetcher and
// constraint parser:
//
// resolver, err := deps.NewPubGrubResolver("pypi", fetcher, parser)
// g, err := resolver.Resolve(ctx, "requests", deps.Options{})
//
// The resolver uses PubGrub for proper version constraint solving
// with conflict detection and backtracking, then enriches metadata
// in parallel.
fmt.Println("NewPubGrubResolver creates a SAT-solver dependency resolver")
}
Output: NewPubGrubResolver creates a SAT-solver dependency resolver
func (*PubGrubResolver) Fetch ¶
Fetch delegates to the underlying Fetcher, satisfying the Fetcher interface.
func (*PubGrubResolver) FetchVersion ¶
func (r *PubGrubResolver) FetchVersion(ctx context.Context, name, version string, refresh bool) (*Package, error)
FetchVersion delegates to the underlying Fetcher, satisfying the Fetcher interface.
func (*PubGrubResolver) ListVersions ¶
func (r *PubGrubResolver) ListVersions(ctx context.Context, name string, refresh bool) ([]string, error)
ListVersions delegates to the underlying VersionLister, satisfying the VersionLister interface so callers can list versions through the resolver.
func (*PubGrubResolver) ListVersionsWithConstraints ¶
func (r *PubGrubResolver) ListVersionsWithConstraints(ctx context.Context, name string, refresh bool) (map[string]string, error)
ListVersionsWithConstraints delegates to the underlying fetcher if it implements RuntimeConstraintLister. Returns nil, nil if not supported.
func (*PubGrubResolver) Name ¶
func (r *PubGrubResolver) Name() string
Name returns the resolver identifier.
func (*PubGrubResolver) ProbeRuntimeConstraint ¶
func (r *PubGrubResolver) ProbeRuntimeConstraint(ctx context.Context, name, version string, refresh bool) (RuntimeConstraintProbe, error)
ProbeRuntimeConstraint probes runtime requirements for a package. It fetches either latest or a specific version and returns normalized runtime metadata.
type PyPIURLProvider ¶
type PyPIURLProvider struct {
// contains filtered or unexported fields
}
PyPIURLProvider fetches package URLs from PyPI in parallel. The underlying pypi.Client handles rate limiting (50 req/s, burst 30).
func NewPyPIURLProvider ¶
func NewPyPIURLProvider(c cache.Cache, cacheTTL time.Duration) *PyPIURLProvider
NewPyPIURLProvider creates a new PyPI URL provider.
func (*PyPIURLProvider) FetchURLs ¶
func (p *PyPIURLProvider) FetchURLs(ctx context.Context, names []string, refresh bool) (map[string]PackageURLs, error)
FetchURLs fetches repository URLs for the given package names from PyPI. Packages are processed in chunks of 50 for better progress feedback.
type PyprojectDetector ¶
type PyprojectDetector struct{}
PyprojectDetector detects dependency sections in pyproject.toml files.
func (*PyprojectDetector) DetectSections ¶
func (d *PyprojectDetector) DetectSections(content string) []SourceRange
func (*PyprojectDetector) Supports ¶
func (d *PyprojectDetector) Supports(filename string) bool
type RequirementsDetector ¶
type RequirementsDetector struct{}
RequirementsDetector detects dependency lines in requirements.txt files.
func (*RequirementsDetector) DetectSections ¶
func (d *RequirementsDetector) DetectSections(content string) []SourceRange
func (*RequirementsDetector) Supports ¶
func (d *RequirementsDetector) Supports(filename string) bool
type Resolver ¶
type Resolver interface {
// Resolve fetches the package and its transitive dependencies.
//
// Starting from pkg, the resolver recursively fetches dependencies up to
// Options.MaxDepth levels deep and Options.MaxNodes total packages.
//
// Returns a [dag.DAG] where:
// - Nodes represent packages (ID = package name)
// - Edges represent dependencies (From depends on To)
// - Node.Meta contains package metadata from [Package.Metadata] and
// enrichment from Options.MetadataProviders
//
// The DAG is fully connected from the root package. Isolated nodes may
// appear if dependency fetching fails for non-root packages.
//
// Returns an error if:
// - The root package cannot be fetched (registry error or does not exist)
// - The context is canceled
// - Internal errors occur
//
// Partial failures (missing transitive dependencies) are logged via
// Options.Logger but do not fail the entire resolution.
//
// Resolve is safe for concurrent use if the underlying Fetcher is safe.
Resolve(ctx context.Context, pkg string, opts Options) (*dag.DAG, error)
// Name returns the resolver's identifier (e.g., "pypi", "npm", "crates").
//
// This is used for logging and error messages.
Name() string
}
Resolver builds a dependency graph starting from a root package.
The sole production implementation is PubGrubResolver, which uses a SAT-solver for proper constraint resolution with backtracking.
type RubyGemsURLProvider ¶
type RubyGemsURLProvider struct {
// contains filtered or unexported fields
}
RubyGemsURLProvider fetches package URLs from RubyGems in parallel. The underlying rubygems.Client handles rate limiting (30 req/s, burst 20).
func NewRubyGemsURLProvider ¶
func NewRubyGemsURLProvider(c cache.Cache, cacheTTL time.Duration) *RubyGemsURLProvider
NewRubyGemsURLProvider creates a new RubyGems URL provider.
func (*RubyGemsURLProvider) FetchURLs ¶
func (p *RubyGemsURLProvider) FetchURLs(ctx context.Context, names []string, refresh bool) (map[string]PackageURLs, error)
FetchURLs fetches repository URLs for the given gem names from RubyGems. Packages are processed in chunks of 50 for better progress feedback.
type RuntimeConstraintLister ¶
type RuntimeConstraintLister interface {
// ListVersionsWithConstraints returns a map of version -> runtime constraint
// (e.g., ">=3.8" for Python) for all versions of a package.
// Empty string values indicate no runtime constraint for that version.
//
// Returns an error if:
// - The package does not exist in the registry
// - The registry API is unreachable or returns an error
ListVersionsWithConstraints(ctx context.Context, name string, refresh bool) (map[string]string, error)
}
RuntimeConstraintLister extends Fetcher with the ability to list runtime constraints for all versions in a single API call. This is more efficient than calling FetchVersion for each version individually.
This is optional - fetchers that don't implement it will fall back to individual FetchVersion calls for runtime constraint information.
type RuntimeConstraintProbe ¶
type RuntimeConstraintProbe struct {
// Constraint is the normalized runtime constraint string (e.g., ">=3.8").
Constraint string
// MinVersion is the extracted minimum runtime version when derivable.
MinVersion string
}
RuntimeConstraintProbe contains runtime requirement information for a package.
type RuntimeConstraintProber ¶
type RuntimeConstraintProber interface {
// ProbeRuntimeConstraint returns runtime requirements for a package.
// If version is empty, the latest package version is probed.
ProbeRuntimeConstraint(ctx context.Context, name, version string, refresh bool) (RuntimeConstraintProbe, error)
}
RuntimeConstraintProber extends Resolver with package-level runtime probing. This lets orchestration layers infer runtime defaults without constructing registry-specific clients directly.
type SectionDetector ¶
type SectionDetector interface {
// DetectSections scans the manifest content and returns the line ranges
// where dependencies are defined.
DetectSections(content string) []SourceRange
// Supports reports whether this detector handles the given filename.
Supports(filename string) bool
}
SectionDetector detects dependency sections in manifest content. Each language implements this interface to find where dependencies are declared.
func DefaultDetectors ¶
func DefaultDetectors() []SectionDetector
DefaultDetectors returns the standard set of section detectors for all supported languages.
type SourceRange ¶
type SourceRange struct {
// StartLine is the 1-indexed line number where the section starts.
StartLine int `json:"start_line"`
// EndLine is the 1-indexed line number where the section ends (inclusive).
EndLine int `json:"end_line"`
// Section is a human-readable identifier for the section (e.g., "project.dependencies").
Section string `json:"section"`
}
SourceRange identifies a section of a manifest file where dependencies are defined. This is used to highlight the relevant sections in the frontend viewer.
func DetectDependencySections ¶
func DetectDependencySections(content, filename string, detectors ...SectionDetector) []SourceRange
DetectDependencySections finds dependency sections in a manifest file. It tries each detector in order and returns results from the first match. Returns nil if no detector supports the filename.
type URLProvider ¶
type URLProvider interface {
// FetchURLs returns repository URLs for the given package names.
// Implementations should fetch in parallel and respect rate limits.
// Returns a map from package name to URLs (ProjectURLs map and HomePage).
// Packages not found or without URLs are omitted from the result.
FetchURLs(ctx context.Context, names []string, refresh bool) (map[string]PackageURLs, error)
}
URLProvider fetches repository URLs for packages from a registry. Used by EnrichGraph to populate PackageRef.ProjectURLs when parsing manifest files. This enables GitHub enrichment for lock files and other manifests that don't include repository URLs directly.
func NewURLProvider ¶
NewURLProvider returns the ecosystem URL provider for the given language. Returns nil for unsupported languages.
type UvLockDetector ¶
type UvLockDetector struct{}
UvLockDetector detects package sections in uv.lock files.
func (*UvLockDetector) DetectSections ¶
func (d *UvLockDetector) DetectSections(content string) []SourceRange
func (*UvLockDetector) Supports ¶
func (d *UvLockDetector) Supports(filename string) bool
type VersionHinter ¶
type VersionHinter interface {
// HintedVersion extracts an exact version string from a constraint that
// should be hinted to the version lister. Returns empty string if no
// version should be hinted. This allows PubGrub to find versions that
// aren't listed by the registry (e.g., Go pseudo-versions).
HintedVersion(constraint string) string
}
VersionHinter is an optional interface that ConstraintParser implementations can implement to provide version hints from constraints. This is needed for ecosystems like Go where constraints reference exact versions (pseudo-versions) that may not appear in the registry's version list.
type VersionLister ¶
type VersionLister interface {
// ListVersions returns all available versions for a package, sorted from
// oldest to newest. Pre-release versions (alpha, beta, rc, dev) may be
// included but are typically filtered out during constraint resolution.
//
// Returns an error if:
// - The package does not exist in the registry
// - The registry API is unreachable or returns an error
ListVersions(ctx context.Context, name string, refresh bool) ([]string, error)
}
VersionLister extends Fetcher with the ability to list available versions. This is optional - fetchers that don't implement it will fall back to latest version resolution for transitive dependencies.
type YarnLockDetector ¶
type YarnLockDetector struct{}
YarnLockDetector detects package entries in yarn.lock files.
func (*YarnLockDetector) DetectSections ¶
func (d *YarnLockDetector) DetectSections(content string) []SourceRange
func (*YarnLockDetector) Supports ¶
func (d *YarnLockDetector) Supports(filename string) bool
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package golang provides dependency resolution for Go modules.
|
Package golang provides dependency resolution for Go modules. |
|
Package java provides dependency resolution for Maven/Java packages.
|
Package java provides dependency resolution for Maven/Java packages. |
|
Package javascript provides dependency resolution for npm packages.
|
Package javascript provides dependency resolution for npm packages. |
|
Package languages provides the complete list of supported language ecosystems.
|
Package languages provides the complete list of supported language ecosystems. |
|
Package metadata provides metadata enrichment from external sources.
|
Package metadata provides metadata enrichment from external sources. |
|
Package php provides dependency resolution for PHP Composer packages.
|
Package php provides dependency resolution for PHP Composer packages. |
|
Package python provides dependency resolution for Python packages.
|
Package python provides dependency resolution for Python packages. |
|
Package ruby provides dependency resolution for Ruby gems.
|
Package ruby provides dependency resolution for Ruby gems. |
|
Package rust provides dependency resolution for Rust crates.
|
Package rust provides dependency resolution for Rust crates. |