From 78bc6afa674abf27834f1ba2a82d7289c376d7b5 Mon Sep 17 00:00:00 2001 From: Sahil Bansal Date: Thu, 28 Aug 2025 21:17:59 +0530 Subject: [PATCH] fix command parsing for unknown flags (#70) * fix command parsing for unknown flags * fix test case * add dev flag support for npm cmds --- packagemanager/npm.go | 55 ++++++++++++++++++++++++++------------ packagemanager/npm_test.go | 8 +++--- packagemanager/pypi.go | 2 ++ 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/packagemanager/npm.go b/packagemanager/npm.go index 135c23b..bde04e2 100644 --- a/packagemanager/npm.go +++ b/packagemanager/npm.go @@ -2,10 +2,12 @@ package packagemanager import ( "fmt" + "io" "slices" "strings" packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" + "github.com/spf13/pflag" ) type NpmPackageManagerConfig struct { @@ -67,31 +69,49 @@ func (npm *npmPackageManager) ParseCommand(args []string) (*ParsedCommand, error Command: command, }, nil } - - // Extract packages from args - var packages []string - var isManifestInstall bool - var foundInstallCmd bool - + // Find the install command position + var installCmdIndex = -1 for idx, arg := range args { if slices.Contains(npm.Config.InstallCommands, arg) { - foundInstallCmd = true - // All subsequent args are packages except for flags - for i := idx + 1; i < len(args); i++ { - if strings.HasPrefix(args[i], "-") { - continue - } - - packages = append(packages, args[i]) - } - + installCmdIndex = idx break } } + if installCmdIndex == -1 { + // No install command found, return as-is + return &ParsedCommand{Command: command}, nil + } + + // Extract arguments after the install command + installArgs := args[installCmdIndex+1:] + + // Extract packages from args + var packages []string + var isManifestInstall bool + var devPackages []string + + flagSet := pflag.NewFlagSet(npm.Config.CommandName, pflag.ContinueOnError) + flagSet.SetOutput(io.Discard) + flagSet.ParseErrorsWhitelist.UnknownFlags = true + + switch npm.Config.CommandName { + case "npm", "pnpm": + flagSet.StringArrayVarP(&devPackages, "save-dev", "D", nil, "Install dev packages") + case "bun": + flagSet.StringArrayVarP(&devPackages, "dev", "d", nil, "Install dev packages") + } + + err := flagSet.Parse(installArgs) + if err != nil { + return &ParsedCommand{Command: command}, nil + } + + packages = flagSet.Args() + // If install command was found but no explicit packages, // this is a manifest-based installation - if foundInstallCmd && len(packages) == 0 { + if installCmdIndex != -1 && len(packages) == 0 { isManifestInstall = true } @@ -101,6 +121,7 @@ func (npm *npmPackageManager) ParseCommand(args []string) (*ParsedCommand, error Command: command, }, nil } + packages = append(packages, devPackages...) // Process all package arguments var installTargets []*PackageInstallTarget diff --git a/packagemanager/npm_test.go b/packagemanager/npm_test.go index 3b1e59a..a112423 100644 --- a/packagemanager/npm_test.go +++ b/packagemanager/npm_test.go @@ -44,7 +44,7 @@ func TestNpmParseCommand(t *testing.T) { }, { name: "install a development package with short flag", - command: "npm i @types/node -D", + command: "npm i -D @types/node", assert: func(t *testing.T, parsedCommand *ParsedCommand, err error) { assert.NoError(t, err) assert.Equal(t, 1, len(parsedCommand.InstallTargets)) @@ -89,7 +89,7 @@ func TestNpmParseCommand(t *testing.T) { }, { name: "multiple development packages", - command: "npm i @types/node @types/react -D", + command: "npm i -D @types/node -D @types/react", assert: func(t *testing.T, parsedCommand *ParsedCommand, err error) { assert.NoError(t, err) assert.Equal(t, 2, len(parsedCommand.InstallTargets)) @@ -128,12 +128,12 @@ func TestNpmParseCommand(t *testing.T) { }, }, { - name: "npm install with flags but no packages", + name: "npm install with dev flag but no packages", command: "npm install --save-dev", assert: func(t *testing.T, parsedCommand *ParsedCommand, err error) { assert.NoError(t, err) assert.Equal(t, 0, len(parsedCommand.InstallTargets)) - assert.Equal(t, true, parsedCommand.IsManifestInstall) + assert.Equal(t, false, parsedCommand.IsManifestInstall) // with no package name, npm won’t add or install anything new }, }, } diff --git a/packagemanager/pypi.go b/packagemanager/pypi.go index c5b0219..07267f1 100644 --- a/packagemanager/pypi.go +++ b/packagemanager/pypi.go @@ -113,6 +113,7 @@ func (p *pipCommandParser) ParseCommand(args []string) (*ParsedCommand, error) { flagSet := pflag.NewFlagSet("pip", pflag.ContinueOnError) flagSet.SetOutput(io.Discard) + flagSet.ParseErrorsWhitelist.UnknownFlags = true // Define flags var requirementFiles []string @@ -238,6 +239,7 @@ func (u *uvCommandParser) ParseCommand(args []string) (*ParsedCommand, error) { // Set up flag parsing flagSet := pflag.NewFlagSet("uv", pflag.ContinueOnError) flagSet.SetOutput(io.Discard) + flagSet.ParseErrorsWhitelist.UnknownFlags = true var manifestFiles []string