From 200c19b26fe20d1278ddaafc3e69ba94083cd21f Mon Sep 17 00:00:00 2001 From: Gani Georgiev Date: Sat, 1 Aug 2026 15:05:40 +0300 Subject: [PATCH] added non-zero exit status support for returned command errors --- CHANGELOG.md | 5 ++++- pocketbase.go | 56 +++++++++++++++++++-------------------------------- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19ca9e00..b928bcf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ -## v0.39.11 (WIP) +## v0.40.0 (WIP) + +- Propagate console command errors and recovered panics to `app.Start()` so that the program can exit with non-zero code while still ensuring that `app.OnTerminate` hook (responsible for the app graceful shutdown handling) was triggered. + _⚠️ Note that this could be a slight breaking change in case you are chaining PocketBase commands and relied on the previous `0` exit status for `Command.RunE` returned errors._ - Fixed "API preview" examples ([#7782](https://github.com/pocketbase/pocketbase/issues/7782)). diff --git a/pocketbase.go b/pocketbase.go index 91b92c81..509364c3 100644 --- a/pocketbase.go +++ b/pocketbase.go @@ -1,6 +1,7 @@ package pocketbase import ( + "errors" "io" "os" "os/signal" @@ -9,7 +10,6 @@ import ( "syscall" "time" - "github.com/fatih/color" "github.com/pocketbase/pocketbase/cmd" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/tools/hook" @@ -117,8 +117,9 @@ func NewWithConfig(config Config) *PocketBase { hideStartBanner: config.HideStartBanner, } - // replace with a colored stderr writer - pb.RootCmd.SetErr(newErrWriter()) + // don't write command errors to the stderr because the error is + // propagated back to the app.Start() and could result in duplication + pb.RootCmd.SetErr(&nopWrite{}) // parse base flags // (errors are ignored, since the full flags parsing happens on Execute()) @@ -183,26 +184,25 @@ func (pb *PocketBase) Execute() error { } } - done := make(chan bool, 1) + execCh := make(chan error, 1) + sigCh := make(chan os.Signal, 1) // listen for interrupt signal to gracefully shutdown the application - go func() { - sigch := make(chan os.Signal, 1) - signal.Notify(sigch, os.Interrupt, syscall.SIGTERM) - <-sigch - - done <- true - }() + signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) // execute the root command go func() { - // note: leave to the commands to decide whether to print their error - pb.RootCmd.Execute() - - done <- true + execCh <- routine.SafeWrap(pb.RootCmd.Execute)() }() - <-done + // wait for either an OS signal or the command to complete + var execErr error + select { + case <-sigCh: + case execErr = <-execCh: + } + + signal.Stop(sigCh) // trigger cleanups // @@ -210,7 +210,7 @@ func (pb *PocketBase) Execute() error { event := new(core.TerminateEvent) event.App = pb return pb.OnTerminate().Trigger(event, func(e *core.TerminateEvent) error { - return e.App.ResetBootstrapState() + return errors.Join(e.App.ResetBootstrapState(), execErr) }) } @@ -308,24 +308,10 @@ func inspectRuntime() (baseDir string, withGoRun bool) { return } -// newErrWriter returns a red colored stderr writter. -func newErrWriter() *coloredWriter { - return &coloredWriter{ - w: os.Stderr, - c: color.New(color.FgRed), - } -} +var _ io.Writer = (*nopWrite)(nil) -// coloredWriter is a small wrapper struct to construct a [color.Color] writter. -type coloredWriter struct { - w io.Writer - c *color.Color -} +type nopWrite struct{} -// Write writes the p bytes using the colored writer. -func (colored *coloredWriter) Write(p []byte) (n int, err error) { - colored.c.SetWriter(colored.w) - defer colored.c.UnsetWriter(colored.w) - - return colored.c.Print(string(p)) +func (w *nopWrite) Write(p []byte) (n int, err error) { + return }