From 60b58933534ea958117e7fecda5fa83ef9063b32 Mon Sep 17 00:00:00 2001 From: Himanshu Sardana Date: Sun, 3 May 2026 19:23:28 +0000 Subject: feat: add file watcher --- cmd/serve.go | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 103 insertions(+), 5 deletions(-) (limited to 'cmd') diff --git a/cmd/serve.go b/cmd/serve.go index d53e632..634fcb6 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -7,8 +7,11 @@ import ( "net/http" "os" "path/filepath" + "time" + "github.com/HimanshuSardana/kite/internal/build" "github.com/HimanshuSardana/kite/pkg/config" + "github.com/fsnotify/fsnotify" ) func runServe(args []string) { @@ -28,6 +31,25 @@ func runServe(args []string) { } } + // Initial build + buildSite(themeName) + + // Start file watcher for hot-reload + go watchAndRebuild(themeName) + + fs := http.FileServer(http.Dir("./output/")) + http.Handle("/", fs) + + log.Printf("Serving on http://localhost:%s (with hot-reload)", port) + + if err := http.ListenAndServe(":"+port, nil); err != nil { + log.Fatalf("Server error: %s\n", err) + } +} + +func buildSite(themeName string) { + log.Println("Building site...") + themeCSS := fmt.Sprintf("./themes/%s/style.css", themeName) outputCSS := "./output/style.css" @@ -35,16 +57,92 @@ func runServe(args []string) { log.Printf("Warning: Could not copy theme CSS: %v", err) } - fs := http.FileServer(http.Dir("./output/")) - http.Handle("/", fs) + opts := build.BuildOptions{ + ThemeName: themeName, + } - log.Printf("Serving on http://localhost:%s", port) + if err := build.Build(opts); err != nil { + log.Printf("Build error: %v", err) + } else { + log.Println("Build completed successfully!") + } +} - if err := http.ListenAndServe(":"+port, nil); err != nil { - log.Fatalf("Server error: %s\n", err) +func watchAndRebuild(themeName string) { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Printf("Failed to create watcher: %v", err) + return + } + defer watcher.Close() + + contentDir := "./content" + if err := addDirRecursive(watcher, contentDir); err != nil { + log.Printf("Failed to watch content directory: %v", err) + return + } + + // Also watch themes directory for theme changes + themesDir := "./themes" + if err := addDirRecursive(watcher, themesDir); err != nil { + log.Printf("Warning: Could not watch themes directory: %v", err) + } + + if err := watcher.Add("./config.yaml"); err != nil { + log.Printf("Warning: Could not watch config.yaml: %v", err) + } + + log.Println("Watching for changes in content/, themes/, and config.yaml...") + + var debounceTimer *time.Timer + + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + // Only trigger on write or create events + if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create { + log.Printf("Detected change: %s", event.Name) + + // If a new directory is created, watch it + if event.Op&fsnotify.Create == fsnotify.Create { + if info, err := os.Stat(event.Name); err == nil && info.IsDir() { + addDirRecursive(watcher, event.Name) + } + } + + // Debounce: wait 500ms before rebuilding + if debounceTimer != nil { + debounceTimer.Stop() + } + debounceTimer = time.AfterFunc(500*time.Millisecond, func() { + buildSite(themeName) + }) + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Printf("Watcher error: %v", err) + } } } +func addDirRecursive(watcher *fsnotify.Watcher, dir string) error { + return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + log.Printf("Watching directory: %s", path) + return watcher.Add(path) + } + return nil + }) +} + func copyFile(src, dst string) error { in, err := os.Open(src) if err != nil { -- cgit v1.3.1