dev: automated commit - 2025-05-31 17:07:41
This commit is contained in:
		
							parent
							
								
									f037255a1a
								
							
						
					
					
						commit
						b35d327830
					
				
					 4 changed files with 209 additions and 345 deletions
				
			
		
							
								
								
									
										2
									
								
								dev
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								dev
									
										
									
									
									
								
							|  | @ -34,6 +34,8 @@ if [[ ! -f "$BINARY" ]] || [[ "main.go" -nt "$BINARY" ]]; then | ||||||
|     log "Building dev tool..." |     log "Building dev tool..." | ||||||
|     mkdir -p bin |     mkdir -p bin | ||||||
| 
 | 
 | ||||||
|  |     go mod tidy | ||||||
|  | 
 | ||||||
|     if go build -ldflags="-s -w" -o "$BINARY" main.go; then |     if go build -ldflags="-s -w" -o "$BINARY" main.go; then | ||||||
|         log "✅ Built: $BINARY" |         log "✅ Built: $BINARY" | ||||||
|     else |     else | ||||||
|  |  | ||||||
							
								
								
									
										7
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										7
									
								
								go.mod
									
										
									
									
									
								
							|  | @ -1,3 +1,10 @@ | ||||||
| module marianozunino/dev | module marianozunino/dev | ||||||
| 
 | 
 | ||||||
| go 1.24.1 | go 1.24.1 | ||||||
|  | 
 | ||||||
|  | require github.com/spf13/cobra v1.9.1 | ||||||
|  | 
 | ||||||
|  | require ( | ||||||
|  | 	github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||||||
|  | 	github.com/spf13/pflag v1.0.6 // indirect | ||||||
|  | ) | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								go.sum
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								go.sum
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= | ||||||
|  | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= | ||||||
|  | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= | ||||||
|  | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||||||
|  | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= | ||||||
|  | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= | ||||||
|  | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= | ||||||
|  | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||||
|  | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
|  | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
							
								
								
									
										463
									
								
								main.go
									
										
									
									
									
								
							
							
						
						
									
										463
									
								
								main.go
									
										
									
									
									
								
							|  | @ -10,43 +10,33 @@ import ( | ||||||
| 	"sort" | 	"sort" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/spf13/cobra" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Colors for terminal output |  | ||||||
| const ( | const ( | ||||||
| 	Red    = "\033[0;31m" | 	Red    = "\033[0;31m" | ||||||
| 	Green  = "\033[0;32m" | 	Green  = "\033[0;32m" | ||||||
| 	Blue   = "\033[0;34m" | 	Blue   = "\033[0;34m" | ||||||
| 	Yellow = "\033[0;33m" | 	Yellow = "\033[0;33m" | ||||||
| 	NC     = "\033[0m" // No Color | 	NC     = "\033[0m" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Global configuration |  | ||||||
| type Config struct { | type Config struct { | ||||||
| 	ScriptDir string |  | ||||||
| 	EnvFile   string |  | ||||||
| 	RunsDir string | 	RunsDir string | ||||||
| 	LogsDir string | 	LogsDir string | ||||||
| 	DryRun  bool | 	DryRun  bool | ||||||
| 	Verbose bool | 	Verbose bool | ||||||
| 	Filters   []string |  | ||||||
| 	Command   string |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Script represents an executable script |  | ||||||
| type Script struct { | type Script struct { | ||||||
| 	Path        string | 	Path        string | ||||||
| 	Name        string | 	Name        string | ||||||
| 	Description string | 	Description string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ExecutionResult tracks script execution outcomes | var config Config | ||||||
| type ExecutionResult struct { |  | ||||||
| 	Executed []string |  | ||||||
| 	Failed   []string |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| // Logging functions |  | ||||||
| func log(msg string, args ...interface{}) { | func log(msg string, args ...interface{}) { | ||||||
| 	fmt.Printf(Green+"[RUN]"+NC+" "+msg+"\n", args...) | 	fmt.Printf(Green+"[RUN]"+NC+" "+msg+"\n", args...) | ||||||
| } | } | ||||||
|  | @ -59,13 +49,11 @@ func errorLog(msg string, args ...interface{}) { | ||||||
| 	fmt.Fprintf(os.Stderr, Red+"[ERROR]"+NC+" "+msg+"\n", args...) | 	fmt.Fprintf(os.Stderr, Red+"[ERROR]"+NC+" "+msg+"\n", args...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Check if command exists in PATH |  | ||||||
| func commandExists(cmd string) bool { | func commandExists(cmd string) bool { | ||||||
| 	_, err := exec.LookPath(cmd) | 	_, err := exec.LookPath(cmd) | ||||||
| 	return err == nil | 	return err == nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Check for required and optional dependencies |  | ||||||
| func checkDependencies() error { | func checkDependencies() error { | ||||||
| 	requiredTools := []string{"git", "find", "grep"} | 	requiredTools := []string{"git", "find", "grep"} | ||||||
| 	optionalTools := []string{"gitleaks"} | 	optionalTools := []string{"gitleaks"} | ||||||
|  | @ -73,14 +61,12 @@ func checkDependencies() error { | ||||||
| 
 | 
 | ||||||
| 	log("Checking dependencies...") | 	log("Checking dependencies...") | ||||||
| 
 | 
 | ||||||
| 	// Check required tools |  | ||||||
| 	for _, tool := range requiredTools { | 	for _, tool := range requiredTools { | ||||||
| 		if !commandExists(tool) { | 		if !commandExists(tool) { | ||||||
| 			missing = append(missing, tool) | 			missing = append(missing, tool) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Check optional tools |  | ||||||
| 	for _, tool := range optionalTools { | 	for _, tool := range optionalTools { | ||||||
| 		if !commandExists(tool) { | 		if !commandExists(tool) { | ||||||
| 			warn("Optional tool missing: %s (recommended for security scanning)", tool) | 			warn("Optional tool missing: %s (recommended for security scanning)", tool) | ||||||
|  | @ -89,7 +75,6 @@ func checkDependencies() error { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Report missing required tools |  | ||||||
| 	if len(missing) > 0 { | 	if len(missing) > 0 { | ||||||
| 		errorLog("Missing required tools: %s", strings.Join(missing, ", ")) | 		errorLog("Missing required tools: %s", strings.Join(missing, ", ")) | ||||||
| 		errorLog("Please install missing dependencies") | 		errorLog("Please install missing dependencies") | ||||||
|  | @ -100,7 +85,6 @@ func checkDependencies() error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Run security scan using gitleaks |  | ||||||
| func runSecurityScan() error { | func runSecurityScan() error { | ||||||
| 	log("Running security scan...") | 	log("Running security scan...") | ||||||
| 
 | 
 | ||||||
|  | @ -133,19 +117,16 @@ func runSecurityScan() error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Check if we're in a git repository |  | ||||||
| func isGitRepo() bool { | func isGitRepo() bool { | ||||||
| 	cmd := exec.Command("git", "rev-parse", "--git-dir") | 	cmd := exec.Command("git", "rev-parse", "--git-dir") | ||||||
| 	return cmd.Run() == nil | 	return cmd.Run() == nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Check for uncommitted changes |  | ||||||
| func hasUncommittedChanges() bool { | func hasUncommittedChanges() bool { | ||||||
| 	cmd := exec.Command("git", "diff-index", "--quiet", "HEAD", "--") | 	cmd := exec.Command("git", "diff-index", "--quiet", "HEAD", "--") | ||||||
| 	return cmd.Run() != nil | 	return cmd.Run() != nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Get current git branch |  | ||||||
| func getCurrentBranch() (string, error) { | func getCurrentBranch() (string, error) { | ||||||
| 	cmd := exec.Command("git", "branch", "--show-current") | 	cmd := exec.Command("git", "branch", "--show-current") | ||||||
| 	output, err := cmd.Output() | 	output, err := cmd.Output() | ||||||
|  | @ -155,28 +136,23 @@ func getCurrentBranch() (string, error) { | ||||||
| 	return strings.TrimSpace(string(output)), nil | 	return strings.TrimSpace(string(output)), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Handle git push workflow |  | ||||||
| func handlePush() error { | func handlePush() error { | ||||||
| 	log("Preparing to push repository...") | 	log("Preparing to push repository...") | ||||||
| 
 | 
 | ||||||
| 	// Check if we're in a git repository |  | ||||||
| 	if !isGitRepo() { | 	if !isGitRepo() { | ||||||
| 		errorLog("Not in a git repository") | 		errorLog("Not in a git repository") | ||||||
| 		return fmt.Errorf("not in git repo") | 		return fmt.Errorf("not in git repo") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Check for uncommitted changes |  | ||||||
| 	if hasUncommittedChanges() { | 	if hasUncommittedChanges() { | ||||||
| 		warn("You have uncommitted changes") | 		warn("You have uncommitted changes") | ||||||
| 		fmt.Println() | 		fmt.Println() | ||||||
| 
 | 
 | ||||||
| 		// Show git status |  | ||||||
| 		cmd := exec.Command("git", "status", "--short") | 		cmd := exec.Command("git", "status", "--short") | ||||||
| 		cmd.Stdout = os.Stdout | 		cmd.Stdout = os.Stdout | ||||||
| 		cmd.Run() | 		cmd.Run() | ||||||
| 		fmt.Println() | 		fmt.Println() | ||||||
| 
 | 
 | ||||||
| 		// Generate default commit message |  | ||||||
| 		defaultMsg := fmt.Sprintf("dev: automated commit - %s", time.Now().Format("2006-01-02 15:04:05")) | 		defaultMsg := fmt.Sprintf("dev: automated commit - %s", time.Now().Format("2006-01-02 15:04:05")) | ||||||
| 
 | 
 | ||||||
| 		fmt.Print("Commit all changes? (Y/n): ") | 		fmt.Print("Commit all changes? (Y/n): ") | ||||||
|  | @ -196,7 +172,6 @@ func handlePush() error { | ||||||
| 				commitMsg = defaultMsg | 				commitMsg = defaultMsg | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			// Add and commit changes |  | ||||||
| 			if err := exec.Command("git", "add", ".").Run(); err != nil { | 			if err := exec.Command("git", "add", ".").Run(); err != nil { | ||||||
| 				return fmt.Errorf("failed to add changes: %v", err) | 				return fmt.Errorf("failed to add changes: %v", err) | ||||||
| 			} | 			} | ||||||
|  | @ -209,18 +184,15 @@ func handlePush() error { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Run security scan |  | ||||||
| 	if err := runSecurityScan(); err != nil { | 	if err := runSecurityScan(); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Get current branch |  | ||||||
| 	branch, err := getCurrentBranch() | 	branch, err := getCurrentBranch() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("failed to get current branch: %v", err) | 		return fmt.Errorf("failed to get current branch: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Push to origin |  | ||||||
| 	log("Pushing branch '%s' to origin...", branch) | 	log("Pushing branch '%s' to origin...", branch) | ||||||
| 	cmd := exec.Command("git", "push", "origin", branch) | 	cmd := exec.Command("git", "push", "origin", branch) | ||||||
| 
 | 
 | ||||||
|  | @ -233,41 +205,6 @@ func handlePush() error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Show help message |  | ||||||
| func showHelp(programName string) { |  | ||||||
| 	fmt.Printf(`%s - Development script runner |  | ||||||
| 
 |  | ||||||
| Usage: %s <command> [options] [arguments] |  | ||||||
| 
 |  | ||||||
| COMMANDS: |  | ||||||
|   run [filters...]  Run scripts matching filters (or all if no filters) |  | ||||||
|   push,u,ush        Commit, scan for secrets, and push to git origin |  | ||||||
|   ls, list          List all available scripts |  | ||||||
|   new <name>        Create a new script template |  | ||||||
|   deps, check       Check for required dependencies |  | ||||||
|   help              Show this help message |  | ||||||
| 
 |  | ||||||
| RUN OPTIONS: |  | ||||||
|   --dry             Show what would run without executing |  | ||||||
|   --verbose         Show detailed output during execution |  | ||||||
| 
 |  | ||||||
| EXAMPLES: |  | ||||||
|   %s                # Show this help |  | ||||||
|   %s run            # Run all scripts (with confirmation) |  | ||||||
|   %s run docker     # Run scripts containing 'docker' |  | ||||||
|   %s run --dry api  # Show what API scripts would run |  | ||||||
|   %s ls             # List all scripts |  | ||||||
|   %s new backup     # Create a new script called 'backup.sh' |  | ||||||
|   %s push           # Secure git push with secret scanning |  | ||||||
| 
 |  | ||||||
| SECURITY: |  | ||||||
|   The push command automatically scans for secrets using GitLeaks |  | ||||||
|   before pushing to prevent accidental credential exposure. |  | ||||||
| 
 |  | ||||||
| `, programName, programName, programName, programName, programName, programName, programName, programName, programName) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Get script description from comment |  | ||||||
| func getDescription(scriptPath string) string { | func getDescription(scriptPath string) string { | ||||||
| 	file, err := os.Open(scriptPath) | 	file, err := os.Open(scriptPath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -288,7 +225,6 @@ func getDescription(scriptPath string) string { | ||||||
| 	return "No description" | 	return "No description" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Check if script name matches any of the filters |  | ||||||
| func matchesFilters(scriptName string, filters []string) bool { | func matchesFilters(scriptName string, filters []string) bool { | ||||||
| 	if len(filters) == 0 { | 	if len(filters) == 0 { | ||||||
| 		return true | 		return true | ||||||
|  | @ -304,16 +240,14 @@ func matchesFilters(scriptName string, filters []string) bool { | ||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Find executable scripts in runs directory | func findScripts(filters []string) ([]Script, error) { | ||||||
| func findScripts(runsDir string, filters []string) ([]Script, error) { |  | ||||||
| 	var scripts []Script | 	var scripts []Script | ||||||
| 
 | 
 | ||||||
| 	err := filepath.Walk(runsDir, func(path string, info os.FileInfo, err error) error { | 	err := filepath.Walk(config.RunsDir, func(path string, info os.FileInfo, err error) error { | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Check if file is executable |  | ||||||
| 		if info.Mode().IsRegular() && (info.Mode()&0o111) != 0 { | 		if info.Mode().IsRegular() && (info.Mode()&0o111) != 0 { | ||||||
| 			scriptName := filepath.Base(path) | 			scriptName := filepath.Base(path) | ||||||
| 			if matchesFilters(scriptName, filters) { | 			if matchesFilters(scriptName, filters) { | ||||||
|  | @ -330,7 +264,6 @@ func findScripts(runsDir string, filters []string) ([]Script, error) { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Sort scripts by name |  | ||||||
| 	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].Name < scripts[j].Name | ||||||
| 	}) | 	}) | ||||||
|  | @ -338,96 +271,21 @@ func findScripts(runsDir string, filters []string) ([]Script, error) { | ||||||
| 	return scripts, nil | 	return scripts, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // List all available scripts | func executeScript(script Script, verbose bool) error { | ||||||
| func listScripts(runsDir string) error { | 	logFile := filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log") | ||||||
| 	scripts, err := findScripts(runsDir, nil) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if len(scripts) == 0 { |  | ||||||
| 		warn("No executable scripts found in %s", 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 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Create a new script template |  | ||||||
| func createNewScript(runsDir, scriptName string) error { |  | ||||||
| 	if scriptName == "" { |  | ||||||
| 		return fmt.Errorf("script name required") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Ensure runs directory exists |  | ||||||
| 	if err := os.MkdirAll(runsDir, 0o755); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Add .sh extension if not provided |  | ||||||
| 	if !strings.HasSuffix(scriptName, ".sh") { |  | ||||||
| 		scriptName += ".sh" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	scriptPath := filepath.Join(runsDir, scriptName) |  | ||||||
| 
 |  | ||||||
| 	// Check if script already exists |  | ||||||
| 	if _, err := os.Stat(scriptPath); err == nil { |  | ||||||
| 		return fmt.Errorf("script %s already exists", scriptName) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Create script template |  | ||||||
| 	template := fmt.Sprintf(`#!/usr/bin/env bash |  | ||||||
| # NAME: %s script |  | ||||||
| 
 |  | ||||||
| set -euo pipefail |  | ||||||
| 
 |  | ||||||
| # Add your script logic here |  | ||||||
| echo "Running %s script..." |  | ||||||
| 
 |  | ||||||
| # Example: |  | ||||||
| # - Add your commands below |  | ||||||
| # - Use proper error handling |  | ||||||
| # - Add logging as needed |  | ||||||
| 
 |  | ||||||
| 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 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Execute a single script |  | ||||||
| func executeScript(script Script, logsDir string, verbose bool) error { |  | ||||||
| 	logFile := filepath.Join(logsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log") |  | ||||||
| 
 |  | ||||||
| 	cmd := exec.Command(script.Path) | 	cmd := exec.Command(script.Path) | ||||||
| 
 | 
 | ||||||
| 	if verbose { | 	if verbose { | ||||||
| 		// Create log file |  | ||||||
| 		logFileHandle, err := os.Create(logFile) | 		logFileHandle, err := os.Create(logFile) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		defer logFileHandle.Close() | 		defer logFileHandle.Close() | ||||||
| 
 | 
 | ||||||
| 		// Use MultiWriter to write to both stdout and log file |  | ||||||
| 		cmd.Stdout = io.MultiWriter(os.Stdout, logFileHandle) | 		cmd.Stdout = io.MultiWriter(os.Stdout, logFileHandle) | ||||||
| 		cmd.Stderr = io.MultiWriter(os.Stderr, logFileHandle) | 		cmd.Stderr = io.MultiWriter(os.Stderr, logFileHandle) | ||||||
| 
 |  | ||||||
| 		return cmd.Run() | 		return cmd.Run() | ||||||
| 	} else { | 	} else { | ||||||
| 		// Only write to log file |  | ||||||
| 		logFileHandle, err := os.Create(logFile) | 		logFileHandle, err := os.Create(logFile) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
|  | @ -436,77 +294,10 @@ func executeScript(script Script, logsDir string, verbose bool) error { | ||||||
| 
 | 
 | ||||||
| 		cmd.Stdout = logFileHandle | 		cmd.Stdout = logFileHandle | ||||||
| 		cmd.Stderr = logFileHandle | 		cmd.Stderr = logFileHandle | ||||||
| 
 |  | ||||||
| 		return cmd.Run() | 		return cmd.Run() | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Parse command line arguments |  | ||||||
| func parseArgs(args []string) (*Config, error) { |  | ||||||
| 	config := &Config{} |  | ||||||
| 
 |  | ||||||
| 	// Get current working directory (equivalent to bash SCRIPT_DIR) |  | ||||||
| 	scriptDir, err := os.Getwd() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	config.ScriptDir = scriptDir |  | ||||||
| 	config.EnvFile = filepath.Join(config.ScriptDir, ".env") |  | ||||||
| 	config.RunsDir = filepath.Join(config.ScriptDir, "runs") |  | ||||||
| 	config.LogsDir = filepath.Join(config.ScriptDir, "logs") |  | ||||||
| 
 |  | ||||||
| 	if len(args) == 0 { |  | ||||||
| 		return config, fmt.Errorf("help") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	i := 0 |  | ||||||
| 	// First argument should be the command |  | ||||||
| 	if i < len(args) { |  | ||||||
| 		arg := args[i] |  | ||||||
| 		switch arg { |  | ||||||
| 		case "--help", "help": |  | ||||||
| 			return config, fmt.Errorf("help") |  | ||||||
| 		case "run": |  | ||||||
| 			config.Command = "run" |  | ||||||
| 			i++ |  | ||||||
| 		case "push", "u", "ush": |  | ||||||
| 			config.Command = "push" |  | ||||||
| 			i++ |  | ||||||
| 		case "ls", "list": |  | ||||||
| 			config.Command = "list" |  | ||||||
| 			i++ |  | ||||||
| 		case "new": |  | ||||||
| 			config.Command = "new" |  | ||||||
| 			i++ |  | ||||||
| 		case "deps", "check": |  | ||||||
| 			config.Command = "deps" |  | ||||||
| 			i++ |  | ||||||
| 		default: |  | ||||||
| 			return nil, fmt.Errorf("unknown command: %s", arg) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Parse remaining arguments based on command |  | ||||||
| 	for i < len(args) { |  | ||||||
| 		arg := args[i] |  | ||||||
| 		switch arg { |  | ||||||
| 		case "--dry": |  | ||||||
| 			config.DryRun = true |  | ||||||
| 		case "--verbose": |  | ||||||
| 			config.Verbose = true |  | ||||||
| 		default: |  | ||||||
| 			if strings.HasPrefix(arg, "-") { |  | ||||||
| 				return nil, fmt.Errorf("unknown option: %s", arg) |  | ||||||
| 			} |  | ||||||
| 			config.Filters = append(config.Filters, arg) |  | ||||||
| 		} |  | ||||||
| 		i++ |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return config, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Confirm execution of all scripts |  | ||||||
| func confirmExecution(scripts []Script) bool { | func confirmExecution(scripts []Script) bool { | ||||||
| 	fmt.Printf(Red + "⚠️  About to run ALL scripts:" + NC + "\n") | 	fmt.Printf(Red + "⚠️  About to run ALL scripts:" + NC + "\n") | ||||||
| 	for _, script := range scripts { | 	for _, script := range scripts { | ||||||
|  | @ -522,73 +313,19 @@ func confirmExecution(scripts []Script) bool { | ||||||
| 	return answer == "y" || answer == "yes" | 	return answer == "y" || answer == "yes" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func main() { | func initConfig() { | ||||||
| 	const programName = "dev" | 	wd, _ := os.Getwd() | ||||||
|  | 	config.RunsDir = filepath.Join(wd, "runs") | ||||||
|  | 	config.LogsDir = filepath.Join(wd, "logs") | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 	// Parse arguments | func ensureRunsDir() error { | ||||||
| 	config, err := parseArgs(os.Args[1:]) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if err.Error() == "help" { |  | ||||||
| 			showHelp(programName) |  | ||||||
| 			os.Exit(0) |  | ||||||
| 		} |  | ||||||
| 		errorLog("Error: %v", err) |  | ||||||
| 		fmt.Println() |  | ||||||
| 		showHelp(programName) |  | ||||||
| 		os.Exit(1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Handle special commands |  | ||||||
| 	switch config.Command { |  | ||||||
| 	case "push": |  | ||||||
| 		if err := handlePush(); err != nil { |  | ||||||
| 			os.Exit(1) |  | ||||||
| 		} |  | ||||||
| 		os.Exit(0) |  | ||||||
| 	case "deps": |  | ||||||
| 		if err := checkDependencies(); err != nil { |  | ||||||
| 			os.Exit(1) |  | ||||||
| 		} |  | ||||||
| 		os.Exit(0) |  | ||||||
| 	case "list": |  | ||||||
| 		if err := listScripts(config.RunsDir); err != nil { |  | ||||||
| 			errorLog("Error listing scripts: %v", err) |  | ||||||
| 			os.Exit(1) |  | ||||||
| 		} |  | ||||||
| 		os.Exit(0) |  | ||||||
| 	case "new": |  | ||||||
| 		if len(config.Filters) == 0 { |  | ||||||
| 			errorLog("Script name required for 'new' command") |  | ||||||
| 			fmt.Printf("Usage: %s new <script-name>\n", programName) |  | ||||||
| 			os.Exit(1) |  | ||||||
| 		} |  | ||||||
| 		scriptName := config.Filters[0] |  | ||||||
| 		if err := createNewScript(config.RunsDir, scriptName); err != nil { |  | ||||||
| 			errorLog("Error creating script: %v", err) |  | ||||||
| 			os.Exit(1) |  | ||||||
| 		} |  | ||||||
| 		os.Exit(0) |  | ||||||
| 	case "run": |  | ||||||
| 		// Continue with script execution logic below |  | ||||||
| 	case "": |  | ||||||
| 		// No command provided, show help |  | ||||||
| 		showHelp(programName) |  | ||||||
| 		os.Exit(0) |  | ||||||
| 	default: |  | ||||||
| 		errorLog("Unknown command: %s", config.Command) |  | ||||||
| 		showHelp(programName) |  | ||||||
| 		os.Exit(1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Initialize runs directory if it doesn't exist (only for run command) |  | ||||||
| 	if _, err := os.Stat(config.RunsDir); os.IsNotExist(err) { | 	if _, err := os.Stat(config.RunsDir); os.IsNotExist(err) { | ||||||
| 		log("Creating runs directory at: %s", config.RunsDir) | 		log("Creating runs directory at: %s", config.RunsDir) | ||||||
| 		if err := os.MkdirAll(config.RunsDir, 0o755); err != nil { | 		if err := os.MkdirAll(config.RunsDir, 0o755); err != nil { | ||||||
| 			errorLog("Failed to create runs directory: %v", err) | 			return err | ||||||
| 			os.Exit(1) |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Create example script |  | ||||||
| 		exampleScript := filepath.Join(config.RunsDir, "example.sh") | 		exampleScript := filepath.Join(config.RunsDir, "example.sh") | ||||||
| 		content := `#!/usr/bin/env bash | 		content := `#!/usr/bin/env bash | ||||||
| # NAME: Example script | # NAME: Example script | ||||||
|  | @ -596,49 +333,58 @@ echo "Hello from runs/example.sh!" | ||||||
| echo "Edit this script or add your own to runs/" | echo "Edit this script or add your own to runs/" | ||||||
| ` | ` | ||||||
| 		if err := os.WriteFile(exampleScript, []byte(content), 0o755); err != nil { | 		if err := os.WriteFile(exampleScript, []byte(content), 0o755); err != nil { | ||||||
| 			errorLog("Failed to create example script: %v", err) | 			return err | ||||||
| 			os.Exit(1) |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		log("✅ Created runs/ directory with example script") | 		log("✅ Created runs/ directory with example script") | ||||||
| 		log("Use '%s ls' to list scripts or '%s new <name>' to create a new one", programName, programName) | 		log("Use 'dev ls' to list scripts or 'dev new <name>' to create a new one") | ||||||
| 		os.Exit(0) | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var rootCmd = &cobra.Command{ | ||||||
|  | 	Use:   "dev", | ||||||
|  | 	Short: "Development script runner", | ||||||
|  | 	Long:  "A simple tool to manage and run development scripts with security scanning", | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var runCmd = &cobra.Command{ | ||||||
|  | 	Use:   "run [filters...]", | ||||||
|  | 	Short: "Run scripts matching filters (or all if no filters)", | ||||||
|  | 	RunE: func(cmd *cobra.Command, args []string) error { | ||||||
|  | 		if err := ensureRunsDir(); err != nil { | ||||||
|  | 			return err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	// Create logs directory |  | ||||||
| 		if err := os.MkdirAll(config.LogsDir, 0o755); err != nil { | 		if err := os.MkdirAll(config.LogsDir, 0o755); err != nil { | ||||||
| 		errorLog("Failed to create logs directory: %v", err) | 			return err | ||||||
| 		os.Exit(1) |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	// Find scripts to run | 		scripts, err := findScripts(args) | ||||||
| 	scripts, err := findScripts(config.RunsDir, config.Filters) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 		errorLog("Error finding scripts: %v", err) | 			return err | ||||||
| 		os.Exit(1) |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if len(scripts) == 0 { | 		if len(scripts) == 0 { | ||||||
| 		if len(config.Filters) > 0 { | 			if len(args) > 0 { | ||||||
| 			warn("No scripts match the given filters: %s", strings.Join(config.Filters, ", ")) | 				warn("No scripts match the given filters: %s", strings.Join(args, ", ")) | ||||||
| 			fmt.Printf("Use '%s ls' to see all available scripts\n", programName) | 				fmt.Println("Use 'dev ls' to see all available scripts") | ||||||
| 			} else { | 			} else { | ||||||
| 				warn("No executable scripts found in %s", config.RunsDir) | 				warn("No executable scripts found in %s", config.RunsDir) | ||||||
| 			fmt.Printf("Use '%s new <name>' to create a new script\n", programName) | 				fmt.Println("Use 'dev new <name>' to create a new script") | ||||||
| 			} | 			} | ||||||
| 		os.Exit(0) | 			return nil | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	// Confirm execution if running all scripts without dry run | 		if len(args) == 0 && !config.DryRun { | ||||||
| 	if len(config.Filters) == 0 && !config.DryRun { |  | ||||||
| 			if !confirmExecution(scripts) { | 			if !confirmExecution(scripts) { | ||||||
| 				fmt.Println("Cancelled") | 				fmt.Println("Cancelled") | ||||||
| 			os.Exit(0) | 				return nil | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	// Execute scripts | 		var executed, failed []string | ||||||
| 	result := ExecutionResult{} |  | ||||||
| 
 | 
 | ||||||
| 		for _, script := range scripts { | 		for _, script := range scripts { | ||||||
| 			if config.DryRun { | 			if config.DryRun { | ||||||
|  | @ -651,27 +397,126 @@ echo "Edit this script or add your own to runs/" | ||||||
| 				fmt.Printf("  %s\n", script.Description) | 				fmt.Printf("  %s\n", script.Description) | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 		if err := executeScript(script, config.LogsDir, config.Verbose); err != nil { | 			if err := executeScript(script, config.Verbose); err != nil { | ||||||
| 				errorLog("❌ %s failed (see %s)", script.Name, | 				errorLog("❌ %s failed (see %s)", script.Name, | ||||||
| 					filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log")) | 					filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log")) | ||||||
| 			result.Failed = append(result.Failed, script.Name) | 				failed = append(failed, script.Name) | ||||||
| 			} else { | 			} else { | ||||||
| 				log("✅ %s completed", script.Name) | 				log("✅ %s completed", script.Name) | ||||||
| 			result.Executed = append(result.Executed, script.Name) | 				executed = append(executed, script.Name) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	// Print summary |  | ||||||
| 		if !config.DryRun { | 		if !config.DryRun { | ||||||
| 			fmt.Printf("\n" + Blue + "═══ SUMMARY ═══" + NC + "\n") | 			fmt.Printf("\n" + Blue + "═══ SUMMARY ═══" + NC + "\n") | ||||||
| 		fmt.Printf("✅ Completed: %d\n", len(result.Executed)) | 			fmt.Printf("✅ Completed: %d\n", len(executed)) | ||||||
| 		if len(result.Failed) > 0 { | 			if len(failed) > 0 { | ||||||
| 			fmt.Printf("❌ Failed: %d\n", len(result.Failed)) | 				fmt.Printf("❌ Failed: %d\n", len(failed)) | ||||||
| 				fmt.Printf("\n" + Red + "Failed scripts:" + NC + "\n") | 				fmt.Printf("\n" + Red + "Failed scripts:" + NC + "\n") | ||||||
| 			for _, failed := range result.Failed { | 				for _, f := range failed { | ||||||
| 				fmt.Printf("  • %s\n", failed) | 					fmt.Printf("  • %s\n", f) | ||||||
| 				} | 				} | ||||||
|  | 				return fmt.Errorf("some scripts failed") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var pushCmd = &cobra.Command{ | ||||||
|  | 	Use:     "push", | ||||||
|  | 	Aliases: []string{"u", "ush"}, | ||||||
|  | 	Short:   "Commit, scan for secrets, and push to git origin", | ||||||
|  | 	RunE: func(cmd *cobra.Command, args []string) error { | ||||||
|  | 		return handlePush() | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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 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() { | ||||||
|  | 	initConfig() | ||||||
|  | 
 | ||||||
|  | 	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") | ||||||
|  | 
 | ||||||
|  | 	rootCmd.AddCommand(runCmd, pushCmd, listCmd, newCmd, depsCmd) | ||||||
|  | 
 | ||||||
|  | 	if err := rootCmd.Execute(); err != nil { | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue