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 }