added non-zero exit status support for returned command errors

This commit is contained in:
Gani Georgiev committed 2026-08-01 15:05:40 +03:00
1 parent fd25a8ae32
commit 200c19b26f
2 files changed
+25 -36

No files matched your search

+4 -1
View File
@@ -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)). - Fixed "API preview" examples ([#7782](https://github.com/pocketbase/pocketbase/issues/7782)).
+21 -35
View File
@@ -1,6 +1,7 @@
package pocketbase package pocketbase
import ( import (
"errors"
"io" "io"
"os" "os"
"os/signal" "os/signal"
@@ -9,7 +10,6 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/fatih/color"
"github.com/pocketbase/pocketbase/cmd" "github.com/pocketbase/pocketbase/cmd"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/hook" "github.com/pocketbase/pocketbase/tools/hook"
@@ -117,8 +117,9 @@ func NewWithConfig(config Config) *PocketBase {
hideStartBanner: config.HideStartBanner, hideStartBanner: config.HideStartBanner,
} }
// replace with a colored stderr writer // don't write command errors to the stderr because the error is
pb.RootCmd.SetErr(newErrWriter()) // propagated back to the app.Start() and could result in duplication
pb.RootCmd.SetErr(&nopWrite{})
// parse base flags // parse base flags
// (errors are ignored, since the full flags parsing happens on Execute()) // (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 // listen for interrupt signal to gracefully shutdown the application
go func() { signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
sigch := make(chan os.Signal, 1)
signal.Notify(sigch, os.Interrupt, syscall.SIGTERM)
<-sigch
done <- true
}()
// execute the root command // execute the root command
go func() { go func() {
// note: leave to the commands to decide whether to print their error execCh <- routine.SafeWrap(pb.RootCmd.Execute)()
pb.RootCmd.Execute()
done <- true
}() }()
<-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 // trigger cleanups
// //
@@ -210,7 +210,7 @@ func (pb *PocketBase) Execute() error {
event := new(core.TerminateEvent) event := new(core.TerminateEvent)
event.App = pb event.App = pb
return pb.OnTerminate().Trigger(event, func(e *core.TerminateEvent) error { 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 return
} }
// newErrWriter returns a red colored stderr writter. var _ io.Writer = (*nopWrite)(nil)
func newErrWriter() *coloredWriter {
return &coloredWriter{
w: os.Stderr,
c: color.New(color.FgRed),
}
}
// coloredWriter is a small wrapper struct to construct a [color.Color] writter. type nopWrite struct{}
type coloredWriter struct {
w io.Writer
c *color.Color
}
// Write writes the p bytes using the colored writer. func (w *nopWrite) Write(p []byte) (n int, err error) {
func (colored *coloredWriter) Write(p []byte) (n int, err error) { return
colored.c.SetWriter(colored.w)
defer colored.c.UnsetWriter(colored.w)
return colored.c.Print(string(p))
} }