mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat/#55 bun support (#56)
* add support for bun package manager * update readme
This commit is contained in:
@@ -63,6 +63,7 @@ pnpm add <package-name>
|
||||
- [Usage](#usage)
|
||||
- [Recommended: Automated Setup](#recommended)
|
||||
- [Alternative: Manual Commands](#alternative)
|
||||
- [Lockfile Installation](#lockfile)
|
||||
- [Silent Mode](#silent-mode)
|
||||
- [Dry Run](#dry-run)
|
||||
- [Verbose Mode](#verbose-mode)
|
||||
@@ -87,10 +88,11 @@ PMG supports the following package managers:
|
||||
| --------------- | --------- | --------------------------- |
|
||||
| `npm` | ✅ Active | `pmg npm install <package>` |
|
||||
| `pnpm` | ✅ Active | `pmg pnpm add <package>` |
|
||||
| `bun` | ✅ Active | `pmg bun add <package>` |
|
||||
| `pip` | ✅ Active | `pmg pip install <package>` |
|
||||
| `uv` | 🚧 Planned | |
|
||||
| `yarn` | 🚧 Planned | |
|
||||
| `poetry` | 🚧 Planned | |
|
||||
| `uv` | 🚧 Planned | |
|
||||
|
||||
> Want us to support your favorite package manager? [Open an issue](https://github.com/safedep/pmg/issues) and let us know!
|
||||
|
||||
@@ -164,6 +166,7 @@ After setup, use your package managers normally:
|
||||
```bash
|
||||
npm install <package-name>
|
||||
pnpm add <package-name>
|
||||
bun add <package-name>
|
||||
pip install <package-name>
|
||||
```
|
||||
|
||||
@@ -173,9 +176,23 @@ You can also run PMG manually without aliases:
|
||||
```bash
|
||||
pmg npm install <package-name>
|
||||
pmg pnpm add <package-name>
|
||||
pmg bun add <package-name>
|
||||
pmg pip install <package-name>
|
||||
```
|
||||
|
||||
### Lockfile Installation
|
||||
PMG seamlessly protects lockfile-based installations:
|
||||
|
||||
```bash
|
||||
npm install # Uses package-lock.json
|
||||
pnpm install # Uses pnpm-lock.yaml
|
||||
bun install # Uses bun.lock
|
||||
|
||||
pip install -r requirements.txt # Uses requirements file
|
||||
```
|
||||
|
||||
PMG scans the exact package versions specified in lockfiles and blocks installation if malicious packages are detected.
|
||||
|
||||
### Silent Mode
|
||||
|
||||
Use the `--silent` flag to run PMG in silent mode:
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package npm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/safedep/dry/log"
|
||||
"github.com/safedep/pmg/config"
|
||||
"github.com/safedep/pmg/internal/analytics"
|
||||
"github.com/safedep/pmg/internal/flows"
|
||||
"github.com/safedep/pmg/internal/ui"
|
||||
"github.com/safedep/pmg/packagemanager"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewBunCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "bun [action] [package]",
|
||||
Short: "Guard bun package manager",
|
||||
DisableFlagParsing: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := executeBunFlow(cmd.Context(), args)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to execute bun flow: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func executeBunFlow(ctx context.Context, args []string) error {
|
||||
analytics.TrackCommandBun()
|
||||
packageManager, err := packagemanager.NewNpmPackageManager(packagemanager.DefaultBunPackageManagerConfig())
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to create bun package manager proxy: %s", err)
|
||||
}
|
||||
|
||||
config, err := config.FromContext(ctx)
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to get config: %s", err)
|
||||
}
|
||||
|
||||
parsedCommand, err := packageManager.ParseCommand(args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse command: %w", 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, packageResolver, config).Run(ctx, args, parsedCommand)
|
||||
}
|
||||
@@ -25,6 +25,7 @@ const (
|
||||
Npm PackageManagerName = "npm"
|
||||
Pnpm PackageManagerName = "pnpm"
|
||||
Pip PackageManagerName = "pip"
|
||||
Bun PackageManagerName = "bun"
|
||||
)
|
||||
|
||||
type ExtractorManager struct {
|
||||
@@ -37,6 +38,7 @@ func NewExtractorManager() *ExtractorManager {
|
||||
Npm: &NpmExtractor{},
|
||||
Pnpm: &PnpmExtractor{},
|
||||
Pip: &PipExtractor{},
|
||||
Bun: &BunExtractor{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
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"
|
||||
@@ -123,3 +124,58 @@ func parsePnpmLockFile(lockfilePath, scanDir string) ([]*packagev1.PackageVersio
|
||||
|
||||
return packages, nil
|
||||
}
|
||||
|
||||
type BunExtractor struct{}
|
||||
|
||||
func (n *BunExtractor) GetSupportedFiles() []string {
|
||||
return []string{"bun.lock"}
|
||||
}
|
||||
|
||||
func (n *BunExtractor) GetEcosystem() packagev1.Ecosystem {
|
||||
return packagev1.Ecosystem_ECOSYSTEM_NPM
|
||||
}
|
||||
|
||||
func (n *BunExtractor) GetPackageManager() PackageManagerName {
|
||||
return Bun
|
||||
}
|
||||
|
||||
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: %s", 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: %s", 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
|
||||
}
|
||||
|
||||
@@ -180,6 +180,7 @@ require (
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tdakkota/asciicheck v0.4.1 // indirect
|
||||
github.com/tetafro/godot v1.5.0 // indirect
|
||||
github.com/tidwall/jsonc v0.3.2 // indirect
|
||||
github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect
|
||||
github.com/timonwong/loggercheck v0.10.1 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect
|
||||
|
||||
@@ -448,6 +448,8 @@ github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpR
|
||||
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=
|
||||
github.com/tetafro/godot v1.5.0 h1:aNwfVI4I3+gdxjMgYPus9eHmoBeJIbnajOyqZYStzuw=
|
||||
github.com/tetafro/godot v1.5.0/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio=
|
||||
github.com/tidwall/jsonc v0.3.2 h1:ZTKrmejRlAJYdn0kcaFqRAKlxxFIC21pYq8vLa4p2Wc=
|
||||
github.com/tidwall/jsonc v0.3.2/go.mod h1:dw+3CIxqHi+t8eFSpzzMlcVYxKp08UP5CD8/uSFCyJE=
|
||||
github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 h1:y4mJRFlM6fUyPhoXuFg/Yu02fg/nIPFMOY8tOqppoFg=
|
||||
github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460=
|
||||
github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg=
|
||||
|
||||
@@ -3,6 +3,7 @@ package analytics
|
||||
const (
|
||||
eventRun = "pmg_command_run"
|
||||
eventCommandNpm = "pmg_command_npm"
|
||||
eventCommandBun = "pmg_command_bun"
|
||||
eventCommandPnpm = "pmg_command_pnpm"
|
||||
eventCommandPip = "pmg_command_pip"
|
||||
|
||||
@@ -19,6 +20,10 @@ func TrackCommandNpm() {
|
||||
TrackEvent(eventCommandNpm)
|
||||
}
|
||||
|
||||
func TrackCommandBun() {
|
||||
TrackEvent(eventCommandBun)
|
||||
}
|
||||
|
||||
func TrackCommandPnpm() {
|
||||
TrackEvent(eventCommandPnpm)
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ func main() {
|
||||
|
||||
cmd.AddCommand(npm.NewNpmCommand())
|
||||
cmd.AddCommand(npm.NewPnpmCommand())
|
||||
cmd.AddCommand(npm.NewBunCommand())
|
||||
cmd.AddCommand(pypi.NewPipCommand())
|
||||
cmd.AddCommand(version.NewVersionCommand())
|
||||
cmd.AddCommand(setup.NewSetupCommand())
|
||||
|
||||
@@ -27,6 +27,13 @@ func DefaultPnpmPackageManagerConfig() NpmPackageManagerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultBunPackageManagerConfig() NpmPackageManagerConfig {
|
||||
return NpmPackageManagerConfig{
|
||||
InstallCommands: []string{"install", "i", "add"},
|
||||
CommandName: "bun",
|
||||
}
|
||||
}
|
||||
|
||||
type npmPackageManager struct {
|
||||
Config NpmPackageManagerConfig
|
||||
}
|
||||
@@ -48,7 +55,7 @@ func (npm *npmPackageManager) Ecosystem() packagev1.Ecosystem {
|
||||
}
|
||||
|
||||
func (npm *npmPackageManager) ParseCommand(args []string) (*ParsedCommand, error) {
|
||||
if len(args) > 0 && (args[0] == "npm" || args[0] == "pnpm") {
|
||||
if len(args) > 0 && (args[0] == "npm" || args[0] == "pnpm" || args[0] == "bun") {
|
||||
args = args[1:]
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user