Files
ss/internal/corecfg/hooks_exec.go
2026-07-19 20:01:38 +08:00

122 lines
2.9 KiB
Go

package corecfg
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
)
// executeHooks runs every core_config_generated.* script found in the hooks
// directory, in sorted order. Direct port of _execute_hooks.
func (m *Manager) executeHooks(configFilePath string) {
hooksDir := filepath.Join(m.Storage.ConfigDir, "hooks")
if _, err := os.Stat(hooksDir); os.IsNotExist(err) {
return
}
entries, err := os.ReadDir(hooksDir)
if err != nil {
return
}
var hookFiles []string
for _, entry := range entries {
if entry.IsDir() {
continue
}
if strings.HasPrefix(entry.Name(), "core_config_generated.") {
hookFiles = append(hookFiles, filepath.Join(hooksDir, entry.Name()))
}
}
if len(hookFiles) == 0 {
return
}
fmt.Printf("🔧 Found %d hook(s) to execute\n", len(hookFiles))
sort.Strings(hookFiles)
for _, hookFile := range hookFiles {
executeHook(hookFile, configFilePath)
}
}
// executeHook runs a single hook script with the generated config file path
// as its argument. Direct port of _execute_hook, adapted for the fact that
// the Go binary has no bundled interpreter of its own (unlike Python's
// sys.executable) — .py hooks are run via a system "python3"/"python"
// found on PATH.
func executeHook(hookPath, configFilePath string) bool {
if _, err := os.Stat(hookPath); os.IsNotExist(err) {
return false
}
var cmdArgs []string
switch strings.ToLower(filepath.Ext(hookPath)) {
case ".py":
python := "python3"
if runtime.GOOS == "windows" {
python = "python"
}
if _, err := exec.LookPath(python); err != nil {
alt := "python"
if runtime.GOOS == "windows" {
alt = "python3"
}
if _, err := exec.LookPath(alt); err == nil {
python = alt
}
}
cmdArgs = []string{python, hookPath, configFilePath}
case ".js":
cmdArgs = []string{"node", hookPath, configFilePath}
case ".nu":
cmdArgs = []string{"nu", hookPath, configFilePath}
default:
if runtime.GOOS != "windows" {
os.Chmod(hookPath, 0o755)
}
cmdArgs = []string{hookPath, configFilePath}
}
fmt.Printf("🔧 Executing hook: %s\n", hookPath)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, cmdArgs[0], cmdArgs[1:]...)
cmd.Dir = filepath.Dir(hookPath)
cmd.Env = append(os.Environ(), "PYTHONIOENCODING=utf-8")
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if ctx.Err() == context.DeadlineExceeded {
fmt.Printf("⏰ Hook timed out: %s\n", filepath.Base(hookPath))
return false
}
if err == nil {
fmt.Printf("✅ Hook executed successfully: %s\n", filepath.Base(hookPath))
if out := strings.TrimSpace(stdout.String()); out != "" {
fmt.Printf(" Output: %s\n", out)
}
return true
}
fmt.Printf("❌ Hook failed: %s\n", filepath.Base(hookPath))
if errOut := strings.TrimSpace(stderr.String()); errOut != "" {
fmt.Printf(" Error: %s\n", errOut)
}
return false
}