fix(fn-run): propagar stdout/stderr de bash functions library-style
Scripts bash del registry siguen dos patrones: - Con guarda BASH_SOURCE[0]==$0: se auto-invocan al ejecutar directamente - Library-style (sin guarda): definen una función <basename>() pero no la llaman al nivel top-level → bash <script> args produce silencio total El dispatcher en buildBashCommand detecta ahora tres casos: 1. Tiene guarda BASH_SOURCE[0]==$0 → ejecutar directamente (sin cambio) 2. Library-style con función <basename>() → source + llamada explícita: bash -c 'source "$1"; shift; fn_name "$@"' -- <script> [args...] 3. Pipeline top-level (sin función ni guarda) → ejecutar directamente También corrige scan_secrets_in_dirty.sh y git_hook_audit_app_drift.sh para aceptar worktrees git (donde .git es un archivo, no un directorio). Añade bashFunctionName() helper y 4 tests unitarios/integración. Fix reportado en issue 0077 con gradle_unit_test como caso canario. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+58
-1
@@ -194,10 +194,67 @@ func buildGoCommand(fn *registry.Function, registryRoot, absPath string, args []
|
||||
}
|
||||
|
||||
|
||||
// bashFunctionName returns the name of the top-level function defined in the
|
||||
// script that matches the file basename (e.g. "gradle_unit_test" for
|
||||
// "gradle_unit_test.sh"), or "" if no such function is found.
|
||||
//
|
||||
// Library-style bash scripts define a function `<basename>()` or
|
||||
// `function <basename>` at the top level but do not call it. When executed
|
||||
// directly with `bash <script> args...` the function is defined but never
|
||||
// invoked, so no output is produced. Detecting this pattern lets the
|
||||
// dispatcher source the script and call the function explicitly.
|
||||
func bashFunctionName(path, content string) string {
|
||||
base := strings.TrimSuffix(filepath.Base(path), ".sh")
|
||||
// Match "basename()" or "basename (){" at the start of a line.
|
||||
for _, line := range strings.Split(content, "\n") {
|
||||
trimmed := strings.TrimLeft(line, " \t")
|
||||
if strings.HasPrefix(trimmed, base+"()") ||
|
||||
strings.HasPrefix(trimmed, base+" ()") {
|
||||
return base
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func buildBashCommand(absPath string, args []string) (*exec.Cmd, error) {
|
||||
dir := filepath.Dir(absPath)
|
||||
|
||||
content := ""
|
||||
if data, err := os.ReadFile(absPath); err == nil {
|
||||
content = string(data)
|
||||
}
|
||||
|
||||
// Case 1: script has the self-executing guard — run directly.
|
||||
// The guard pattern is: if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
// (many scripts also use BASH_SOURCE[0] only for SCRIPT_DIR — we must
|
||||
// match the == "$0" comparison specifically to avoid false positives).
|
||||
hasSelfGuard := strings.Contains(content, `BASH_SOURCE[0]}" == "$0"`) ||
|
||||
strings.Contains(content, `BASH_SOURCE[0]}" = "$0"`)
|
||||
if hasSelfGuard {
|
||||
cmdArgs := append([]string{absPath}, args...)
|
||||
cmd := exec.Command("bash", cmdArgs...)
|
||||
cmd.Dir = dir
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// Case 2: library-style script — defines a function `<basename>()` at
|
||||
// the top level but never calls it. Source the script and call the
|
||||
// function explicitly so stdout/stderr reach the caller.
|
||||
//
|
||||
// bash -c 'source "$1"; shift; fn_name "$@"' -- <script> [args...]
|
||||
if fnName := bashFunctionName(absPath, content); fnName != "" {
|
||||
inline := fmt.Sprintf(`source "$1"; shift; %s "$@"`, fnName)
|
||||
cmdArgs := append([]string{"-c", inline, "--", absPath}, args...)
|
||||
cmd := exec.Command("bash", cmdArgs...)
|
||||
cmd.Dir = dir
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// Case 3: top-level pipeline script — executes code directly without a
|
||||
// wrapping function (e.g. propose_capability_groups.sh). Run as-is.
|
||||
cmdArgs := append([]string{absPath}, args...)
|
||||
cmd := exec.Command("bash", cmdArgs...)
|
||||
cmd.Dir = filepath.Dir(absPath)
|
||||
cmd.Dir = dir
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user