diff --git a/README.md b/README.md index 602a851..23458a7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Kuality CLI -Scan any website for accessibility, performance, SEO, cross-browser, stress testing, and 30+ QA dimensions — from your terminal. +Test any website for accessibility, performance, SEO, cross-browser, stress testing, and 30+ QA dimensions — from your terminal. ## Install @@ -24,28 +24,28 @@ go install github.com/kuality-io/cli@latest # Authenticate with your API key (get one at https://kuality.io/settings/api-keys) kuality auth login -# Run a scan -kuality scan example.com +# Run a test +kuality test example.com -# Run a specific scan type -kuality scan example.com --type a11y +# Run a specific test type +kuality test example.com --type a11y # Fail CI if high-severity findings exist -kuality scan example.com --type a11y --fail-on high +kuality test example.com --type a11y --fail-on high # JSON output for scripting -kuality scan example.com --format json +kuality test example.com --format json # JUnit XML for CI test reporting -kuality scan example.com --format junit > results.xml +kuality test example.com --format junit > results.xml ``` ## Commands | Command | Description | |---------|-------------| -| `kuality scan ` | Run a quality scan | -| `kuality status ` | Check scan status | +| `kuality test ` | Run a quality test | +| `kuality status ` | Check test status | | `kuality reports list` | List recent reports | | `kuality reports show ` | Show report details | | `kuality targets` | List configured targets | @@ -54,9 +54,9 @@ kuality scan example.com --format junit > results.xml | `kuality auth status` | Check auth status | | `kuality auth logout` | Remove stored API key | -## Scan types +## Test types -37 scan types across 8 categories: +37 test types across 8 categories: | Category | Types | |----------|-------| @@ -74,9 +74,9 @@ kuality scan example.com --format junit > results.xml ### GitHub Actions ```yaml -- name: Quality scan +- name: Quality test run: | - kuality scan ${{ vars.SITE_URL }} --type a11y --fail-on high --format junit > kuality-results.xml + kuality test ${{ vars.SITE_URL }} --type a11y --fail-on high --format junit > kuality-results.xml - name: Upload results uses: actions/upload-artifact@v4 @@ -88,9 +88,9 @@ kuality scan example.com --format junit > results.xml ### GitLab CI ```yaml -quality_scan: +quality_test: script: - - kuality scan $SITE_URL --type a11y --fail-on high --format junit > kuality-results.xml + - kuality test $SITE_URL --type a11y --fail-on high --format junit > kuality-results.xml artifacts: reports: junit: kuality-results.xml @@ -100,7 +100,7 @@ quality_scan: ```bash export KUALITY_API_KEY="your-key" -kuality scan example.com --type a11y --fail-on high --quiet +kuality test example.com --type a11y --fail-on high --quiet ``` Exit codes: `0` = pass, `1` = findings exceed `--fail-on` threshold. diff --git a/cmd/reports.go b/cmd/reports.go index 3363a6c..3f1292c 100644 --- a/cmd/reports.go +++ b/cmd/reports.go @@ -16,13 +16,13 @@ var ( var reportsCmd = &cobra.Command{ Use: "reports", - Short: "View scan reports", + Short: "View test reports", } var reportsListCmd = &cobra.Command{ Use: "list", Short: "List recent reports", - Long: `List recent scan reports for your organization. + Long: `List recent test reports for your organization. Examples: kuality reports list @@ -64,7 +64,7 @@ Examples: rows[i] = []string{ id, r.Target, - r.TypeOfScan, + r.TypeOfTest, fmt.Sprintf("%s %s", output.StatusIcon(r.State), r.State), r.Score.String(), output.SeverityColor("high", r.High), @@ -80,7 +80,7 @@ Examples: var reportsShowCmd = &cobra.Command{ Use: "show ", Short: "Show detailed report", - Long: `Show the full details of a scan report. + Long: `Show the full details of a test report. Examples: kuality reports show abc123 @@ -119,7 +119,7 @@ Examples: fmt.Println() fmt.Printf(" Report ID: %s\n", report.ID) fmt.Printf(" Target: %s\n", report.Target) - fmt.Printf(" Scan type: %s\n", report.TypeOfScan) + fmt.Printf(" Test type: %s\n", report.TypeOfTest) fmt.Printf(" Score: %s\n", report.Score) fmt.Printf(" Status: %s %s\n", output.StatusIcon(report.State), report.State) if report.StartDate != "" { @@ -149,7 +149,7 @@ Examples: } func init() { - reportsListCmd.Flags().StringVarP(&flagReportType, "type", "t", "", "Filter by scan type") + reportsListCmd.Flags().StringVarP(&flagReportType, "type", "t", "", "Filter by test type") reportsListCmd.Flags().StringVar(&flagReportTarget, "target", "", "Filter by target URL") reportsCmd.AddCommand(reportsListCmd) diff --git a/cmd/root.go b/cmd/root.go index 4bb6d73..3709d04 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -21,10 +21,10 @@ var ( var rootCmd = &cobra.Command{ Use: "kuality", - Short: "Kuality CLI — scan any site from your terminal", - Long: `Kuality CLI lets you run website quality scans from the command line. + Short: "Kuality CLI — test any site from your terminal", + Long: `Kuality CLI lets you run website quality tests from the command line. -Scan for accessibility, performance, SEO, cross-browser compatibility, +Test for accessibility, performance, SEO, cross-browser compatibility, and 30+ other quality dimensions. Integrate into CI/CD pipelines with exit codes and JUnit output. diff --git a/cmd/score.go b/cmd/score.go index 81f4c78..75335d5 100644 --- a/cmd/score.go +++ b/cmd/score.go @@ -38,7 +38,7 @@ Examples: } if len(scores) == 0 { - fmt.Println("No scores available. Run a scan first.") + fmt.Println("No scores available. Run a test first.") return nil } diff --git a/cmd/status.go b/cmd/status.go index adda74e..e659461 100644 --- a/cmd/status.go +++ b/cmd/status.go @@ -10,9 +10,9 @@ import ( ) var statusCmd = &cobra.Command{ - Use: "status ", - Short: "Check the status of a scan", - Long: `Check the current status of a previously started scan. + Use: "status ", + Short: "Check the status of a test", + Long: `Check the current status of a previously started test. Examples: kuality status abc123 @@ -29,9 +29,9 @@ Examples: return err } - status, err := c.GetScanStatus(args[0]) + status, err := c.GetTestStatus(args[0]) if err != nil { - return fmt.Errorf("failed to get scan status: %w", err) + return fmt.Errorf("failed to get test status: %w", err) } if flagFormat == "json" { @@ -43,7 +43,7 @@ Examples: state = status.State } - fmt.Printf("Scan ID: %s\n", status.ScanID) + fmt.Printf("Test ID: %s\n", status.TestID) fmt.Printf("Report ID: %s\n", status.ReportID) fmt.Printf("Target: %s\n", status.Target) fmt.Printf("Status: %s %s\n", output.StatusIcon(state), state) diff --git a/cmd/targets.go b/cmd/targets.go index 780971b..f5ec5d4 100644 --- a/cmd/targets.go +++ b/cmd/targets.go @@ -12,7 +12,7 @@ import ( var targetsCmd = &cobra.Command{ Use: "targets", Short: "List configured targets", - Long: `List all scan targets configured in your organization. + Long: `List all test targets configured in your organization. Examples: kuality targets diff --git a/cmd/scan.go b/cmd/test.go similarity index 62% rename from cmd/scan.go rename to cmd/test.go index 117267a..4e4c100 100644 --- a/cmd/scan.go +++ b/cmd/test.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/cobra" ) -var validScanTypes = []string{ +var validTestTypes = []string{ "a11y", "webvitals", "seo", "formaudit", "brokenlinks", "cookie", "headers", "jsaudit", "tech", "cms", "api", "firefox", "webkit", "uxaudit", "animation", "colorblind", "assets", "screenreader", @@ -24,57 +24,57 @@ var validScanTypes = []string{ } var ( - flagScanType string + flagTestType string flagFailOn string flagNoWait bool flagTimeout int ) -var scanCmd = &cobra.Command{ - Use: "scan ", - Short: "Run a quality scan on a URL", - Long: `Run a scan against a URL and wait for results. +var testCmd = &cobra.Command{ + Use: "test ", + Short: "Run a quality test on a URL", + Long: `Run a test against a URL and wait for results. -By default, scans all quality dimensions. Use --type to run a -specific scan type. Use --fail-on to set a severity threshold +By default, tests all quality dimensions. Use --type to run a +specific test type. Use --fail-on to set a severity threshold for non-zero exit codes (useful in CI/CD pipelines). Examples: - kuality scan example.com - kuality scan example.com --type a11y - kuality scan example.com --type a11y --fail-on high - kuality scan example.com --type seo --format json - kuality scan example.com --type webvitals --format junit - kuality scan example.com --no-wait`, + kuality test example.com + kuality test example.com --type a11y + kuality test example.com --type a11y --fail-on high + kuality test example.com --type seo --format json + kuality test example.com --type webvitals --format junit + kuality test example.com --no-wait`, Args: cobra.ExactArgs(1), - RunE: runScan, + RunE: runTest, } func init() { - scanCmd.Flags().StringVarP(&flagScanType, "type", "t", "web", "Scan type (a11y, seo, webvitals, headers, ssl, ...)") - scanCmd.Flags().StringVar(&flagFailOn, "fail-on", "", "Exit non-zero if findings at this severity or above (high, medium, low)") - scanCmd.Flags().BoolVar(&flagNoWait, "no-wait", false, "Start scan and exit without waiting for results") - scanCmd.Flags().IntVar(&flagTimeout, "timeout", 600, "Maximum seconds to wait for scan completion") + testCmd.Flags().StringVarP(&flagTestType, "type", "t", "web", "Test type (a11y, seo, webvitals, headers, ssl, ...)") + testCmd.Flags().StringVar(&flagFailOn, "fail-on", "", "Exit non-zero if findings at this severity or above (high, medium, low)") + testCmd.Flags().BoolVar(&flagNoWait, "no-wait", false, "Start test and exit without waiting for results") + testCmd.Flags().IntVar(&flagTimeout, "timeout", 600, "Maximum seconds to wait for test completion") - scanCmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return validScanTypes, cobra.ShellCompDirectiveNoFileComp + testCmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return validTestTypes, cobra.ShellCompDirectiveNoFileComp }) - scanCmd.RegisterFlagCompletionFunc("fail-on", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + testCmd.RegisterFlagCompletionFunc("fail-on", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return []string{"high", "medium", "low"}, cobra.ShellCompDirectiveNoFileComp }) - rootCmd.AddCommand(scanCmd) + rootCmd.AddCommand(testCmd) } -func runScan(cmd *cobra.Command, args []string) error { +func runTest(cmd *cobra.Command, args []string) error { target := args[0] if flagFailOn != "" && flagFailOn != "high" && flagFailOn != "medium" && flagFailOn != "low" { return fmt.Errorf("--fail-on must be one of: high, medium, low") } - if !isValidScanType(flagScanType) { - return fmt.Errorf("unknown scan type %q. Run 'kuality scan --help' for valid types", flagScanType) + if !isValidTestType(flagTestType) { + return fmt.Errorf("unknown test type %q. Run 'kuality test --help' for valid types", flagTestType) } cfg, err := loadConfig() @@ -88,27 +88,27 @@ func runScan(cmd *cobra.Command, args []string) error { } if !flagQuiet { - fmt.Printf("Starting %s scan on %s...\n", flagScanType, target) + fmt.Printf("Starting %s test on %s...\n", flagTestType, target) } - scan, err := c.CreateScan(target, flagScanType) + test, err := c.CreateTest(target, flagTestType) if err != nil { - return fmt.Errorf("failed to start scan: %w", err) + return fmt.Errorf("failed to start test: %w", err) } if !flagQuiet { - fmt.Printf("Scan started (ID: %s)\n", scan.ScanID) + fmt.Printf("Test started (ID: %s)\n", test.TestID) } if flagNoWait { if flagFormat == "json" { - return output.JSON(os.Stdout, scan) + return output.JSON(os.Stdout, test) } - fmt.Printf("Scan ID: %s\nPoll with: kuality status %s\n", scan.ScanID, scan.ScanID) + fmt.Printf("Test ID: %s\nPoll with: kuality status %s\n", test.TestID, test.TestID) return nil } - reportID, err := waitForScan(c, scan.ScanID, time.Duration(flagTimeout)*time.Second) + reportID, err := waitForTest(c, test.TestID, time.Duration(flagTimeout)*time.Second) if err != nil { return err } @@ -128,7 +128,7 @@ func runScan(cmd *cobra.Command, args []string) error { } if report.State == "failed" { - return fmt.Errorf("scan failed: %s", report.Error) + return fmt.Errorf("test failed: %s", report.Error) } switch flagFormat { @@ -145,15 +145,15 @@ func runScan(cmd *cobra.Command, args []string) error { return nil } -func waitForScan(c *client.Client, scanID string, timeout time.Duration) (string, error) { +func waitForTest(c *client.Client, testID string, timeout time.Duration) (string, error) { deadline := time.Now().Add(timeout) spinner := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} i := 0 for time.Now().Before(deadline) { - status, err := c.GetScanStatus(scanID) + status, err := c.GetTestStatus(testID) if err != nil { - return "", fmt.Errorf("failed to check scan status: %w", err) + return "", fmt.Errorf("failed to check test status: %w", err) } state := status.Status @@ -165,7 +165,7 @@ func waitForScan(c *client.Client, scanID string, timeout time.Duration) (string case "completed": if !flagQuiet { fmt.Print("\r\033[K") - fmt.Println("Scan completed.") + fmt.Println("Test completed.") } return status.ReportID, nil case "failed": @@ -176,20 +176,20 @@ func waitForScan(c *client.Client, scanID string, timeout time.Duration) (string } if !flagQuiet { - fmt.Printf("\r\033[K%s Scanning... (%s)", spinner[i%len(spinner)], state) + fmt.Printf("\r\033[K%s Testing... (%s)", spinner[i%len(spinner)], state) i++ } time.Sleep(3 * time.Second) } - return "", fmt.Errorf("scan timed out after %s. Check status with: kuality status %s", timeout, scanID) + return "", fmt.Errorf("test timed out after %s. Check status with: kuality status %s", timeout, testID) } func printReport(r *client.Report) { fmt.Println() fmt.Printf(" Target: %s\n", r.Target) - fmt.Printf(" Scan type: %s\n", r.TypeOfScan) + fmt.Printf(" Test type: %s\n", r.TypeOfTest) fmt.Printf(" Score: %s\n", r.Score) fmt.Printf(" Status: %s %s\n", output.StatusIcon(r.State), r.State) fmt.Println() @@ -223,8 +223,8 @@ func checkThreshold(r *client.Report, failOn string) error { return nil } -func isValidScanType(t string) bool { - for _, v := range validScanTypes { +func isValidTestType(t string) bool { + for _, v := range validTestTypes { if v == t { return true } diff --git a/internal/client/client.go b/internal/client/client.go index 5c6f2b8..47fa269 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -111,23 +111,23 @@ func (c *Client) decodeResponse(resp *http.Response, target any) error { return nil } -// Scan types +// Test types -type CreateScanRequest struct { +type CreateTestRequest struct { Target string `json:"target"` - ScanType string `json:"scan_type"` + TestType string `json:"scan_type"` } -type ScanResponse struct { - ScanID string `json:"scan_id"` +type TestResponse struct { + TestID string `json:"scan_id"` ReportID string `json:"report_id"` Status string `json:"status"` Target string `json:"target"` PollURL string `json:"poll_url"` } -type ScanStatus struct { - ScanID string `json:"scan_id"` +type TestStatus struct { + TestID string `json:"scan_id"` ReportID string `json:"report_id"` Status string `json:"status"` State string `json:"state"` @@ -138,7 +138,7 @@ type Report struct { ID string `json:"id"` ReportID string `json:"report_id"` Target string `json:"target"` - TypeOfScan string `json:"type_of_scan"` + TypeOfTest string `json:"type_of_scan"` State string `json:"state"` Score json.Number `json:"score"` High int `json:"high"` @@ -156,7 +156,7 @@ type Report struct { type ReportListItem struct { ID string `json:"id"` Target string `json:"target"` - TypeOfScan string `json:"type_of_scan"` + TypeOfTest string `json:"type_of_scan"` State string `json:"state"` Score json.Number `json:"score"` High int `json:"high"` @@ -184,29 +184,29 @@ type GateResult struct { Details string `json:"details"` } -func (c *Client) CreateScan(target, scanType string) (*ScanResponse, error) { - resp, err := c.do("POST", "/api/v1/scans", &CreateScanRequest{ +func (c *Client) CreateTest(target, testType string) (*TestResponse, error) { + resp, err := c.do("POST", "/api/v1/scans", &CreateTestRequest{ Target: target, - ScanType: scanType, + TestType: testType, }) if err != nil { return nil, err } - var result ScanResponse + var result TestResponse if err := c.decodeResponse(resp, &result); err != nil { return nil, err } return &result, nil } -func (c *Client) GetScanStatus(scanID string) (*ScanStatus, error) { - resp, err := c.do("GET", "/api/v1/scans/"+url.PathEscape(scanID), nil) +func (c *Client) GetTestStatus(testID string) (*TestStatus, error) { + resp, err := c.do("GET", "/api/v1/scans/"+url.PathEscape(testID), nil) if err != nil { return nil, err } - var result ScanStatus + var result TestStatus if err := c.decodeResponse(resp, &result); err != nil { return nil, err } @@ -241,11 +241,11 @@ func (c *Client) GetReportJUnit(reportID string) ([]byte, error) { return io.ReadAll(io.LimitReader(resp.Body, 10*1024*1024)) } -func (c *Client) ListReports(scanType, target string) ([]ReportListItem, error) { +func (c *Client) ListReports(testType, target string) ([]ReportListItem, error) { path := "/api/v1/reports" params := url.Values{} - if scanType != "" { - params.Set("scan_type", scanType) + if testType != "" { + params.Set("scan_type", testType) } if target != "" { params.Set("target", target)