summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorHimanshu Sardana <himanshusardana2005@gmail.com>2026-05-03 19:23:28 +0000
committerHimanshu Sardana <himanshusardana2005@gmail.com>2026-05-03 19:23:28 +0000
commit60b58933534ea958117e7fecda5fa83ef9063b32 (patch)
tree7945bb5fa01464249ff9f2559fa5027cfcf86da8 /cmd
parent05bafcf95f2e319339ab431e26f855894580bf8c (diff)
feat: add file watcher
Diffstat (limited to 'cmd')
-rw-r--r--cmd/serve.go108
1 files changed, 103 insertions, 5 deletions
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 {