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)
|
- [Usage](#usage)
|
||||||
- [Recommended: Automated Setup](#recommended)
|
- [Recommended: Automated Setup](#recommended)
|
||||||
- [Alternative: Manual Commands](#alternative)
|
- [Alternative: Manual Commands](#alternative)
|
||||||
|
- [Lockfile Installation](#lockfile)
|
||||||
- [Silent Mode](#silent-mode)
|
- [Silent Mode](#silent-mode)
|
||||||
- [Dry Run](#dry-run)
|
- [Dry Run](#dry-run)
|
||||||
- [Verbose Mode](#verbose-mode)
|
- [Verbose Mode](#verbose-mode)
|
||||||
@@ -87,10 +88,11 @@ PMG supports the following package managers:
|
|||||||
| --------------- | --------- | --------------------------- |
|
| --------------- | --------- | --------------------------- |
|
||||||
| `npm` | ✅ Active | `pmg npm install <package>` |
|
| `npm` | ✅ Active | `pmg npm install <package>` |
|
||||||
| `pnpm` | ✅ Active | `pmg pnpm add <package>` |
|
| `pnpm` | ✅ Active | `pmg pnpm add <package>` |
|
||||||
|
| `bun` | ✅ Active | `pmg bun add <package>` |
|
||||||
| `pip` | ✅ Active | `pmg pip install <package>` |
|
| `pip` | ✅ Active | `pmg pip install <package>` |
|
||||||
|
| `uv` | 🚧 Planned | |
|
||||||
| `yarn` | 🚧 Planned | |
|
| `yarn` | 🚧 Planned | |
|
||||||
| `poetry` | 🚧 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!
|
> 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
|
```bash
|
||||||
npm install <package-name>
|
npm install <package-name>
|
||||||
pnpm add <package-name>
|
pnpm add <package-name>
|
||||||
|
bun add <package-name>
|
||||||
pip install <package-name>
|
pip install <package-name>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -173,9 +176,23 @@ You can also run PMG manually without aliases:
|
|||||||
```bash
|
```bash
|
||||||
pmg npm install <package-name>
|
pmg npm install <package-name>
|
||||||
pmg pnpm add <package-name>
|
pmg pnpm add <package-name>
|
||||||
|
pmg bun add <package-name>
|
||||||
pmg pip install <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
|
### Silent Mode
|
||||||
|
|
||||||
Use the `--silent` flag to run PMG in 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"
|
Npm PackageManagerName = "npm"
|
||||||
Pnpm PackageManagerName = "pnpm"
|
Pnpm PackageManagerName = "pnpm"
|
||||||
Pip PackageManagerName = "pip"
|
Pip PackageManagerName = "pip"
|
||||||
|
Bun PackageManagerName = "bun"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ExtractorManager struct {
|
type ExtractorManager struct {
|
||||||
@@ -37,6 +38,7 @@ func NewExtractorManager() *ExtractorManager {
|
|||||||
Npm: &NpmExtractor{},
|
Npm: &NpmExtractor{},
|
||||||
Pnpm: &PnpmExtractor{},
|
Pnpm: &PnpmExtractor{},
|
||||||
Pip: &PipExtractor{},
|
Pip: &PipExtractor{},
|
||||||
|
Bun: &BunExtractor{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
|
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"
|
||||||
|
"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/packagelockjson"
|
||||||
"github.com/google/osv-scalibr/extractor/filesystem/language/javascript/pnpmlock"
|
"github.com/google/osv-scalibr/extractor/filesystem/language/javascript/pnpmlock"
|
||||||
"github.com/google/osv-scalibr/fs"
|
"github.com/google/osv-scalibr/fs"
|
||||||
@@ -123,3 +124,58 @@ func parsePnpmLockFile(lockfilePath, scanDir string) ([]*packagev1.PackageVersio
|
|||||||
|
|
||||||
return packages, nil
|
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/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/tdakkota/asciicheck v0.4.1 // indirect
|
github.com/tdakkota/asciicheck v0.4.1 // indirect
|
||||||
github.com/tetafro/godot v1.5.0 // 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/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect
|
||||||
github.com/timonwong/loggercheck v0.10.1 // indirect
|
github.com/timonwong/loggercheck v0.10.1 // indirect
|
||||||
github.com/tomarrell/wrapcheck/v2 v2.10.0 // 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/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 h1:aNwfVI4I3+gdxjMgYPus9eHmoBeJIbnajOyqZYStzuw=
|
||||||
github.com/tetafro/godot v1.5.0/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio=
|
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 h1:y4mJRFlM6fUyPhoXuFg/Yu02fg/nIPFMOY8tOqppoFg=
|
||||||
github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460=
|
github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460=
|
||||||
github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg=
|
github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg=
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package analytics
|
|||||||
const (
|
const (
|
||||||
eventRun = "pmg_command_run"
|
eventRun = "pmg_command_run"
|
||||||
eventCommandNpm = "pmg_command_npm"
|
eventCommandNpm = "pmg_command_npm"
|
||||||
|
eventCommandBun = "pmg_command_bun"
|
||||||
eventCommandPnpm = "pmg_command_pnpm"
|
eventCommandPnpm = "pmg_command_pnpm"
|
||||||
eventCommandPip = "pmg_command_pip"
|
eventCommandPip = "pmg_command_pip"
|
||||||
|
|
||||||
@@ -19,6 +20,10 @@ func TrackCommandNpm() {
|
|||||||
TrackEvent(eventCommandNpm)
|
TrackEvent(eventCommandNpm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TrackCommandBun() {
|
||||||
|
TrackEvent(eventCommandBun)
|
||||||
|
}
|
||||||
|
|
||||||
func TrackCommandPnpm() {
|
func TrackCommandPnpm() {
|
||||||
TrackEvent(eventCommandPnpm)
|
TrackEvent(eventCommandPnpm)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ func main() {
|
|||||||
|
|
||||||
cmd.AddCommand(npm.NewNpmCommand())
|
cmd.AddCommand(npm.NewNpmCommand())
|
||||||
cmd.AddCommand(npm.NewPnpmCommand())
|
cmd.AddCommand(npm.NewPnpmCommand())
|
||||||
|
cmd.AddCommand(npm.NewBunCommand())
|
||||||
cmd.AddCommand(pypi.NewPipCommand())
|
cmd.AddCommand(pypi.NewPipCommand())
|
||||||
cmd.AddCommand(version.NewVersionCommand())
|
cmd.AddCommand(version.NewVersionCommand())
|
||||||
cmd.AddCommand(setup.NewSetupCommand())
|
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 {
|
type npmPackageManager struct {
|
||||||
Config NpmPackageManagerConfig
|
Config NpmPackageManagerConfig
|
||||||
}
|
}
|
||||||
@@ -48,7 +55,7 @@ func (npm *npmPackageManager) Ecosystem() packagev1.Ecosystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (npm *npmPackageManager) ParseCommand(args []string) (*ParsedCommand, error) {
|
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:]
|
args = args[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user