package packagemanager import ( "context" "slices" "strings" packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" ) type Command struct { Exe string Args []string } type PackageInstallTarget struct { PackageVersion *packagev1.PackageVersion // Extras specifies additional features to be installed with a Python package // Example: "django[mysql,redis]" has Extras as ["mysql", "redis"] // Currently only specific to Python packages Extras []string // IsExplicitVersion indicates the user provided an explicit version constraint // (e.g. ==1.2.3) as opposed to the version being auto-resolved by the resolver. IsExplicitVersion bool } func (pit *PackageInstallTarget) HasVersion() bool { return pit.PackageVersion != nil && pit.PackageVersion.GetVersion() != "" } type ParsedCommand struct { // Original command Command Command // Parsed install target if this is an install command InstallTargets []*PackageInstallTarget // IsManifestInstall indicates if this is a manifest-based installation // (e.g., npm install, pip install -r requirements.txt) IsManifestInstall bool // ManifestFiles contains the list of manifest files to install from // (e.g., ["requirements.txt"] for pip install -r requirements.txt) ManifestFiles []string // IsKnownNonDownloadCommand is true for commands that are known to not download packages // (e.g., npm ls, pip list, yarn why). Used by the proxy to decide whether to skip // interception when proxy.install_only is enabled. Unknown commands default to false so // the proxy runs — fail safe when a new subcommand is added to a package manager. IsKnownNonDownloadCommand bool } // IsInstallationCommand returns true if command installs packages (explicit targets or from manifest). // This is used by guard mode where we need to know which packages are being installed. func (pc *ParsedCommand) IsInstallationCommand() bool { return pc.HasInstallTarget() || pc.HasManifestInstall() } // MayDownloadPackages returns true if the command may download packages from a registry. // Returns false only for commands explicitly known to be non-download (e.g., npm ls, pip list). // Unknown commands return true by default — fail safe when new package manager subcommands appear. func (pc *ParsedCommand) MayDownloadPackages() bool { return !pc.IsKnownNonDownloadCommand } func (pc *ParsedCommand) HasInstallTarget() bool { return len(pc.InstallTargets) > 0 } func (pc *ParsedCommand) HasManifestInstall() bool { return pc.IsManifestInstall } func (pc *ParsedCommand) ShouldExtractFromManifest() bool { return pc.IsManifestInstall && !pc.HasInstallTarget() } // IsFirstNonFlagArgInList checks if the first non-flag argument in args is in the given list. // Only the first non-flag arg (the subcommand) is checked to avoid false positives when package // names or script arguments happen to match a known command. func IsFirstNonFlagArgInList(args []string, nonDownloadCmds []string) bool { for _, arg := range args { if strings.HasPrefix(arg, "-") { continue } return slices.Contains(nonDownloadCmds, arg) } return false } // PackageManager is the contract for implementing a package manager type PackageManager interface { // Name of the package manager implementation Name() string // ParseCommand parses the command and returns a parsed command // specific to the package manager implementation ParseCommand(args []string) (*ParsedCommand, error) // Ecosystem of the package manager Ecosystem() packagev1.Ecosystem } // PackageResolver is the contract for resolving package info type PackageResolver interface { // ResolveLatestVersion resolves the latest version for a given package ResolveLatestVersion(context.Context, *packagev1.Package) (*packagev1.PackageVersion, error) // ResolveDependencies resolves the dependencies for a given package version // It returns a flattened list of all the dependencies based on implementation // specific config. The version resolution is based on minimum version selection // for a given version range. ResolveDependencies(context.Context, *packagev1.PackageVersion) ([]*packagev1.PackageVersion, error) }