diff --git a/README.md b/README.md index d902f0c..ed52c90 100644 --- a/README.md +++ b/README.md @@ -51,14 +51,18 @@ go build -o slinky ./ Usage: ```bash -slinky check . --patterns "docs/**/*.md" --patterns "**/*.go" --md-out results.md --json-out results.json +# Headless: provide one or more targets (files, dirs, or globs) +slinky check **/* +slinky check ./docs/**/* ./markdown/**/* + +# TUI mode: same targets +slinky run **/* ``` -TUI mode: - -```bash -slinky run . --patterns "**/*" -``` +Notes: +- Targets can be files, directories, or doublestar globs. Multiple targets are allowed. +- If no targets are provided, the default is `**/*` relative to the current working directory. +- Legacy flags `--glob` and `--patterns` are still supported, but positional targets are preferred. ### Notes diff --git a/cmd/check.go b/cmd/check.go index 35a3989..7f77c1c 100644 --- a/cmd/check.go +++ b/cmd/check.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "os" + "path/filepath" "sort" "strings" "time" @@ -29,24 +30,31 @@ type SerializableResult struct { func init() { checkCmd := &cobra.Command{ - Use: "check [path]", - Short: "Scan a directory for URLs and validate them (headless)", - Args: cobra.MaximumNArgs(1), + Use: "check [targets...]", + Short: "Scan for URLs and validate them (headless)", + Args: cobra.ArbitraryArgs, RunE: func(cmd *cobra.Command, args []string) error { path := "." - if len(args) == 1 { - path = args[0] - } var gl []string - if len(patterns) > 0 { - gl = append(gl, patterns...) - } else if globPat != "" { - gl = strings.Split(globPat, ",") + if len(args) > 0 { + for _, a := range args { + for _, part := range strings.Split(a, ",") { + p := strings.TrimSpace(part) + if p != "" { + gl = append(gl, toSlash(p)) + } + } + } } else { gl = []string{"**/*"} } + gl = expandDirectories(path, gl) + + // Emit normalized patterns for debugging + fmt.Printf("::debug:: Effective patterns: %s\n", strings.Join(gl, ",")) + timeout := time.Duration(timeoutSeconds) * time.Second cfg := web.Config{MaxConcurrency: maxConcurrency, RequestTimeout: timeout} @@ -149,8 +157,6 @@ func init() { }, } - checkCmd.Flags().StringVar(&globPat, "glob", "", "comma-separated glob patterns for files (doublestar); empty = all files") - checkCmd.Flags().StringSliceVar(&patterns, "patterns", nil, "file match patterns (doublestar). Examples: docs/**/*.md,**/*.go; defaults to **/*") checkCmd.Flags().IntVar(&maxConcurrency, "concurrency", 16, "maximum concurrent requests") checkCmd.Flags().StringVar(&jsonOut, "json-out", "", "path to write full JSON results (array)") checkCmd.Flags().StringVar(&mdOut, "md-out", "", "path to write Markdown report for PR comment") @@ -165,7 +171,43 @@ func init() { var ( timeoutSeconds int failOnFailures bool - patterns []string repoBlobBase string respectGitignore bool ) + +func toSlash(p string) string { + p = strings.TrimSpace(p) + if p == "" { + return p + } + p = filepath.ToSlash(p) + if after, ok := strings.CutPrefix(p, "./"); ok { + p = after + } + return p +} + +func hasGlobMeta(s string) bool { + return strings.ContainsAny(s, "*?[") +} + +func expandDirectories(root string, pats []string) []string { + var out []string + for _, p := range pats { + pp := strings.TrimSpace(p) + if pp == "" { + continue + } + if hasGlobMeta(pp) { + out = append(out, pp) + continue + } + abs := filepath.Join(root, filepath.FromSlash(pp)) + if fi, err := os.Stat(abs); err == nil && fi.IsDir() { + out = append(out, strings.TrimSuffix(pp, "/")+"/**/*") + } else { + out = append(out, pp) + } + } + return out +} diff --git a/cmd/run.go b/cmd/run.go index 6530c38..ba229d6 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -11,29 +11,28 @@ import ( func init() { runCmd := &cobra.Command{ - Use: "run [path]", + Use: "run [targets...]", Short: "Scan a directory/repo for URLs in files and validate them (TUI)", - Args: cobra.MaximumNArgs(1), + Args: cobra.ArbitraryArgs, RunE: func(cmd *cobra.Command, args []string) error { - path := "." - if len(args) == 1 { - path = args[0] - } cfg := web.Config{MaxConcurrency: maxConcurrency} var gl []string - if len(patterns) > 0 { - gl = append(gl, patterns...) - } else if globPat != "" { - gl = strings.Split(globPat, ",") + if len(args) > 0 { + for _, a := range args { + for _, part := range strings.Split(a, ",") { + p := strings.TrimSpace(part) + if p != "" { + gl = append(gl, p) + } + } + } } else { gl = []string{"**/*"} } - return tui.Run(path, gl, cfg, jsonOut, mdOut) + return tui.Run(".", gl, cfg, jsonOut, mdOut) }, } - runCmd.Flags().StringVar(&globPat, "glob", "", "comma-separated glob patterns for files (doublestar); empty = all files") - runCmd.Flags().StringSliceVar(&patterns, "patterns", nil, "file match patterns (doublestar). Examples: docs/**/*.md,**/*.go; defaults to **/*") runCmd.Flags().IntVar(&maxConcurrency, "concurrency", 16, "maximum concurrent requests") runCmd.Flags().StringVar(&jsonOut, "json-out", "", "path to write full JSON results (array)") runCmd.Flags().StringVar(&mdOut, "md-out", "", "path to write Markdown report for PR comment") @@ -44,6 +43,5 @@ func init() { var ( maxConcurrency int jsonOut string - globPat string mdOut string ) diff --git a/entrypoint.sh b/entrypoint.sh index 6a27256..acd8b56 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -14,7 +14,7 @@ COMMENT_PR_ARG="${INPUT_COMMENT_PR:-true}" STEP_SUMMARY_ARG="${INPUT_STEP_SUMMARY:-true}" # Build argv safely -set -- check "$PATH_ARG" --concurrency "$CONCURRENCY_ARG" --timeout "$TIMEOUT_ARG" +set -- check --concurrency "$CONCURRENCY_ARG" --timeout "$TIMEOUT_ARG" if [ "${FAIL_ON_FAILURES_ARG}" = "true" ]; then set -- "$@" --fail-on-failures=true else @@ -22,12 +22,9 @@ else fi if [ -n "${PATTERNS_ARG}" ]; then - NORM_PATTERNS=$(printf "%s" "${PATTERNS_ARG}" | sed 's/,[[:space:]]*/,/g') - IFS=',' - for pat in $NORM_PATTERNS; do - set -- "$@" --patterns "$pat" - done - unset IFS + set -- "$@" "$PATTERNS_ARG" +else + set -- "$@" "**/*" fi if [ -n "${JSON_OUT_ARG}" ]; then @@ -60,8 +57,9 @@ fi # Emit consolidated config at start (visible with ACTIONS_STEP_DEBUG=true) EFFECTIVE_REPO_BLOB_BASE="${SLINKY_REPO_BLOB_BASE_URL:-$REPO_BLOB_BASE_ARG}" -printf "::debug:: Config: path=%s patterns=%s concurrency=%s timeout=%s respect_gitignore=%s json_out=%s md_out=%s fail_on_failures=%s comment_pr=%s step_summary=%s repo_blob_base_url=%s\n" \ - "$PATH_ARG" "$PATTERNS_ARG" "$CONCURRENCY_ARG" "$TIMEOUT_ARG" "$RESPECT_GITIGNORE_ARG" "$JSON_OUT_ARG" "$MD_OUT_ARG" \ +TARGETS_DEBUG="${PATTERNS_ARG:-**/*}" +printf "::debug:: Config: targets=%s concurrency=%s timeout=%s respect_gitignore=%s json_out=%s md_out=%s fail_on_failures=%s comment_pr=%s step_summary=%s repo_blob_base_url=%s\n" \ + "$TARGETS_DEBUG" "$CONCURRENCY_ARG" "$TIMEOUT_ARG" "$RESPECT_GITIGNORE_ARG" "$JSON_OUT_ARG" "$MD_OUT_ARG" \ "$FAIL_ON_FAILURES_ARG" "$COMMENT_PR_ARG" "$STEP_SUMMARY_ARG" "$EFFECTIVE_REPO_BLOB_BASE" printf "::debug:: CLI Args: slinky %s\n" "$*"