mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat/#28 uv support (#62)
* follow proper consistent naming in pypi packagemanager * follow proper consistent naming in pypi packagemanager - 2 * feat: add specialized command parsers to handle pip and uv command formats * add uv support & modify extractor to be more robust * add uv alias * refactor var name & add error handling * update readme & add support for `uv pip sync` cmd
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
package extractor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
|
||||
"github.com/google/osv-scalibr/extractor/filesystem"
|
||||
"github.com/google/osv-scalibr/extractor/filesystem/language/javascript/bunlock"
|
||||
"github.com/google/osv-scalibr/extractor/filesystem/language/javascript/packagelockjson"
|
||||
"github.com/google/osv-scalibr/extractor/filesystem/language/javascript/pnpmlock"
|
||||
"github.com/google/osv-scalibr/extractor/filesystem/language/python/requirements"
|
||||
"github.com/google/osv-scalibr/extractor/filesystem/language/python/uvlock"
|
||||
"github.com/google/osv-scalibr/fs"
|
||||
)
|
||||
|
||||
func getExtractorForFile(filename string) (filesystem.Extractor, error) {
|
||||
filename = filepath.Base(filename)
|
||||
|
||||
// Regex for requirements files (match requirements.txt and requirements-{word}.txt)
|
||||
reqPattern := regexp.MustCompile(`^requirements(?:-\w+)?\.txt$`)
|
||||
|
||||
switch {
|
||||
case filename == "package-lock.json":
|
||||
return packagelockjson.NewDefault(), nil
|
||||
case filename == "pnpm-lock.yaml":
|
||||
return pnpmlock.New(), nil
|
||||
case filename == "bun.lock":
|
||||
return bunlock.New(), nil
|
||||
case reqPattern.MatchString(filename):
|
||||
return requirements.NewDefault(), nil
|
||||
case filename == "uv.lock":
|
||||
return uvlock.New(), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported lockfile type: %s", filename)
|
||||
}
|
||||
}
|
||||
|
||||
func parseLockfile(lockfilePath, scanDir string, ecosystem packagev1.Ecosystem) ([]*packagev1.PackageVersion, error) {
|
||||
extractor, err := getExtractorForFile(lockfilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file, err := os.Open(lockfilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open lockfile: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
inputConfig := &filesystem.ScanInput{
|
||||
FS: fs.DirFS(scanDir),
|
||||
Path: lockfilePath,
|
||||
Reader: file,
|
||||
}
|
||||
|
||||
inventory, err := extractor.Extract(context.Background(), inputConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract packages: %w", err)
|
||||
}
|
||||
|
||||
var packages []*packagev1.PackageVersion
|
||||
|
||||
for _, invPkg := range inventory.Packages {
|
||||
pkg := &packagev1.PackageVersion{
|
||||
Package: &packagev1.Package{
|
||||
Name: invPkg.Name,
|
||||
Ecosystem: ecosystem,
|
||||
},
|
||||
Version: invPkg.Version,
|
||||
}
|
||||
|
||||
packages = append(packages, pkg)
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
}
|
||||
@@ -26,6 +26,7 @@ const (
|
||||
Pnpm PackageManagerName = "pnpm"
|
||||
Pip PackageManagerName = "pip"
|
||||
Bun PackageManagerName = "bun"
|
||||
Uv PackageManagerName = "uv"
|
||||
)
|
||||
|
||||
type ExtractorManager struct {
|
||||
@@ -39,6 +40,7 @@ func NewExtractorManager() *ExtractorManager {
|
||||
Pnpm: &PnpmExtractor{},
|
||||
Pip: &PipExtractor{},
|
||||
Bun: &BunExtractor{},
|
||||
Uv: &UvExtractor{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+3
-123
@@ -1,16 +1,7 @@
|
||||
package extractor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
|
||||
"github.com/google/osv-scalibr/extractor/filesystem"
|
||||
"github.com/google/osv-scalibr/extractor/filesystem/language/javascript/bunlock"
|
||||
"github.com/google/osv-scalibr/extractor/filesystem/language/javascript/packagelockjson"
|
||||
"github.com/google/osv-scalibr/extractor/filesystem/language/javascript/pnpmlock"
|
||||
"github.com/google/osv-scalibr/fs"
|
||||
)
|
||||
|
||||
// NpmExtractor handles package-lock.json files
|
||||
@@ -29,44 +20,7 @@ func (n *NpmExtractor) GetPackageManager() PackageManagerName {
|
||||
}
|
||||
|
||||
func (n *NpmExtractor) Extract(lockfilePath, scanDir string) ([]*packagev1.PackageVersion, error) {
|
||||
return parseNpmPackageLockFile(lockfilePath, scanDir)
|
||||
}
|
||||
|
||||
func parseNpmPackageLockFile(lockfilePath, scanDir string) ([]*packagev1.PackageVersion, error) {
|
||||
packagelockExtractor := packagelockjson.NewDefault()
|
||||
|
||||
file, err := os.Open(lockfilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open lockfile: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
inputConfig := &filesystem.ScanInput{
|
||||
FS: fs.DirFS(scanDir),
|
||||
Path: lockfilePath,
|
||||
Reader: file,
|
||||
}
|
||||
|
||||
inventory, err := packagelockExtractor.Extract(context.Background(), inputConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract packages: %w", err)
|
||||
}
|
||||
|
||||
var packages []*packagev1.PackageVersion
|
||||
|
||||
for _, invPkg := range inventory.Packages {
|
||||
pkg := &packagev1.PackageVersion{
|
||||
Package: &packagev1.Package{
|
||||
Name: invPkg.Name,
|
||||
Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM,
|
||||
},
|
||||
Version: invPkg.Version,
|
||||
}
|
||||
|
||||
packages = append(packages, pkg)
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
return parseLockfile(lockfilePath, scanDir, n.GetEcosystem())
|
||||
}
|
||||
|
||||
// PnpmExtractor handles pnpm-lock.yaml files
|
||||
@@ -85,44 +39,7 @@ func (p *PnpmExtractor) GetPackageManager() PackageManagerName {
|
||||
}
|
||||
|
||||
func (p *PnpmExtractor) Extract(lockfilePath, scanDir string) ([]*packagev1.PackageVersion, error) {
|
||||
return parsePnpmLockFile(lockfilePath, scanDir)
|
||||
}
|
||||
|
||||
func parsePnpmLockFile(lockfilePath, scanDir string) ([]*packagev1.PackageVersion, error) {
|
||||
pnpmLockExtractor := pnpmlock.New()
|
||||
|
||||
file, err := os.Open(lockfilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open lockfile: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
inputConfig := &filesystem.ScanInput{
|
||||
FS: fs.DirFS(scanDir),
|
||||
Path: lockfilePath,
|
||||
Reader: file,
|
||||
}
|
||||
|
||||
inventory, err := pnpmLockExtractor.Extract(context.Background(), inputConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract packages: %w", err)
|
||||
}
|
||||
|
||||
var packages []*packagev1.PackageVersion
|
||||
|
||||
for _, invPkg := range inventory.Packages {
|
||||
pkg := &packagev1.PackageVersion{
|
||||
Package: &packagev1.Package{
|
||||
Name: invPkg.Name,
|
||||
Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM,
|
||||
},
|
||||
Version: invPkg.Version,
|
||||
}
|
||||
|
||||
packages = append(packages, pkg)
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
return parseLockfile(lockfilePath, scanDir, p.GetEcosystem())
|
||||
}
|
||||
|
||||
type BunExtractor struct{}
|
||||
@@ -140,42 +57,5 @@ func (n *BunExtractor) GetPackageManager() PackageManagerName {
|
||||
}
|
||||
|
||||
func (n *BunExtractor) Extract(lockfilePath, scanDir string) ([]*packagev1.PackageVersion, error) {
|
||||
return parseBunPackageLockFile(lockfilePath, scanDir)
|
||||
}
|
||||
|
||||
func parseBunPackageLockFile(lockfilePath, scanDir string) ([]*packagev1.PackageVersion, error) {
|
||||
bunlockExtractor := bunlock.New()
|
||||
|
||||
file, err := os.Open(lockfilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open lockfile: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
inputConfig := &filesystem.ScanInput{
|
||||
FS: fs.DirFS(scanDir),
|
||||
Path: lockfilePath,
|
||||
Reader: file,
|
||||
}
|
||||
|
||||
inventory, err := bunlockExtractor.Extract(context.Background(), inputConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract packages: %w", err)
|
||||
}
|
||||
|
||||
var packages []*packagev1.PackageVersion
|
||||
|
||||
for _, invPkg := range inventory.Packages {
|
||||
pkg := &packagev1.PackageVersion{
|
||||
Package: &packagev1.Package{
|
||||
Name: invPkg.Name,
|
||||
Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM,
|
||||
},
|
||||
Version: invPkg.Version,
|
||||
}
|
||||
|
||||
packages = append(packages, pkg)
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
return parseLockfile(lockfilePath, scanDir, n.GetEcosystem())
|
||||
}
|
||||
|
||||
+14
-39
@@ -1,14 +1,7 @@
|
||||
package extractor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
|
||||
"github.com/google/osv-scalibr/extractor/filesystem"
|
||||
"github.com/google/osv-scalibr/extractor/filesystem/language/python/requirements"
|
||||
"github.com/google/osv-scalibr/fs"
|
||||
)
|
||||
|
||||
// PipExtractor handles requirements.txt files
|
||||
@@ -27,42 +20,24 @@ func (p *PipExtractor) GetPackageManager() PackageManagerName {
|
||||
}
|
||||
|
||||
func (n *PipExtractor) Extract(lockfilePath, scanDir string) ([]*packagev1.PackageVersion, error) {
|
||||
return parseRequirementsTxtFile(lockfilePath, scanDir)
|
||||
return parseLockfile(lockfilePath, scanDir, n.GetEcosystem())
|
||||
}
|
||||
|
||||
func parseRequirementsTxtFile(lockfilePath, scanDir string) ([]*packagev1.PackageVersion, error) {
|
||||
requirementsExtractor := requirements.NewDefault()
|
||||
// UvExtractor handles uv.lock files
|
||||
type UvExtractor struct{}
|
||||
|
||||
file, err := os.Open(lockfilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open lockfile: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
func (u *UvExtractor) GetSupportedFiles() []string {
|
||||
return []string{"uv.lock"}
|
||||
}
|
||||
|
||||
inputConfig := &filesystem.ScanInput{
|
||||
FS: fs.DirFS(scanDir),
|
||||
Path: lockfilePath,
|
||||
Reader: file,
|
||||
}
|
||||
func (u *UvExtractor) GetEcosystem() packagev1.Ecosystem {
|
||||
return packagev1.Ecosystem_ECOSYSTEM_PYPI
|
||||
}
|
||||
|
||||
inventory, err := requirementsExtractor.Extract(context.Background(), inputConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract packages: %w", err)
|
||||
}
|
||||
func (u *UvExtractor) GetPackageManager() PackageManagerName {
|
||||
return Uv
|
||||
}
|
||||
|
||||
var packages []*packagev1.PackageVersion
|
||||
|
||||
for _, invPkg := range inventory.Packages {
|
||||
pkg := &packagev1.PackageVersion{
|
||||
Package: &packagev1.Package{
|
||||
Name: invPkg.Name,
|
||||
Ecosystem: packagev1.Ecosystem_ECOSYSTEM_PYPI,
|
||||
},
|
||||
Version: invPkg.Version,
|
||||
}
|
||||
|
||||
packages = append(packages, pkg)
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
func (u *UvExtractor) Extract(lockfilePath, scanDir string) ([]*packagev1.PackageVersion, error) {
|
||||
return parseLockfile(lockfilePath, scanDir, u.GetEcosystem())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user