Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions internal/cmd/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"fmt"

"github.com/open-delivery-spec/cli/internal/policy"
"github.com/open-delivery-spec/cli/internal/report"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand All @@ -17,7 +18,9 @@ var reportCmd = &cobra.Command{

The command is convention-first: it reads GitHub Actions environment data when
available and falls back to local git metadata. Missing PR-only context is
reported as skipped instead of requiring extra flags.`,
reported as skipped instead of requiring extra flags.

Use --profile to select a compliance policy: oss, enterprise, or regulated.`,
RunE: func(cmd *cobra.Command, args []string) error {
outputDir := reportOutput
if outputDir == "" {
Expand All @@ -27,15 +30,34 @@ reported as skipped instead of requiring extra flags.`,
outputDir = "ods-report"
}

// Load policy from profile flag or config file
p, err := policy.LoadPolicy()
if err != nil {
fmt.Fprintf(cmd.ErrOrStderr(), "Warning: could not load policy: %v (using defaults)\n", err)
}

inputs := report.DiscoverInputs()
result := report.Build(inputs, report.Options{Strict: strict, Check: viper.GetString("report.check")})
result := report.Build(inputs, report.Options{
Strict: strict,
Check: viper.GetString("report.check"),
})

// Ensure policy info is attached
if p != nil {
result.Policy = p
result.PolicyProfile = p.Profile
}

if err := report.WriteFiles(result, outputDir); err != nil {
return fmt.Errorf("writing report: %w", err)
}

fmt.Printf("ODS compliance report written to %s\n", outputDir)
fmt.Printf("Status: %s\n", result.Status)
fmt.Printf("Score: %d / 100\n", result.Score)
if p != nil {
fmt.Printf("Policy: %s\n", p.Profile)
}

if result.Status == report.StatusNonCompliant {
return fmt.Errorf("ODS compliance report is non-compliant")
Expand Down
14 changes: 10 additions & 4 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import (
)

var (
cfgFile string
specVer string
strict bool
schemaDir string
cfgFile string
specVer string
strict bool
schemaDir string
policyProfile string
policyFile string
)

var rootCmd = &cobra.Command{
Expand Down Expand Up @@ -47,9 +49,13 @@ func init() {
rootCmd.PersistentFlags().StringVar(&specVer, "spec-version", "1.0.0", "ODS spec version")
rootCmd.PersistentFlags().BoolVar(&strict, "strict", false, "treat warnings as errors")
rootCmd.PersistentFlags().StringVar(&schemaDir, "schema-dir", "", "custom schema directory path")
rootCmd.PersistentFlags().StringVar(&policyProfile, "profile", "", "compliance profile: oss, enterprise, regulated")
rootCmd.PersistentFlags().StringVar(&policyFile, "policy", "", "path to policy YAML file")

viper.BindPFlag("spec_version", rootCmd.PersistentFlags().Lookup("spec-version"))
viper.BindPFlag("strict", rootCmd.PersistentFlags().Lookup("strict"))
viper.BindPFlag("profile", rootCmd.PersistentFlags().Lookup("profile"))
viper.BindPFlag("policy_file", rootCmd.PersistentFlags().Lookup("policy"))
}

func initConfig() {
Expand Down
39 changes: 34 additions & 5 deletions internal/cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"

"github.com/open-delivery-spec/cli/internal/policy"
"github.com/open-delivery-spec/cli/internal/validator"
"github.com/spf13/cobra"
)
Expand All @@ -26,11 +27,13 @@ var validateBranchCmd = &cobra.Command{

Examples:
ods validate branch feature/add-oauth-login
ods validate branch bugfix/fix-null-pointer --strict`,
ods validate branch bugfix/fix-null-pointer --strict
ods validate branch feature/add-oauth --profile enterprise`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
result, err := validator.ValidateBranch(name)
p, _ := policy.LoadPolicy()
result, err := validator.ValidateBranchWithPolicy(name, p)
if err != nil {
return err
}
Expand All @@ -45,13 +48,15 @@ var validateCommitCmd = &cobra.Command{

Examples:
ods validate commit --file commit-msg.txt
git log -1 --format=%B | ods validate commit --stdin`,
git log -1 --format=%B | ods validate commit --stdin
ods validate commit --file msg.txt --profile regulated`,
RunE: func(cmd *cobra.Command, args []string) error {
msg, err := readInput()
if err != nil {
return fmt.Errorf("reading commit message: %w", err)
}
result, err := validator.ValidateCommitMessage(msg)
p, _ := policy.LoadPolicy()
result, err := validator.ValidateCommitMessageWithPolicy(msg, p)
if err != nil {
return err
}
Expand All @@ -62,12 +67,18 @@ Examples:
var validatePRCmd = &cobra.Command{
Use: "pr",
Short: "Validate a PR description",
Long: `Validate a PR description against the ODS PR Description spec.

Examples:
ods validate pr --file PR_BODY.md
ods validate pr --file body.md --profile enterprise`,
RunE: func(cmd *cobra.Command, args []string) error {
body, err := readInput()
if err != nil {
return fmt.Errorf("reading PR description: %w", err)
}
result, err := validator.ValidatePRDescription(body)
p, _ := policy.LoadPolicy()
result, err := validator.ValidatePRDescriptionWithPolicy(body, p)
if err != nil {
return err
}
Expand Down Expand Up @@ -186,6 +197,7 @@ func printResult(r validator.Result) error {
for _, w := range r.Warnings {
fmt.Printf(" - %s\n", w)
}
printFixSuggestions(r)
if strict && len(r.Warnings) > 0 {
return fmt.Errorf("validation failed with %d warning(s) in strict mode", len(r.Warnings))
}
Expand All @@ -194,6 +206,7 @@ func printResult(r validator.Result) error {
for _, e := range r.Errors {
fmt.Printf(" - %s\n", e)
}
printFixSuggestions(r)
errCount := len(r.Errors)
if strict && len(r.Warnings) > 0 {
fmt.Println("\nWarnings (treated as errors in strict mode):")
Expand All @@ -207,6 +220,22 @@ func printResult(r validator.Result) error {
return nil
}

func printFixSuggestions(r validator.Result) {
if len(r.FixSuggestions) == 0 {
return
}
fmt.Println("\n🔧 Fix suggestions:")
for i, fs := range r.FixSuggestions {
fmt.Printf(" %d. %s\n", i+1, fs.Title)
if fs.Description != "" {
fmt.Printf(" %s\n", fs.Description)
}
if fs.Template != "" {
fmt.Printf(" Template: %s\n", fs.Template)
}
}
}

// ioReadAll avoids importing io/ioutil (deprecated) and io for minimal deps
func ioReadAll(f *os.File) ([]byte, error) {
var buf []byte
Expand Down
Loading
Loading