diff --git a/esr b/esr index 9d207a4..161fd7d 100755 Binary files a/esr and b/esr differ diff --git a/internal/esr/builder.go b/internal/esr/builder.go index c8570e3..8047f38 100644 --- a/internal/esr/builder.go +++ b/internal/esr/builder.go @@ -33,40 +33,25 @@ func (b *Builder) Build(buildOptions *api.BuildOptions) error { b.BuiltCSS = []string{} for _, file := range result.OutputFiles { + dir := filepath.Dir(file.Path) + if _, err := os.Stat(dir); os.IsNotExist(err) { + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return fmt.Errorf("esr :: failed to create directory: %v", err) + } + } + + if err := os.WriteFile(file.Path, file.Contents, os.ModePerm); err != nil { + return fmt.Errorf("esr :: failed to write file: %v", err) + } + if filepath.Ext(file.Path) != ".map" { - dir := filepath.Dir(file.Path) - if _, err := os.Stat(dir); os.IsNotExist(err) { - if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return fmt.Errorf("esr :: failed to create directory: %v", err) - } - } - - if err := os.WriteFile(file.Path, file.Contents, os.ModePerm); err != nil { - return fmt.Errorf("esr :: failed to write file: %v", err) - } - - // fmt.Printf("esr :: wrote: %s\n", filepath.Join(b.Config.Outdir, filepath.Base(file.Path))) - fmt.Printf("esr :: wrote: %s\n", filepath.Join(filepath.Dir(file.Path), filepath.Base(file.Path))) - // fmt.Printf("esr :: wrote: %s\n", file.Path) - - if filepath.Ext(file.Path) == ".js" { + if filepath.Ext(file.Path) == ".js" || filepath.Ext(file.Path) == ".mjs" { b.BuiltJS = append(b.BuiltJS, file.Path) } if filepath.Ext(file.Path) == ".css" { b.BuiltCSS = append(b.BuiltCSS, file.Path) } - } else { - dir := filepath.Dir(file.Path) - if _, err := os.Stat(dir); os.IsNotExist(err) { - if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return fmt.Errorf("esr :: failed to create directory: %v", err) - } - } - - if err := os.WriteFile(file.Path, file.Contents, os.ModePerm); err != nil { - return fmt.Errorf("esr :: failed to write file: %v", err) - } } } diff --git a/internal/esr/esr.go b/internal/esr/esr.go index 9e5f3c4..df8a29b 100644 --- a/internal/esr/esr.go +++ b/internal/esr/esr.go @@ -3,6 +3,9 @@ package esr import ( "esr/internal/config" "fmt" + "os" + "os/signal" + "syscall" "github.com/evanw/esbuild/pkg/api" ) @@ -47,15 +50,57 @@ func ExecuteTask(esr *Esr, build, serve, run, watch bool) { } if run { - esr.Runner.Run(entryPoint) - } - - if watch && !(serve || run) { + if watch { + // Special case for --run --watch: we start watching and re-run on changes + opts := ModeOptions(esr.Config, ModeRunner, entryPoint, esr.Runner.TempFilePath) + if err := esr.Builder.Build(opts); err != nil { + fmt.Println("Build error:", err) + return + } + + // Enter alt screen for watch mode + EnterAltScreen() + ClearScreen() + + // Run once initially + esr.Runner.Run(entryPoint) + + // Then set up watching + err := esr.Builder.StartWatch(opts, func() { + ClearScreen() + esr.Runner.Run(entryPoint) + }) + + if err != nil { + fmt.Println("Watch error:", err) + ExitAltScreen() + return + } + + // Block main thread with a signal handler to keep the process alive + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + <-sigCh + + // Ensure we exit the alternate buffer when shutting down + ExitAltScreen() + } else { + // Normal run case (no watch) - don't use alt screen + esr.Runner.Run(entryPoint) + } + } else if watch && !serve { + // Watch-only mode (not combined with serve or run) opts := ModeOptions(esr.Config, ModeServer, entryPoint, "") - err := esr.Builder.StartWatch(opts, esr.generalCallback(run, entryPoint)) + err := esr.Builder.StartWatch(opts, esr.generalCallback(false, entryPoint)) if err != nil { fmt.Println("Watch error:", err) + return } + + // Block main thread with a signal handler to keep the process alive + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + <-sigCh } } diff --git a/internal/esr/runner.go b/internal/esr/runner.go index edb3b9d..9174cde 100644 --- a/internal/esr/runner.go +++ b/internal/esr/runner.go @@ -40,21 +40,35 @@ func newTempFilePath(entry string) string { func (r *Runner) Run(entryPoint string) { opts := ModeOptions(r.Config, ModeRunner, entryPoint, r.TempFilePath) opts.Outfile = r.TempFilePath + + defer func() { + filesToCleanup := []string{r.TempFilePath} + + if r.Config.Run.Sourcemap { + filesToCleanup = append(filesToCleanup, r.TempFilePath+".map") + } + + for _, file := range filesToCleanup { + if err := os.Remove(file); err != nil { + if !os.IsNotExist(err) { + // Silently ignore cleanup errors + } + } + } + }() if err := r.Builder.Build(opts); err != nil { fmt.Println("Build error:", err) return } - - fmt.Printf("esr :: running: \"%s %s\"\n", r.Config.Run.Runtime, r.TempFilePath) - + cmd := exec.Command(r.Config.Run.Runtime, r.TempFilePath) cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout if err := cmd.Start(); err != nil { fmt.Println("Error starting process:", err) - os.Exit(1) + return } var wg sync.WaitGroup @@ -62,20 +76,10 @@ func (r *Runner) Run(entryPoint string) { go func() { defer wg.Done() - if err := cmd.Wait(); err != nil { - fmt.Printf("Process finished with error: %v\n", err) - } else { - fmt.Println("Process finished successfully") - } + cmd.Wait() }() wg.Wait() - - // Finally, remove the temp file we created - - if err := os.Remove(r.TempFilePath); err != nil { - fmt.Printf("Failed to remove temp file: %v\n", err) - } } func (r *Runner) Stop(cmd *exec.Cmd) error { diff --git a/internal/esr/utils.go b/internal/esr/utils.go index 053ad50..3409437 100644 --- a/internal/esr/utils.go +++ b/internal/esr/utils.go @@ -15,3 +15,15 @@ func ShowHelpMessage() { fmt.Println(`Usage: esr [init] [--config ] [--serve] [--build] [--watch] `) } + +func EnterAltScreen() { + fmt.Print("\033[?1049h") +} + +func ExitAltScreen() { + fmt.Print("\033[?1049l") +} + +func ClearScreen() { + fmt.Print("\033[H\033[2J") +}