dev: automated commit - 2025-05-31 17:53:38

This commit is contained in:
Mariano Z. 2025-05-31 17:53:40 -03:00
parent 2d690bbe1c
commit 2681786023
Signed by: marianozunino
GPG key ID: 4C73BAD25156DACE
2 changed files with 212 additions and 174 deletions

376
main.go
View file

@ -30,9 +30,10 @@ type Config struct {
} }
type Script struct { type Script struct {
Path string Path string // Full filesystem path
Name string Name string // Just filename (e.g. "install.sh")
Description string RelPath string // Relative path from runs/ (e.g. "tools/install.sh")
Desc string // Description from script comment
} }
var config Config var config Config
@ -225,21 +226,6 @@ func getDescription(scriptPath string) string {
return "No description" return "No description"
} }
func matchesFilters(scriptName string, filters []string) bool {
if len(filters) == 0 {
return true
}
scriptNameLower := strings.ToLower(scriptName)
for _, filter := range filters {
filterLower := strings.ToLower(filter)
if strings.Contains(scriptName, filter) || strings.Contains(scriptNameLower, filterLower) {
return true
}
}
return false
}
func findScripts(filters []string) ([]Script, error) { func findScripts(filters []string) ([]Script, error) {
var scripts []Script var scripts []Script
@ -249,31 +235,72 @@ func findScripts(filters []string) ([]Script, error) {
} }
if info.Mode().IsRegular() && (info.Mode()&0o111) != 0 { if info.Mode().IsRegular() && (info.Mode()&0o111) != 0 {
// Get relative path from runs directory
relPath, err := filepath.Rel(config.RunsDir, path)
if err != nil {
return err
}
// Skip hidden files and directories
if strings.HasPrefix(filepath.Base(path), ".") {
return nil
}
scriptName := filepath.Base(path) scriptName := filepath.Base(path)
if matchesFilters(scriptName, filters) {
if len(filters) == 0 || matchesFilters(relPath, scriptName, filters) {
scripts = append(scripts, Script{ scripts = append(scripts, Script{
Path: path, Path: path,
Name: scriptName, Name: scriptName,
Description: getDescription(path), RelPath: relPath,
Desc: getDescription(path),
}) })
} }
} }
return nil return nil
}) })
if err != nil {
return nil, err
}
sort.Slice(scripts, func(i, j int) bool { sort.Slice(scripts, func(i, j int) bool {
return scripts[i].Name < scripts[j].Name return scripts[i].RelPath < scripts[j].RelPath
}) })
return scripts, nil return scripts, err
} }
func executeScript(script Script, verbose bool) error { func matchesFilters(relPath, scriptName string, filters []string) bool {
for _, filter := range filters {
// Normalize paths for comparison (remove .sh extension from filter if present)
normalizedFilter := strings.TrimSuffix(filter, ".sh")
normalizedRelPath := strings.TrimSuffix(relPath, ".sh")
normalizedScriptName := strings.TrimSuffix(scriptName, ".sh")
// Check exact matches
if normalizedRelPath == normalizedFilter || normalizedScriptName == normalizedFilter {
return true
}
// Check if filter matches the relative path or script name (case insensitive)
filterLower := strings.ToLower(normalizedFilter)
relPathLower := strings.ToLower(normalizedRelPath)
scriptNameLower := strings.ToLower(normalizedScriptName)
if strings.Contains(relPathLower, filterLower) || strings.Contains(scriptNameLower, filterLower) {
return true
}
// Check directory match (e.g., "tools" matches "tools/install.sh")
if strings.HasPrefix(relPathLower, filterLower+"/") {
return true
}
}
return false
}
func executeScript(script Script, args []string, verbose bool) error {
logFile := filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log") logFile := filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log")
cmd := exec.Command(script.Path)
// Create command with script path and arguments
cmd := exec.Command(script.Path, args...)
if verbose { if verbose {
logFileHandle, err := os.Create(logFile) logFileHandle, err := os.Create(logFile)
@ -298,19 +325,40 @@ func executeScript(script Script, verbose bool) error {
} }
} }
func confirmExecution(scripts []Script) bool { func createNewScript(scriptName string) error {
fmt.Printf(Red + "⚠️ About to run ALL scripts:" + NC + "\n") if err := os.MkdirAll(config.RunsDir, 0o755); err != nil {
for _, script := range scripts { return err
fmt.Printf(" • %s - %s\n", script.Name, script.Description)
} }
fmt.Println()
fmt.Print("Continue? (y/N): ") if !strings.HasSuffix(scriptName, ".sh") {
reader := bufio.NewReader(os.Stdin) scriptName += ".sh"
answer, _ := reader.ReadString('\n') }
answer = strings.TrimSpace(strings.ToLower(answer))
return answer == "y" || answer == "yes" scriptPath := filepath.Join(config.RunsDir, scriptName)
if _, err := os.Stat(scriptPath); err == nil {
return fmt.Errorf("script %s already exists", scriptName)
}
template := fmt.Sprintf(`#!/usr/bin/env bash
# NAME: %s script
set -euo pipefail
echo "Running %s script..."
# Add your commands here
echo "✅ %s completed successfully"
`, strings.TrimSuffix(scriptName, ".sh"), scriptName, strings.TrimSuffix(scriptName, ".sh"))
err := os.WriteFile(scriptPath, []byte(template), 0o755)
if err != nil {
return err
}
log("✅ Created script: %s", scriptPath)
return nil
} }
func initConfig() { func initConfig() {
@ -337,7 +385,6 @@ echo "Edit this script or add your own to runs/"
} }
log("✅ Created runs/ directory with example script") log("✅ Created runs/ directory with example script")
log("Use 'dev ls' to list scripts or 'dev new <name>' to create a new one")
return nil return nil
} }
return nil return nil
@ -346,12 +393,22 @@ echo "Edit this script or add your own to runs/"
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "dev", Use: "dev",
Short: "Development script runner", Short: "Development script runner",
Long: "A simple tool to manage and run development scripts with security scanning", Long: "A CLI tool for managing and running development scripts",
RunE: func(cmd *cobra.Command, args []string) error {
// Default behavior: list scripts if no subcommand
return listCmd.RunE(cmd, args)
},
} }
var runCmd = &cobra.Command{ var runCmd = &cobra.Command{
Use: "run [filters...]", Use: "run [filters...] [-- script-args...]",
Short: "Run scripts matching filters (or all if no filters)", Short: "Run scripts matching filters (or all if no filters)",
Long: `Run scripts matching filters. Use -- to pass arguments to scripts.
Examples:
dev run tools/dev.sh # Run specific script
dev run tools/dev.sh -- arg1 arg2 # Run with arguments
dev run --verbose install -- --force # Run with flags`,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
scripts, err := findScripts(nil) scripts, err := findScripts(nil)
if err != nil { if err != nil {
@ -360,14 +417,51 @@ var runCmd = &cobra.Command{
var completions []string var completions []string
for _, script := range scripts { for _, script := range scripts {
scriptName := strings.TrimSuffix(script.Name, ".sh") // Add both the script name and relative path as completion options
if strings.HasPrefix(scriptName, toComplete) { if strings.HasPrefix(script.Name, toComplete) || strings.HasPrefix(script.RelPath, toComplete) {
completions = append(completions, scriptName+"\t"+script.Description) // Use relative path as the completion value with description
completions = append(completions, fmt.Sprintf("%s\t%s", script.RelPath, script.Desc))
} }
} }
return completions, cobra.ShellCompDirectiveNoFileComp return completions, cobra.ShellCompDirectiveNoFileComp
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
// Parse arguments manually to handle -- separator
var filters []string
var scriptArgs []string
// Find "run" command position and -- separator in raw args
rawArgs := os.Args[1:] // Skip program name
runIndex := -1
dashDashIndex := -1
for i, arg := range rawArgs {
if arg == "run" {
runIndex = i
}
if arg == "--" && runIndex >= 0 {
dashDashIndex = i
break
}
}
if dashDashIndex >= 0 {
// Extract everything between "run" and "--" as filters (skip flags)
for i := runIndex + 1; i < dashDashIndex; i++ {
arg := rawArgs[i]
// Skip flags
if !strings.HasPrefix(arg, "-") {
filters = append(filters, arg)
}
}
// Everything after "--" are script args
scriptArgs = rawArgs[dashDashIndex+1:]
} else {
// No --, use cobra's args as filters
filters = args
scriptArgs = []string{}
}
if err := ensureRunsDir(); err != nil { if err := ensureRunsDir(); err != nil {
return err return err
} }
@ -376,68 +470,88 @@ var runCmd = &cobra.Command{
return err return err
} }
scripts, err := findScripts(args) scripts, err := findScripts(filters)
if err != nil { if err != nil {
return err return err
} }
if len(scripts) == 0 { if len(scripts) == 0 {
if len(args) > 0 { if len(filters) > 0 {
warn("No scripts match the given filters: %s", strings.Join(args, ", ")) warn("No scripts match filters: %s", strings.Join(filters, ", "))
fmt.Println("Use 'dev ls' to see all available scripts")
} else { } else {
warn("No executable scripts found in %s", config.RunsDir) warn("No scripts found. Use 'dev new <n>' to create one")
fmt.Println("Use 'dev new <name>' to create a new script")
} }
return nil return nil
} }
if len(args) == 0 && !config.DryRun { // CLI execution
if !confirmExecution(scripts) {
fmt.Println("Cancelled")
return nil
}
}
var executed, failed []string
for _, script := range scripts { for _, script := range scripts {
if config.DryRun { if config.DryRun {
fmt.Printf(Blue+"[DRY]"+NC+" Would run: %s - %s\n", script.Name, script.Description) argsStr := ""
if len(scriptArgs) > 0 {
argsStr = fmt.Sprintf(" with args: %s", strings.Join(scriptArgs, " "))
}
fmt.Printf("[DRY] Would run: %s - %s%s\n", script.RelPath, script.Desc, argsStr)
continue continue
} }
fmt.Printf("\n"+Blue+"▶"+NC+" Running: %s\n", script.Name) argsDisplay := ""
if script.Description != "No description" { if len(scriptArgs) > 0 {
fmt.Printf(" %s\n", script.Description) argsDisplay = fmt.Sprintf(" with args: %s", strings.Join(scriptArgs, " "))
} }
fmt.Printf("Running: %s%s\n", script.RelPath, argsDisplay)
if err := executeScript(script, config.Verbose); err != nil { if err := executeScript(script, scriptArgs, config.Verbose); err != nil {
errorLog("❌ %s failed (see %s)", script.Name, errorLog("❌ %s failed", script.RelPath)
filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log")) if !config.Verbose {
failed = append(failed, script.Name) fmt.Printf(" Check log: %s\n", filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log"))
} else {
log("✅ %s completed", script.Name)
executed = append(executed, script.Name)
}
}
if !config.DryRun {
fmt.Printf("\n" + Blue + "═══ SUMMARY ═══" + NC + "\n")
fmt.Printf("✅ Completed: %d\n", len(executed))
if len(failed) > 0 {
fmt.Printf("❌ Failed: %d\n", len(failed))
fmt.Printf("\n" + Red + "Failed scripts:" + NC + "\n")
for _, f := range failed {
fmt.Printf(" • %s\n", f)
} }
return fmt.Errorf("some scripts failed") } else {
log("✅ %s completed", script.RelPath)
} }
} }
return nil return nil
}, },
} }
var listCmd = &cobra.Command{
Use: "ls",
Aliases: []string{"list"},
Short: "List all available scripts",
RunE: func(cmd *cobra.Command, args []string) error {
if err := ensureRunsDir(); err != nil {
return err
}
scripts, err := findScripts(nil)
if err != nil {
return err
}
if len(scripts) == 0 {
warn("No scripts found in %s", config.RunsDir)
fmt.Println("Use 'dev new <n>' to create a new script")
return nil
}
fmt.Printf("Available scripts in %s:\n\n", config.RunsDir)
for _, script := range scripts {
fmt.Printf(" %s%s%s - %s\n", Blue, script.RelPath, NC, script.Desc)
}
fmt.Printf("\nTotal: %d scripts\n", len(scripts))
return nil
},
}
var newCmd = &cobra.Command{
Use: "new <n>",
Short: "Create a new script template",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return createNewScript(args[0])
},
}
var pushCmd = &cobra.Command{ var pushCmd = &cobra.Command{
Use: "push", Use: "push",
Aliases: []string{"u", "ush"}, Aliases: []string{"u", "ush"},
@ -447,73 +561,6 @@ var pushCmd = &cobra.Command{
}, },
} }
var listCmd = &cobra.Command{
Use: "ls",
Aliases: []string{"list"},
Short: "List all available scripts",
RunE: func(cmd *cobra.Command, args []string) error {
scripts, err := findScripts(nil)
if err != nil {
return err
}
if len(scripts) == 0 {
warn("No executable scripts found in %s", config.RunsDir)
return nil
}
fmt.Printf(Blue + "Available scripts:" + NC + "\n")
for _, script := range scripts {
fmt.Printf(" %s%s%s - %s\n", Green, script.Name, NC, script.Description)
}
fmt.Printf("\nTotal: %d scripts\n", len(scripts))
return nil
},
}
var newCmd = &cobra.Command{
Use: "new <name>",
Short: "Create a new script template",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
scriptName := args[0]
if err := os.MkdirAll(config.RunsDir, 0o755); err != nil {
return err
}
if !strings.HasSuffix(scriptName, ".sh") {
scriptName += ".sh"
}
scriptPath := filepath.Join(config.RunsDir, scriptName)
if _, err := os.Stat(scriptPath); err == nil {
return fmt.Errorf("script %s already exists", scriptName)
}
template := fmt.Sprintf(`#!/usr/bin/env bash
# NAME: %s script
set -euo pipefail
echo "Running %s script..."
# Add your commands here
echo "✅ %s completed successfully"
`, strings.TrimSuffix(scriptName, ".sh"), scriptName, strings.TrimSuffix(scriptName, ".sh"))
if err := os.WriteFile(scriptPath, []byte(template), 0o755); err != nil {
return err
}
log("✅ Created new script: %s", scriptPath)
log("Edit the script to add your commands")
return nil
},
}
var completionCmd = &cobra.Command{ var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]", Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate completion script", Short: "Generate completion script",
@ -537,19 +584,6 @@ Zsh:
$ dev completion zsh > "${fpath[1]}/_dev" $ dev completion zsh > "${fpath[1]}/_dev"
# You will need to start a new shell for this setup to take effect. # You will need to start a new shell for this setup to take effect.
Fish:
$ dev completion fish | source
# To load completions for each session, execute once:
$ dev completion fish > ~/.config/fish/completions/dev.fish
PowerShell:
PS> dev completion powershell | Out-String | Invoke-Expression
# To load completions for every new session, run:
PS> dev completion powershell > dev.ps1
# and source this file from your PowerShell profile.
`, `,
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
@ -569,22 +603,16 @@ PowerShell:
}, },
} }
var depsCmd = &cobra.Command{
Use: "deps",
Aliases: []string{"check"},
Short: "Check for required dependencies",
RunE: func(cmd *cobra.Command, args []string) error {
return checkDependencies()
},
}
func main() { func main() {
initConfig() initConfig()
runCmd.Flags().BoolVar(&config.DryRun, "dry", false, "Show what would run without executing") runCmd.Flags().BoolVar(&config.DryRun, "dry", false, "Show what would run without executing")
runCmd.Flags().BoolVarP(&config.Verbose, "verbose", "v", false, "Show detailed output during execution") runCmd.Flags().BoolVarP(&config.Verbose, "verbose", "v", false, "Show script output in terminal")
rootCmd.AddCommand(runCmd, pushCmd, listCmd, newCmd, depsCmd, completionCmd) // This prevents Cobra from consuming -- and everything after it
runCmd.Flags().SetInterspersed(false)
rootCmd.AddCommand(runCmd, listCmd, newCmd, pushCmd, completionCmd)
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
os.Exit(1) os.Exit(1)

10
runs/tools/dev.sh Executable file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# NAME: tools/dev script
echo "Running tools/dev.sh script..."
echo $1 $2 $3
# Add your commands here
echo "✅ tools/dev completed successfully"