125 lines
2.4 KiB
Go
125 lines
2.4 KiB
Go
package main
|
|
|
|
import (
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
func runExpress(changes <-chan FileChange) {
|
|
var currentProcess *exec.Cmd
|
|
var mu sync.Mutex
|
|
|
|
// Helper to start the express process
|
|
startExpress := func() *exec.Cmd {
|
|
cmd := exec.Command("../express/run.sh")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
log.Printf("[express] Failed to start: %v", err)
|
|
return nil
|
|
}
|
|
|
|
log.Printf("[express] Started (pid %d)", cmd.Process.Pid)
|
|
|
|
// Monitor the process in background
|
|
go func() {
|
|
err := cmd.Wait()
|
|
if err != nil {
|
|
log.Printf("[express] Process exited: %v", err)
|
|
} else {
|
|
log.Printf("[express] Process exited normally")
|
|
}
|
|
}()
|
|
|
|
return cmd
|
|
}
|
|
|
|
// Helper to stop the express process
|
|
stopExpress := func(cmd *exec.Cmd) {
|
|
if cmd == nil || cmd.Process == nil {
|
|
return
|
|
}
|
|
|
|
log.Printf("[express] Stopping (pid %d)", cmd.Process.Pid)
|
|
cmd.Process.Signal(syscall.SIGTERM)
|
|
|
|
// Wait briefly for graceful shutdown
|
|
done := make(chan struct{})
|
|
go func() {
|
|
cmd.Wait()
|
|
close(done)
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
log.Printf("[express] Stopped gracefully")
|
|
case <-time.After(5 * time.Second):
|
|
log.Printf("[express] Force killing")
|
|
cmd.Process.Kill()
|
|
}
|
|
}
|
|
|
|
// Helper to run the build
|
|
runBuild := func() bool {
|
|
log.Printf("[build] Starting ncc build...")
|
|
|
|
cmd := exec.Command("../express/build.sh")
|
|
|
|
stdout, _ := cmd.StdoutPipe()
|
|
stderr, _ := cmd.StderrPipe()
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
log.Printf("[build] Failed to start: %v", err)
|
|
return false
|
|
}
|
|
|
|
// Copy output
|
|
go io.Copy(os.Stdout, stdout)
|
|
go io.Copy(os.Stderr, stderr)
|
|
|
|
err := cmd.Wait()
|
|
if err != nil {
|
|
log.Printf("[build] Failed: %v", err)
|
|
return false
|
|
}
|
|
|
|
log.Printf("[build] Success")
|
|
return true
|
|
}
|
|
|
|
// Debounce timer
|
|
var debounceTimer *time.Timer
|
|
const debounceDelay = 100 * time.Millisecond
|
|
|
|
for change := range changes {
|
|
log.Printf("[watch] %s: %s", change.Operation, change.Path)
|
|
|
|
// Reset debounce timer
|
|
if debounceTimer != nil {
|
|
debounceTimer.Stop()
|
|
}
|
|
|
|
debounceTimer = time.AfterFunc(debounceDelay, func() {
|
|
if !runBuild() {
|
|
log.Printf("[master] Build failed, keeping current process")
|
|
return
|
|
}
|
|
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
// Stop old process
|
|
stopExpress(currentProcess)
|
|
|
|
// Start new process
|
|
currentProcess = startExpress()
|
|
})
|
|
}
|
|
}
|