-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexecutor.go
More file actions
111 lines (93 loc) · 3.3 KB
/
executor.go
File metadata and controls
111 lines (93 loc) · 3.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package devflow
import (
"fmt"
"os/exec"
"runtime"
"strings"
"time"
)
// ExecCommand is a variable to allow mocking in tests
var ExecCommand = exec.Command
// RunCommand executes a shell command
// It returns the output (trimmed) and an error if the command fails
func RunCommand(name string, args ...string) (string, error) {
// Execute
cmd := ExecCommand(name, args...)
outputBytes, err := cmd.CombinedOutput()
output := strings.TrimSpace(string(outputBytes))
if err != nil {
cmdStr := name + " " + strings.Join(args, " ")
return output, fmt.Errorf("command failed: %s\nError: %w\nOutput: %s", cmdStr, err, output)
}
return output, nil
}
// RunCommandSilent executes a command (alias for RunCommand now, as RunCommand is also silent on success)
// kept for backward compatibility if needed, or we can remove it.
// The previous implementation was identical except for logging.
func RunCommandSilent(name string, args ...string) (string, error) {
return RunCommand(name, args...)
}
// RunShellCommand executes a shell command in a cross-platform way
// On Windows: uses cmd.exe /C
// On Unix (Linux/macOS): uses sh -c
func RunShellCommand(command string) (string, error) {
switch runtime.GOOS {
case "windows":
return RunCommand("cmd.exe", "/C", command)
default: // linux, darwin, etc.
return RunCommand("sh", "-c", command)
}
}
// RunShellCommandAsync starts a shell command asynchronously (non-blocking)
// Returns immediately after starting, does not wait for completion
func RunShellCommandAsync(command string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd.exe", "/C", command)
default: // linux, darwin, etc.
cmd = exec.Command("sh", "-c", command)
}
return cmd.Start()
}
// RunCommandWithStdin executes a command with the given string piped to stdin.
// Use this instead of passing secrets as CLI args to avoid leaking them in error messages.
func RunCommandWithStdin(input, name string, args ...string) (string, error) {
cmd := ExecCommand(name, args...)
cmd.Stdin = strings.NewReader(input)
outputBytes, err := cmd.CombinedOutput()
output := strings.TrimSpace(string(outputBytes))
if err != nil {
cmdStr := name + " " + strings.Join(args, " ")
return output, fmt.Errorf("command failed: %s\nError: %w\nOutput: %s", cmdStr, err, output)
}
return output, nil
}
// RunCommandInDir executes a command in a specific directory
func RunCommandInDir(dir, name string, args ...string) (string, error) {
cmd := ExecCommand(name, args...)
cmd.Dir = dir
outputBytes, err := cmd.CombinedOutput()
output := strings.TrimSpace(string(outputBytes))
if err != nil {
cmdStr := name + " " + strings.Join(args, " ")
return output, fmt.Errorf("command failed in %s: %s\nError: %w\nOutput: %s", dir, cmdStr, err, output)
}
return output, nil
}
// RunCommandWithRetryInDir executes a command in a specific directory with retries
func RunCommandWithRetryInDir(dir, name string, args []string, maxRetries int, delay time.Duration) (string, error) {
var output string
var err error
for i := 0; i < maxRetries; i++ {
output, err = RunCommandInDir(dir, name, args...)
if err == nil {
return output, nil
}
if i == maxRetries-1 {
break
}
time.Sleep(delay)
}
return output, fmt.Errorf("command %s failed in %s after %d attempts: %w", name, dir, maxRetries, err)
}