diff --git a/cliv2/cmd/cliv2/behavior/legacy/validation.go b/cliv2/cmd/cliv2/behavior/legacy/validation.go index 25de0ced10..0c8c6709d1 100644 --- a/cliv2/cmd/cliv2/behavior/legacy/validation.go +++ b/cliv2/cmd/cliv2/behavior/legacy/validation.go @@ -5,18 +5,10 @@ import ( "os" "strings" + snyk_cli_errors "github.com/snyk/error-catalog-golang-public/cli" "github.com/spf13/cobra" ) -type unsupportedFlagCombinationError struct { - flag1 string - flag2 string -} - -func (e *unsupportedFlagCombinationError) Error() string { - return fmt.Sprintf("The following option combination is not currently supported: %s + %s", e.flag1, e.flag2) -} - type flagCombinationRule struct { primaryFlag string incompatibleWith []string @@ -26,20 +18,20 @@ var incompatibleFlagRules = []flagCombinationRule{ { primaryFlag: "--all-projects", incompatibleWith: []string{ + "--project-name", "--file", + "--yarn-workspaces", "--package-manager", - "--project-name", "--docker", "--all-sub-projects", - "--yarn-workspaces", }, }, { primaryFlag: "--yarn-workspaces", incompatibleWith: []string{ + "--project-name", "--file", "--package-manager", - "--project-name", "--docker", "--all-sub-projects", }, @@ -49,12 +41,16 @@ var incompatibleFlagRules = []flagCombinationRule{ incompatibleWith: []string{"--file"}, }, { - primaryFlag: "--maven-aggregate-project", - incompatibleWith: []string{"--project-name"}, + primaryFlag: "--project-name", + incompatibleWith: []string{"--maven-aggregate-project"}, + }, + { + primaryFlag: "--json", + incompatibleWith: []string{"--sarif"}, }, } -func validateFlagsCompatibilityWithLegacy(args []string) error { +func validateFlags(args []string) error { presentFlags := parseFlagsFromArgs(args) for _, rule := range incompatibleFlagRules { @@ -64,10 +60,9 @@ func validateFlagsCompatibilityWithLegacy(args []string) error { for _, incompatibleFlag := range rule.incompatibleWith { if presentFlags[incompatibleFlag] { - return &unsupportedFlagCombinationError{ - flag1: extractFlagName(incompatibleFlag), - flag2: extractFlagName(rule.primaryFlag), - } + return snyk_cli_errors.NewInvalidFlagOptionError( + fmt.Sprintf("The following option combination is not currently supported: %s + %s", extractFlagName(incompatibleFlag), extractFlagName(rule.primaryFlag)), + ) } } } @@ -91,23 +86,12 @@ func extractFlagName(flag string) string { return strings.TrimPrefix(flag, "--") } -func unknownCommandError(details string) error { - if details == "" { - return fmt.Errorf("unknown command") - } - return fmt.Errorf("unknown command %s", details) -} - // SetupTestMonitorCommand configures command for test/monitor to ensure parity with legacy behavior func SetupTestMonitorCommand(cmd *cobra.Command) { - // Relax flag validation for backwards compatibility by suppressing cobra's default error - cmd.SetFlagErrorFunc(func(_ *cobra.Command, _ error) error { - return unknownCommandError("") - }) - + cmd.FParseErrWhitelist.UnknownFlags = true cmd.PreRunE = func(c *cobra.Command, args []string) error { - if err := validateFlagsCompatibilityWithLegacy(os.Args[1:]); err != nil { - return unknownCommandError(err.Error()) + if err := validateFlags(os.Args[1:]); err != nil { + return err } return nil } diff --git a/cliv2/cmd/cliv2/behavior/legacy/validation_test.go b/cliv2/cmd/cliv2/behavior/legacy/validation_test.go index 2e49e66288..aa3d8b61b6 100644 --- a/cliv2/cmd/cliv2/behavior/legacy/validation_test.go +++ b/cliv2/cmd/cliv2/behavior/legacy/validation_test.go @@ -1,8 +1,10 @@ package legacy import ( + "fmt" "testing" + "github.com/snyk/error-catalog-golang-public/snyk_errors" "github.com/stretchr/testify/assert" ) @@ -27,15 +29,17 @@ func Test_validateIncompatibleFlagCombinations(t *testing.T) { {[]string{"test", "--all-projects", "--file"}, "file + all-projects"}, {[]string{"test", "--yarn-workspaces", "--file"}, "file + yarn-workspaces"}, {[]string{"test", "--scan-all-unmanaged", "--file"}, "file + scan-all-unmanaged"}, - {[]string{"test", "--maven-aggregate-project", "--project-name"}, "project-name + maven-aggregate-project"}, + {[]string{"test", "--maven-aggregate-project", "--project-name"}, "maven-aggregate-project + project-name"}, } for _, tt := range tests { - err := validateFlagsCompatibilityWithLegacy(tt.args) + err := validateFlags(tt.args) if tt.wantErr == "" { assert.NoError(t, err, "args: %v", tt.args) } else { - assert.EqualError(t, err, "The following option combination is not currently supported: "+tt.wantErr, "args: %v", tt.args) + var errCatalog snyk_errors.Error + assert.ErrorAs(t, err, &errCatalog) + assert.Equal(t, fmt.Sprintf("The following option combination is not currently supported: %s", tt.wantErr), errCatalog.Detail) } } } diff --git a/cliv2/go.mod b/cliv2/go.mod index ba83d4ae49..0dfeac9fa7 100644 --- a/cliv2/go.mod +++ b/cliv2/go.mod @@ -15,7 +15,7 @@ require ( github.com/snyk/cli-extension-dep-graph v0.29.0 github.com/snyk/cli-extension-iac v0.0.0-20260206082514-00c443ccee80 github.com/snyk/cli-extension-iac-rules v0.0.0-20260206080712-9cbb5f95465d - github.com/snyk/cli-extension-os-flows v0.0.0-20260306115903-79ae783267c1 + github.com/snyk/cli-extension-os-flows v0.0.0-20260318134054-104b58eeafad github.com/snyk/cli-extension-sbom v0.0.0-20260310155620-07d1927f26ea github.com/snyk/cli-extension-secrets v0.0.0-20260305092220-defe1129df99 github.com/snyk/code-client-go v1.26.2 diff --git a/cliv2/go.sum b/cliv2/go.sum index 1101bc411f..cf0d61bfeb 100644 --- a/cliv2/go.sum +++ b/cliv2/go.sum @@ -541,8 +541,8 @@ github.com/snyk/cli-extension-iac v0.0.0-20260206082514-00c443ccee80 h1:JHbnSkgG github.com/snyk/cli-extension-iac v0.0.0-20260206082514-00c443ccee80/go.mod h1:Ht5k+sWdi//fM2MjcmBMWjcJmr35iMvQpYlBWnUHL4I= github.com/snyk/cli-extension-iac-rules v0.0.0-20260206080712-9cbb5f95465d h1:xkxHgZ+DT4hRiIEeAEv1JWLJRYV4MbAFvtEUpUkndPA= github.com/snyk/cli-extension-iac-rules v0.0.0-20260206080712-9cbb5f95465d/go.mod h1:ztpTmC4n8MEO0B48M2nL/Q9tb6CkLZ61ZKZbjwhOKRo= -github.com/snyk/cli-extension-os-flows v0.0.0-20260306115903-79ae783267c1 h1:CR4E/6MeRNpFhO6huU0qk0WkUAp3kwJr9Y3jMJE8zfY= -github.com/snyk/cli-extension-os-flows v0.0.0-20260306115903-79ae783267c1/go.mod h1:DREH8r1EdIMSDNTdcG1WDvXQMzdyMRGaQejQdEc8s4c= +github.com/snyk/cli-extension-os-flows v0.0.0-20260318134054-104b58eeafad h1:Quyyc8V9VLc1RpHdY/MTqSiRO8+FVipa/TI/WrEbTj8= +github.com/snyk/cli-extension-os-flows v0.0.0-20260318134054-104b58eeafad/go.mod h1:DREH8r1EdIMSDNTdcG1WDvXQMzdyMRGaQejQdEc8s4c= github.com/snyk/cli-extension-sbom v0.0.0-20260310155620-07d1927f26ea h1:rBIfTmKT1B1Qe6taCO55MfgZ2J0RdZ6jz6UuAXFzCz4= github.com/snyk/cli-extension-sbom v0.0.0-20260310155620-07d1927f26ea/go.mod h1:SJ624HENWG4yjM6jNuLebTeNsMriozf1LcKhMYVm1aY= github.com/snyk/cli-extension-secrets v0.0.0-20260305092220-defe1129df99 h1:UmCYIiMbYBGGaDj2UVo1gZou82z0tawQ/AMN2wsPYMI= diff --git a/test/jest/acceptance/snyk-test/reachability-user-journey.spec.ts b/test/jest/acceptance/snyk-test/reachability-user-journey.spec.ts index 4f211ede7e..7864952b46 100644 --- a/test/jest/acceptance/snyk-test/reachability-user-journey.spec.ts +++ b/test/jest/acceptance/snyk-test/reachability-user-journey.spec.ts @@ -130,6 +130,32 @@ describe('snyk test --reachability', () => { expect(code).toBe(EXIT_CODES.VULNS_FOUND); }); + test('emits a valid json output when unknown flags are passed', async () => { + const { stdout, code, stderr } = await runSnykCLI( + `test ${TEMP_LOCAL_PATH} --reachability --json --foo-bar`, + { + env: { + ...process.env, + ...ReachabilityIntegrationEnv.env, + }, + }, + ); + + expect(code).not.toBe(EXIT_CODES.ERROR); + + expect(stdout).not.toBe(''); + expect(stderr).toBe(''); + + const jsonOutput = JSON.parse(stdout); + + const allVulnsHaveReachabilitySignal = jsonOutput.vulnerabilities.every( + (vuln: { reachability: string; type: string }) => + !!vuln.reachability || vuln.type === 'license', + ); + + expect(allVulnsHaveReachabilitySignal).toBeTruthy(); + }); + test('emits a valid json output and fails the test if vulnerabilies are upgradable', async () => { const { stdout, code, stderr } = await runSnykCLI( `test ${TEMP_LOCAL_PATH} --reachability --fail-on=upgradable --json`,