Refactor entrypoint.sh, check.go, and run.go to support positional targets for URL scanning, enhancing flexibility. Update README.md to reflect changes in target handling and provide clearer usage instructions.

This commit is contained in:
Luke Hagar
2025-09-12 15:52:45 +00:00
parent c563c8026f
commit 1085baa766
4 changed files with 84 additions and 42 deletions

View File

@@ -51,14 +51,18 @@ go build -o slinky ./
Usage: Usage:
```bash ```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: Notes:
- Targets can be files, directories, or doublestar globs. Multiple targets are allowed.
```bash - If no targets are provided, the default is `**/*` relative to the current working directory.
slinky run . --patterns "**/*" - Legacy flags `--glob` and `--patterns` are still supported, but positional targets are preferred.
```
### Notes ### Notes

View File

@@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
"path/filepath"
"sort" "sort"
"strings" "strings"
"time" "time"
@@ -29,24 +30,31 @@ type SerializableResult struct {
func init() { func init() {
checkCmd := &cobra.Command{ checkCmd := &cobra.Command{
Use: "check [path]", Use: "check [targets...]",
Short: "Scan a directory for URLs and validate them (headless)", Short: "Scan for URLs and validate them (headless)",
Args: cobra.MaximumNArgs(1), Args: cobra.ArbitraryArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
path := "." path := "."
if len(args) == 1 {
path = args[0]
}
var gl []string var gl []string
if len(patterns) > 0 { if len(args) > 0 {
gl = append(gl, patterns...) for _, a := range args {
} else if globPat != "" { for _, part := range strings.Split(a, ",") {
gl = strings.Split(globPat, ",") p := strings.TrimSpace(part)
if p != "" {
gl = append(gl, toSlash(p))
}
}
}
} else { } else {
gl = []string{"**/*"} 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 timeout := time.Duration(timeoutSeconds) * time.Second
cfg := web.Config{MaxConcurrency: maxConcurrency, RequestTimeout: timeout} 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().IntVar(&maxConcurrency, "concurrency", 16, "maximum concurrent requests")
checkCmd.Flags().StringVar(&jsonOut, "json-out", "", "path to write full JSON results (array)") 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") checkCmd.Flags().StringVar(&mdOut, "md-out", "", "path to write Markdown report for PR comment")
@@ -165,7 +171,43 @@ func init() {
var ( var (
timeoutSeconds int timeoutSeconds int
failOnFailures bool failOnFailures bool
patterns []string
repoBlobBase string repoBlobBase string
respectGitignore bool 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
}

View File

@@ -11,29 +11,28 @@ import (
func init() { func init() {
runCmd := &cobra.Command{ runCmd := &cobra.Command{
Use: "run [path]", Use: "run [targets...]",
Short: "Scan a directory/repo for URLs in files and validate them (TUI)", 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 { RunE: func(cmd *cobra.Command, args []string) error {
path := "."
if len(args) == 1 {
path = args[0]
}
cfg := web.Config{MaxConcurrency: maxConcurrency} cfg := web.Config{MaxConcurrency: maxConcurrency}
var gl []string var gl []string
if len(patterns) > 0 { if len(args) > 0 {
gl = append(gl, patterns...) for _, a := range args {
} else if globPat != "" { for _, part := range strings.Split(a, ",") {
gl = strings.Split(globPat, ",") p := strings.TrimSpace(part)
if p != "" {
gl = append(gl, p)
}
}
}
} else { } else {
gl = []string{"**/*"} 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().IntVar(&maxConcurrency, "concurrency", 16, "maximum concurrent requests")
runCmd.Flags().StringVar(&jsonOut, "json-out", "", "path to write full JSON results (array)") 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") runCmd.Flags().StringVar(&mdOut, "md-out", "", "path to write Markdown report for PR comment")
@@ -44,6 +43,5 @@ func init() {
var ( var (
maxConcurrency int maxConcurrency int
jsonOut string jsonOut string
globPat string
mdOut string mdOut string
) )

View File

@@ -14,7 +14,7 @@ COMMENT_PR_ARG="${INPUT_COMMENT_PR:-true}"
STEP_SUMMARY_ARG="${INPUT_STEP_SUMMARY:-true}" STEP_SUMMARY_ARG="${INPUT_STEP_SUMMARY:-true}"
# Build argv safely # 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 if [ "${FAIL_ON_FAILURES_ARG}" = "true" ]; then
set -- "$@" --fail-on-failures=true set -- "$@" --fail-on-failures=true
else else
@@ -22,12 +22,9 @@ else
fi fi
if [ -n "${PATTERNS_ARG}" ]; then if [ -n "${PATTERNS_ARG}" ]; then
NORM_PATTERNS=$(printf "%s" "${PATTERNS_ARG}" | sed 's/,[[:space:]]*/,/g') set -- "$@" "$PATTERNS_ARG"
IFS=',' else
for pat in $NORM_PATTERNS; do set -- "$@" "**/*"
set -- "$@" --patterns "$pat"
done
unset IFS
fi fi
if [ -n "${JSON_OUT_ARG}" ]; then if [ -n "${JSON_OUT_ARG}" ]; then
@@ -60,8 +57,9 @@ fi
# Emit consolidated config at start (visible with ACTIONS_STEP_DEBUG=true) # Emit consolidated config at start (visible with ACTIONS_STEP_DEBUG=true)
EFFECTIVE_REPO_BLOB_BASE="${SLINKY_REPO_BLOB_BASE_URL:-$REPO_BLOB_BASE_ARG}" 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" \ TARGETS_DEBUG="${PATTERNS_ARG:-**/*}"
"$PATH_ARG" "$PATTERNS_ARG" "$CONCURRENCY_ARG" "$TIMEOUT_ARG" "$RESPECT_GITIGNORE_ARG" "$JSON_OUT_ARG" "$MD_OUT_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" "$FAIL_ON_FAILURES_ARG" "$COMMENT_PR_ARG" "$STEP_SUMMARY_ARG" "$EFFECTIVE_REPO_BLOB_BASE"
printf "::debug:: CLI Args: slinky %s\n" "$*" printf "::debug:: CLI Args: slinky %s\n" "$*"