From 0b4729f18b2145f8c36f70d351be8f1b8847a58c Mon Sep 17 00:00:00 2001 From: Sahilb315 Date: Tue, 20 May 2025 21:23:48 +0530 Subject: [PATCH] refactor: unify package dependency resolution and improve PyPI version handling using registry adapter --- cmd/npm/npm.go | 17 +- cmd/npm/pnpm.go | 14 +- cmd/pypi/pip.go | 28 +- diff.txt | 362 ++++++++++++++++++++++++++ internal/flows/common.go | 31 +-- packagemanager/dependency_resolver.go | 6 +- packagemanager/npm_resolver.go | 4 +- packagemanager/pypi.go | 2 - packagemanager/pypi_resolver.go | 59 ++--- 9 files changed, 448 insertions(+), 75 deletions(-) create mode 100644 diff.txt diff --git a/cmd/npm/npm.go b/cmd/npm/npm.go index e89dc36..5e8f86f 100644 --- a/cmd/npm/npm.go +++ b/cmd/npm/npm.go @@ -4,6 +4,7 @@ import ( "context" "github.com/safedep/dry/log" + "github.com/safedep/pmg/config" "github.com/safedep/pmg/internal/flows" "github.com/safedep/pmg/internal/ui" "github.com/safedep/pmg/packagemanager" @@ -31,6 +32,20 @@ func executeNpmFlow(ctx context.Context, args []string) error { if err != nil { ui.Fatalf("Failed to create npm package manager proxy: %s", err) } + config, err := config.FromContext(ctx) + if err != nil { + ui.Fatalf("Failed to get config: %s", err) + } - return flows.Common(packageManager).Run(ctx, args) + packageResolverConfig := packagemanager.NewDefaultNpmDependencyResolverConfig() + packageResolverConfig.IncludeTransitiveDependencies = config.Transitive + packageResolverConfig.TransitiveDepth = config.TransitiveDepth + packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies + + packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig) + if err != nil { + ui.Fatalf("Failed to create dependency resolver: %s", err) + } + + return flows.Common(packageManager, packageResolver, config).Run(ctx, args) } diff --git a/cmd/npm/pnpm.go b/cmd/npm/pnpm.go index c161c0d..a010244 100644 --- a/cmd/npm/pnpm.go +++ b/cmd/npm/pnpm.go @@ -5,6 +5,7 @@ import ( _ "embed" "github.com/safedep/dry/log" + "github.com/safedep/pmg/config" "github.com/safedep/pmg/internal/flows" "github.com/safedep/pmg/internal/ui" "github.com/safedep/pmg/packagemanager" @@ -32,6 +33,17 @@ func executePnpmFlow(ctx context.Context, args []string) error { if err != nil { ui.Fatalf("Failed to create pnpm package manager proxy: %s", err) } + config, err := config.FromContext(ctx) + if err != nil { + ui.Fatalf("Failed to get config: %s", err) + } - return flows.Common(packageManager).Run(ctx, args) + packageResolverConfig := packagemanager.NewDefaultNpmDependencyResolverConfig() + packageResolverConfig.IncludeTransitiveDependencies = config.Transitive + packageResolverConfig.TransitiveDepth = config.TransitiveDepth + packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies + + packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig) + + return flows.Common(packageManager, packageResolver, config).Run(ctx, args) } diff --git a/cmd/pypi/pip.go b/cmd/pypi/pip.go index 56d03f7..4cc9a65 100644 --- a/cmd/pypi/pip.go +++ b/cmd/pypi/pip.go @@ -6,6 +6,7 @@ import ( "github.com/safedep/dry/log" "github.com/safedep/pmg/config" + "github.com/safedep/pmg/internal/flows" "github.com/safedep/pmg/internal/ui" "github.com/safedep/pmg/packagemanager" "github.com/spf13/cobra" @@ -17,12 +18,7 @@ func NewPipCommand() *cobra.Command { Short: "Guard pip package manager", DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { - config, err := config.FromContext(cmd.Context()) - if err != nil { - ui.Fatalf("Failed to get config: %s", err) - } - - err = executePipFlow(cmd.Context(), config, args) + err := executePipFlow(cmd.Context(), args) if err != nil { log.Errorf("Failed to execute pip flow: %s", err) } @@ -32,12 +28,24 @@ func NewPipCommand() *cobra.Command { } } -func executePipFlow(context context.Context, config config.Config, args []string) error { +func executePipFlow(ctx context.Context, args []string) error { packageManager, err := packagemanager.NewPipPackageManager(packagemanager.DefaultPipPackageManagerConfig()) if err != nil { return fmt.Errorf("failed to create pip package manager: %w", err) } - cmd, _ := packageManager.ParseCommand(args) - fmt.Println("Cmd: ", cmd.InstallTargets[0]) - return nil + config, err := config.FromContext(ctx) + if err != nil { + ui.Fatalf("Failed to get config: %s", err) + } + packageResolverConfig := packagemanager.NewDefaultPypiDependencyResolverConfig() + packageResolverConfig.IncludeTransitiveDependencies = config.Transitive + packageResolverConfig.TransitiveDepth = config.TransitiveDepth + packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies + + packageResolver, err := packagemanager.NewPypiDependencyResolver(packageResolverConfig) + if err != nil { + ui.Fatalf("Failed to create dependency resolver: %s", err) + } + + return flows.Common(packageManager, packageResolver, config).Run(ctx, args) } diff --git a/diff.txt b/diff.txt new file mode 100644 index 0000000..c508c7e --- /dev/null +++ b/diff.txt @@ -0,0 +1,362 @@ +diff --git a/cmd/npm/npm.go b/cmd/npm/npm.go +index e89dc36..5e8f86f 100644 +--- a/cmd/npm/npm.go ++++ b/cmd/npm/npm.go +@@ -4,6 +4,7 @@ import ( + "context" + + "github.com/safedep/dry/log" ++ "github.com/safedep/pmg/config" + "github.com/safedep/pmg/internal/flows" + "github.com/safedep/pmg/internal/ui" + "github.com/safedep/pmg/packagemanager" +@@ -31,6 +32,20 @@ func executeNpmFlow(ctx context.Context, args []string) error { + if err != nil { + ui.Fatalf("Failed to create npm package manager proxy: %s", err) + } ++ config, err := config.FromContext(ctx) ++ if err != nil { ++ ui.Fatalf("Failed to get config: %s", err) ++ } ++ ++ packageResolverConfig := packagemanager.NewDefaultNpmDependencyResolverConfig() ++ packageResolverConfig.IncludeTransitiveDependencies = config.Transitive ++ packageResolverConfig.TransitiveDepth = config.TransitiveDepth ++ packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies ++ ++ packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig) ++ if err != nil { ++ ui.Fatalf("Failed to create dependency resolver: %s", err) ++ } + +- return flows.Common(packageManager).Run(ctx, args) ++ return flows.Common(packageManager, packageResolver, config).Run(ctx, args) + } +diff --git a/cmd/npm/pnpm.go b/cmd/npm/pnpm.go +index c161c0d..a010244 100644 +--- a/cmd/npm/pnpm.go ++++ b/cmd/npm/pnpm.go +@@ -5,6 +5,7 @@ import ( + _ "embed" + + "github.com/safedep/dry/log" ++ "github.com/safedep/pmg/config" + "github.com/safedep/pmg/internal/flows" + "github.com/safedep/pmg/internal/ui" + "github.com/safedep/pmg/packagemanager" +@@ -32,6 +33,17 @@ func executePnpmFlow(ctx context.Context, args []string) error { + if err != nil { + ui.Fatalf("Failed to create pnpm package manager proxy: %s", err) + } ++ config, err := config.FromContext(ctx) ++ if err != nil { ++ ui.Fatalf("Failed to get config: %s", err) ++ } ++ ++ packageResolverConfig := packagemanager.NewDefaultNpmDependencyResolverConfig() ++ packageResolverConfig.IncludeTransitiveDependencies = config.Transitive ++ packageResolverConfig.TransitiveDepth = config.TransitiveDepth ++ packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies ++ ++ packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig) + +- return flows.Common(packageManager).Run(ctx, args) ++ return flows.Common(packageManager, packageResolver, config).Run(ctx, args) + } +diff --git a/cmd/pypi/pip.go b/cmd/pypi/pip.go +index 56d03f7..4cc9a65 100644 +--- a/cmd/pypi/pip.go ++++ b/cmd/pypi/pip.go +@@ -6,6 +6,7 @@ import ( + + "github.com/safedep/dry/log" + "github.com/safedep/pmg/config" ++ "github.com/safedep/pmg/internal/flows" + "github.com/safedep/pmg/internal/ui" + "github.com/safedep/pmg/packagemanager" + "github.com/spf13/cobra" +@@ -17,12 +18,7 @@ func NewPipCommand() *cobra.Command { + Short: "Guard pip package manager", + DisableFlagParsing: true, + RunE: func(cmd *cobra.Command, args []string) error { +- config, err := config.FromContext(cmd.Context()) +- if err != nil { +- ui.Fatalf("Failed to get config: %s", err) +- } +- +- err = executePipFlow(cmd.Context(), config, args) ++ err := executePipFlow(cmd.Context(), args) + if err != nil { + log.Errorf("Failed to execute pip flow: %s", err) + } +@@ -32,12 +28,24 @@ func NewPipCommand() *cobra.Command { + } + } + +-func executePipFlow(context context.Context, config config.Config, args []string) error { ++func executePipFlow(ctx context.Context, args []string) error { + packageManager, err := packagemanager.NewPipPackageManager(packagemanager.DefaultPipPackageManagerConfig()) + if err != nil { + return fmt.Errorf("failed to create pip package manager: %w", err) + } +- cmd, _ := packageManager.ParseCommand(args) +- fmt.Println("Cmd: ", cmd.InstallTargets[0]) +- return nil ++ config, err := config.FromContext(ctx) ++ if err != nil { ++ ui.Fatalf("Failed to get config: %s", err) ++ } ++ packageResolverConfig := packagemanager.NewDefaultPypiDependencyResolverConfig() ++ packageResolverConfig.IncludeTransitiveDependencies = config.Transitive ++ packageResolverConfig.TransitiveDepth = config.TransitiveDepth ++ packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies ++ ++ packageResolver, err := packagemanager.NewPypiDependencyResolver(packageResolverConfig) ++ if err != nil { ++ ui.Fatalf("Failed to create dependency resolver: %s", err) ++ } ++ ++ return flows.Common(packageManager, packageResolver, config).Run(ctx, args) + } +diff --git a/internal/flows/common.go b/internal/flows/common.go +index a8cbaae..dbc82ca 100644 +--- a/internal/flows/common.go ++++ b/internal/flows/common.go +@@ -11,37 +11,26 @@ import ( + ) + + type commonFlow struct { +- pm packagemanager.PackageManager ++ pm packagemanager.PackageManager ++ packageResolver packagemanager.PackageResolver ++ config config.Config + } + + // Creates a common flow of execution for all package managers. This should work for most + // of the cases unless a package manager has its own unique requirements. Configuration + // should be passed through the context (Global Config) +-func Common(pm packagemanager.PackageManager) *commonFlow { ++func Common(pm packagemanager.PackageManager, pkgResolver packagemanager.PackageResolver, config config.Config) *commonFlow { + return &commonFlow{ +- pm: pm, ++ pm: pm, ++ packageResolver: pkgResolver, ++ config: config, + } + } + + func (f *commonFlow) Run(ctx context.Context, args []string) error { +- config, err := config.FromContext(ctx) +- if err != nil { +- ui.Fatalf("Failed to get config: %s", err) +- } +- +- packageResolverConfig := packagemanager.NewDefaultNpmDependencyResolverConfig() +- packageResolverConfig.IncludeTransitiveDependencies = config.Transitive +- packageResolverConfig.TransitiveDepth = config.TransitiveDepth +- packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies +- +- packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig) +- if err != nil { +- ui.Fatalf("Failed to create dependency resolver: %s", err) +- } +- + var analyzers []analyzer.PackageVersionAnalyzer + +- if config.Paranoid { ++ if f.config.Paranoid { + malysisActiveScanAnalyzer, err := analyzer.NewMalysisActiveScanAnalyzer(analyzer.DefaultMalysisActiveScanAnalyzerConfig()) + if err != nil { + ui.Fatalf("Failed to create malware analyzer: %s", err) +@@ -65,9 +54,9 @@ func (f *commonFlow) Run(ctx context.Context, args []string) error { + } + + guardConfig := guard.DefaultPackageManagerGuardConfig() +- guardConfig.DryRun = config.DryRun ++ guardConfig.DryRun = f.config.DryRun + +- proxy, err := guard.NewPackageManagerGuard(guardConfig, f.pm, packageResolver, analyzers, interaction) ++ proxy, err := guard.NewPackageManagerGuard(guardConfig, f.pm, f.packageResolver, analyzers, interaction) + if err != nil { + ui.Fatalf("Failed to create package manager guard: %s", err) + } +diff --git a/packagemanager/dependency_resolver.go b/packagemanager/dependency_resolver.go +index 1f3c7a1..2d48a8f 100644 +--- a/packagemanager/dependency_resolver.go ++++ b/packagemanager/dependency_resolver.go +@@ -13,7 +13,7 @@ import ( + + // Contract for a function that implements ecosystem specific version + // resolver from a version range specification. +-type versionSpecResolver func(version string) string ++type versionSpecResolver func(packageName, version string) string + + type dependencyResolverConfig struct { + IncludeDevDependencies bool +@@ -38,7 +38,7 @@ func newDependencyResolver(client packageregistry.Client, config dependencyResol + + if versionSpecResolver == nil { + // Default version spec resolver +- versionSpecResolver = func(version string) string { ++ versionSpecResolver = func(packageName, version string) string { + return version + } + } +@@ -141,7 +141,7 @@ func (r *dependencyResolver) resolvePackageDependenciesConcurrent( + Ecosystem: packageVersion.GetPackage().GetEcosystem(), + Name: dependency.Name, + }, +- Version: r.versionSpecResolver(dependency.VersionSpec), ++ Version: r.versionSpecResolver(dependency.Name, dependency.VersionSpec), + }) + } + +diff --git a/packagemanager/npm_resolver.go b/packagemanager/npm_resolver.go +index 68293a4..dd46c2c 100644 +--- a/packagemanager/npm_resolver.go ++++ b/packagemanager/npm_resolver.go +@@ -78,7 +78,9 @@ func (r *npmDependencyResolver) ResolveDependencies(ctx context.Context, + TransitiveDepth: r.config.TransitiveDepth, + FailFast: r.config.FailFast, + MaxConcurrency: r.config.MaxConcurrency, +- }, npmCleanVersion) ++ }, func(packageName, version string) string { ++ return npmCleanVersion(version) ++ }) + + return resolver.resolveDependencies(ctx, packageVersion) + } +diff --git a/packagemanager/pypi.go b/packagemanager/pypi.go +index 615a664..08a79cf 100644 +--- a/packagemanager/pypi.go ++++ b/packagemanager/pypi.go +@@ -83,8 +83,6 @@ func (pip *pipPackageManager) ParseCommand(args []string) (*ParsedCommand, error + } + } + +- fmt.Printf("Package Name: %s Version: %s\n", packageName, version) +- + installTargets = append(installTargets, &PackageInstallTarget{ + PackageVersion: &packagev1.PackageVersion{ + Package: &packagev1.Package{ +diff --git a/packagemanager/pypi_resolver.go b/packagemanager/pypi_resolver.go +index dd6d22a..5e952bb 100644 +--- a/packagemanager/pypi_resolver.go ++++ b/packagemanager/pypi_resolver.go +@@ -2,11 +2,8 @@ package packagemanager + + import ( + "context" +- "encoding/json" + "fmt" +- "net/http" + "strings" +- "time" + + packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" + "github.com/Masterminds/semver" +@@ -62,6 +59,13 @@ func (p *pypiDependencyResolver) ResolveDependencies(ctx context.Context, pkg *p + TransitiveDepth: p.config.TransitiveDepth, + FailFast: p.config.FailFast, + MaxConcurrency: p.config.MaxConcurrency, ++ }, func(packageName, version string) string { ++ ver, err := pipGetMatchingVersion(packageName, version) ++ if err != nil { ++ log.Debugf("error getting matching version for %s@%s", packageName, version) ++ return "" ++ } ++ return ver + }) + + return resolver.resolveDependencies(ctx, pkg) +@@ -74,6 +78,7 @@ func (p *pypiDependencyResolver) ResolveLatestVersion(ctx context.Context, pkg * + } + + pkgInfo, err := pd.GetPackage(pkg.Name) ++ fmt.Println("Package Info version: ", pkgInfo.LatestVersion, " Error: ", err) + if err != nil { + return nil, fmt.Errorf("failed to get package: %w", err) + } +@@ -85,13 +90,6 @@ func (p *pypiDependencyResolver) ResolveLatestVersion(ctx context.Context, pkg * + }, nil + } + +-// PyPIPackage represents the package information from PyPI +-type PyPIPackage struct { +- Releases map[string]any `json:"releases"` +-} +- +-var httpClient = &http.Client{Timeout: 10 * time.Second} +- + func pipGetMatchingVersion(packageName, versionConstraint string) (string, error) { + // Already a exact version + if strings.HasPrefix(versionConstraint, "==") { +@@ -103,8 +101,18 @@ func pipGetMatchingVersion(packageName, versionConstraint string) (string, error + versionConstraint = pipConvertCompatibleRelease(versionConstraint) + } + ++ registry, err := packageregistry.NewPypiAdapter() ++ if err != nil { ++ return "", fmt.Errorf("failed to create pypi adapter: %w", err) ++ } ++ ++ pd, err := registry.PackageDiscovery() ++ if err != nil { ++ return "", fmt.Errorf("failed to get package discovery: %w", err) ++ } ++ + // Get package info from PyPI +- pkg, err := pipFetchPackageVersionsInfo(packageName) ++ pkg, err := pd.GetPackage(packageName) + if err != nil { + return "", err + } +@@ -116,7 +124,7 @@ func pipGetMatchingVersion(packageName, versionConstraint string) (string, error + } + + // Get valid versions and find best match +- bestMatch, err := findBestMatchingVersion(pkg.Releases, constraint) ++ bestMatch, err := findBestMatchingVersion(pkg.Versions, constraint) + if err != nil { + return "", fmt.Errorf("no version matches constraint %q: %w", versionConstraint, err) + } +@@ -124,36 +132,15 @@ func pipGetMatchingVersion(packageName, versionConstraint string) (string, error + return bestMatch.Original(), nil + } + +-// pipFetchPackageVersionsInfo retrieves package information from PyPI +-func pipFetchPackageVersionsInfo(packageName string) (*PyPIPackage, error) { +- url := fmt.Sprintf("https://pypi.org/pypi/%s/json", packageName) +- resp, err := httpClient.Get(url) +- if err != nil { +- return nil, fmt.Errorf("failed to fetch package info: %w", err) +- } +- defer resp.Body.Close() +- +- if resp.StatusCode != 200 { +- return nil, fmt.Errorf("package not found or HTTP error: %d", resp.StatusCode) +- } +- +- var pypiPkg PyPIPackage +- if err := json.NewDecoder(resp.Body).Decode(&pypiPkg); err != nil { +- return nil, fmt.Errorf("failed to parse JSON: %w", err) +- } +- +- return &pypiPkg, nil +-} +- +-func findBestMatchingVersion(releases map[string]any, constraint *semver.Constraints) (*semver.Version, error) { ++func findBestMatchingVersion(releases []packageregistry.PackageVersionInfo, constraint *semver.Constraints) (*semver.Version, error) { + if len(releases) == 0 { + return nil, fmt.Errorf("no versions available") + } + + var bestMatch *semver.Version + // We'll iterate once through all versions +- for v := range releases { +- ver, err := semver.NewVersion(v) ++ for _, v := range releases { ++ ver, err := semver.NewVersion(v.Version) + if err != nil { + continue // Skip invalid versions + } diff --git a/internal/flows/common.go b/internal/flows/common.go index a8cbaae..dbc82ca 100644 --- a/internal/flows/common.go +++ b/internal/flows/common.go @@ -11,37 +11,26 @@ import ( ) type commonFlow struct { - pm packagemanager.PackageManager + pm packagemanager.PackageManager + packageResolver packagemanager.PackageResolver + config config.Config } // Creates a common flow of execution for all package managers. This should work for most // of the cases unless a package manager has its own unique requirements. Configuration // should be passed through the context (Global Config) -func Common(pm packagemanager.PackageManager) *commonFlow { +func Common(pm packagemanager.PackageManager, pkgResolver packagemanager.PackageResolver, config config.Config) *commonFlow { return &commonFlow{ - pm: pm, + pm: pm, + packageResolver: pkgResolver, + config: config, } } func (f *commonFlow) Run(ctx context.Context, args []string) error { - config, err := config.FromContext(ctx) - if err != nil { - ui.Fatalf("Failed to get config: %s", err) - } - - packageResolverConfig := packagemanager.NewDefaultNpmDependencyResolverConfig() - packageResolverConfig.IncludeTransitiveDependencies = config.Transitive - packageResolverConfig.TransitiveDepth = config.TransitiveDepth - packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies - - packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig) - if err != nil { - ui.Fatalf("Failed to create dependency resolver: %s", err) - } - var analyzers []analyzer.PackageVersionAnalyzer - if config.Paranoid { + if f.config.Paranoid { malysisActiveScanAnalyzer, err := analyzer.NewMalysisActiveScanAnalyzer(analyzer.DefaultMalysisActiveScanAnalyzerConfig()) if err != nil { ui.Fatalf("Failed to create malware analyzer: %s", err) @@ -65,9 +54,9 @@ func (f *commonFlow) Run(ctx context.Context, args []string) error { } guardConfig := guard.DefaultPackageManagerGuardConfig() - guardConfig.DryRun = config.DryRun + guardConfig.DryRun = f.config.DryRun - proxy, err := guard.NewPackageManagerGuard(guardConfig, f.pm, packageResolver, analyzers, interaction) + proxy, err := guard.NewPackageManagerGuard(guardConfig, f.pm, f.packageResolver, analyzers, interaction) if err != nil { ui.Fatalf("Failed to create package manager guard: %s", err) } diff --git a/packagemanager/dependency_resolver.go b/packagemanager/dependency_resolver.go index 1f3c7a1..2d48a8f 100644 --- a/packagemanager/dependency_resolver.go +++ b/packagemanager/dependency_resolver.go @@ -13,7 +13,7 @@ import ( // Contract for a function that implements ecosystem specific version // resolver from a version range specification. -type versionSpecResolver func(version string) string +type versionSpecResolver func(packageName, version string) string type dependencyResolverConfig struct { IncludeDevDependencies bool @@ -38,7 +38,7 @@ func newDependencyResolver(client packageregistry.Client, config dependencyResol if versionSpecResolver == nil { // Default version spec resolver - versionSpecResolver = func(version string) string { + versionSpecResolver = func(packageName, version string) string { return version } } @@ -141,7 +141,7 @@ func (r *dependencyResolver) resolvePackageDependenciesConcurrent( Ecosystem: packageVersion.GetPackage().GetEcosystem(), Name: dependency.Name, }, - Version: r.versionSpecResolver(dependency.VersionSpec), + Version: r.versionSpecResolver(dependency.Name, dependency.VersionSpec), }) } diff --git a/packagemanager/npm_resolver.go b/packagemanager/npm_resolver.go index 68293a4..dd46c2c 100644 --- a/packagemanager/npm_resolver.go +++ b/packagemanager/npm_resolver.go @@ -78,7 +78,9 @@ func (r *npmDependencyResolver) ResolveDependencies(ctx context.Context, TransitiveDepth: r.config.TransitiveDepth, FailFast: r.config.FailFast, MaxConcurrency: r.config.MaxConcurrency, - }, npmCleanVersion) + }, func(packageName, version string) string { + return npmCleanVersion(version) + }) return resolver.resolveDependencies(ctx, packageVersion) } diff --git a/packagemanager/pypi.go b/packagemanager/pypi.go index 615a664..08a79cf 100644 --- a/packagemanager/pypi.go +++ b/packagemanager/pypi.go @@ -83,8 +83,6 @@ func (pip *pipPackageManager) ParseCommand(args []string) (*ParsedCommand, error } } - fmt.Printf("Package Name: %s Version: %s\n", packageName, version) - installTargets = append(installTargets, &PackageInstallTarget{ PackageVersion: &packagev1.PackageVersion{ Package: &packagev1.Package{ diff --git a/packagemanager/pypi_resolver.go b/packagemanager/pypi_resolver.go index dd6d22a..5e952bb 100644 --- a/packagemanager/pypi_resolver.go +++ b/packagemanager/pypi_resolver.go @@ -2,11 +2,8 @@ package packagemanager import ( "context" - "encoding/json" "fmt" - "net/http" "strings" - "time" packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" "github.com/Masterminds/semver" @@ -62,6 +59,13 @@ func (p *pypiDependencyResolver) ResolveDependencies(ctx context.Context, pkg *p TransitiveDepth: p.config.TransitiveDepth, FailFast: p.config.FailFast, MaxConcurrency: p.config.MaxConcurrency, + }, func(packageName, version string) string { + ver, err := pipGetMatchingVersion(packageName, version) + if err != nil { + log.Debugf("error getting matching version for %s@%s", packageName, version) + return "" + } + return ver }) return resolver.resolveDependencies(ctx, pkg) @@ -74,6 +78,7 @@ func (p *pypiDependencyResolver) ResolveLatestVersion(ctx context.Context, pkg * } pkgInfo, err := pd.GetPackage(pkg.Name) + fmt.Println("Package Info version: ", pkgInfo.LatestVersion, " Error: ", err) if err != nil { return nil, fmt.Errorf("failed to get package: %w", err) } @@ -85,13 +90,6 @@ func (p *pypiDependencyResolver) ResolveLatestVersion(ctx context.Context, pkg * }, nil } -// PyPIPackage represents the package information from PyPI -type PyPIPackage struct { - Releases map[string]any `json:"releases"` -} - -var httpClient = &http.Client{Timeout: 10 * time.Second} - func pipGetMatchingVersion(packageName, versionConstraint string) (string, error) { // Already a exact version if strings.HasPrefix(versionConstraint, "==") { @@ -103,8 +101,18 @@ func pipGetMatchingVersion(packageName, versionConstraint string) (string, error versionConstraint = pipConvertCompatibleRelease(versionConstraint) } + registry, err := packageregistry.NewPypiAdapter() + if err != nil { + return "", fmt.Errorf("failed to create pypi adapter: %w", err) + } + + pd, err := registry.PackageDiscovery() + if err != nil { + return "", fmt.Errorf("failed to get package discovery: %w", err) + } + // Get package info from PyPI - pkg, err := pipFetchPackageVersionsInfo(packageName) + pkg, err := pd.GetPackage(packageName) if err != nil { return "", err } @@ -116,7 +124,7 @@ func pipGetMatchingVersion(packageName, versionConstraint string) (string, error } // Get valid versions and find best match - bestMatch, err := findBestMatchingVersion(pkg.Releases, constraint) + bestMatch, err := findBestMatchingVersion(pkg.Versions, constraint) if err != nil { return "", fmt.Errorf("no version matches constraint %q: %w", versionConstraint, err) } @@ -124,36 +132,15 @@ func pipGetMatchingVersion(packageName, versionConstraint string) (string, error return bestMatch.Original(), nil } -// pipFetchPackageVersionsInfo retrieves package information from PyPI -func pipFetchPackageVersionsInfo(packageName string) (*PyPIPackage, error) { - url := fmt.Sprintf("https://pypi.org/pypi/%s/json", packageName) - resp, err := httpClient.Get(url) - if err != nil { - return nil, fmt.Errorf("failed to fetch package info: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("package not found or HTTP error: %d", resp.StatusCode) - } - - var pypiPkg PyPIPackage - if err := json.NewDecoder(resp.Body).Decode(&pypiPkg); err != nil { - return nil, fmt.Errorf("failed to parse JSON: %w", err) - } - - return &pypiPkg, nil -} - -func findBestMatchingVersion(releases map[string]any, constraint *semver.Constraints) (*semver.Version, error) { +func findBestMatchingVersion(releases []packageregistry.PackageVersionInfo, constraint *semver.Constraints) (*semver.Version, error) { if len(releases) == 0 { return nil, fmt.Errorf("no versions available") } var bestMatch *semver.Version // We'll iterate once through all versions - for v := range releases { - ver, err := semver.NewVersion(v) + for _, v := range releases { + ver, err := semver.NewVersion(v.Version) if err != nil { continue // Skip invalid versions }