Files
pmg/packagemanager/npm_executor.go
T
f50d296935 add e2e and alias for npx, pnpx (#105)
* add e2e and alias for npx, pnpx

* Update .github/workflows/pmg-e2e.yml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>

---------

Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-10 16:47:43 +05:30

126 lines
3.0 KiB
Go

package packagemanager
import (
"io"
"slices"
"strings"
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
"github.com/spf13/pflag"
)
type NpmPackageExecutorConfig struct {
CommandName string
}
func DefaultNpxPackageExecutorConfig() NpmPackageExecutorConfig {
return NpmPackageExecutorConfig{
CommandName: "npx",
}
}
func DefaultPnpxPackageExecutorConfig() NpmPackageExecutorConfig {
return NpmPackageExecutorConfig{
CommandName: "pnpx",
}
}
type npmPackageExecutor struct {
Config NpmPackageExecutorConfig
}
func NewNpmPackageExecutor(config NpmPackageExecutorConfig) (*npmPackageExecutor, error) {
return &npmPackageExecutor{
Config: config,
}, nil
}
var _ PackageManager = &npmPackageExecutor{}
func (n *npmPackageExecutor) Name() string {
return n.Config.CommandName
}
func (n *npmPackageExecutor) Ecosystem() packagev1.Ecosystem {
return packagev1.Ecosystem_ECOSYSTEM_NPM
}
func (n *npmPackageExecutor) ParseCommand(args []string) (*ParsedCommand, error) {
if len(args) > 0 && (args[0] == "npx" || args[0] == "pnpx") {
args = args[1:]
}
command := Command{Exe: n.Config.CommandName, Args: args}
if len(args) < 1 {
return &ParsedCommand{
Command: command,
}, nil
}
flagSet := pflag.NewFlagSet(n.Config.CommandName, pflag.ContinueOnError)
flagSet.SetOutput(io.Discard)
flagSet.ParseErrorsAllowlist.UnknownFlags = true
var packages []string
switch n.Config.CommandName {
case "npx":
flagSet.StringArrayVarP(&packages, "package", "p", []string{}, "Package List")
case "pnpx":
flagSet.StringArrayVar(&packages, "package", []string{}, "Package List")
}
err := flagSet.Parse(args)
if err != nil {
return &ParsedCommand{Command: command}, nil
}
for _, arg := range flagSet.Args() {
// Append the scoped package
if strings.HasPrefix(arg, "@") && !slices.Contains(packages, arg) {
packages = append(packages, arg)
}
}
// For both npx and pnpx, the first positional argument is typically
// the package to execute (e.g., `npx cowsay@1.6.0` or `pnpx cowsay@1.6.0`).
// However, if -p/--package flags are provided, the first positional arg
// is the binary to run, not the package (e.g., `npx -p typescript tsc`).
if len(flagSet.Args()) > 0 && len(packages) == 0 {
pkg := flagSet.Args()[0]
if !slices.Contains(packages, pkg) {
packages = append(packages, pkg)
}
}
var installTargets []*PackageInstallTarget
for _, pkg := range packages {
packageName, version, err := npmParsePackageInfo(pkg)
if err != nil {
return nil, ErrFailedToParsePackage.Wrap(err)
}
if version != "" {
version = npmCleanVersion(version)
}
installTarget := &PackageInstallTarget{
PackageVersion: &packagev1.PackageVersion{
Package: &packagev1.Package{
Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM,
Name: packageName,
},
Version: version,
},
}
installTargets = append(installTargets, installTarget)
}
return &ParsedCommand{
Command: command,
InstallTargets: installTargets,
}, nil
}