Adding upstream version 0.28.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
88f1d47ab6
commit
e28c88ef14
933 changed files with 194711 additions and 0 deletions
328
pocketbase.go
Normal file
328
pocketbase.go
Normal file
|
@ -0,0 +1,328 @@
|
|||
package pocketbase
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/pocketbase/pocketbase/cmd"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/tools/hook"
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
"github.com/pocketbase/pocketbase/tools/routine"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
_ "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
var _ core.App = (*PocketBase)(nil)
|
||||
|
||||
// Version of PocketBase
|
||||
var Version = "(untracked)"
|
||||
|
||||
// PocketBase defines a PocketBase app launcher.
|
||||
//
|
||||
// It implements [core.App] via embedding and all of the app interface methods
|
||||
// could be accessed directly through the instance (eg. PocketBase.DataDir()).
|
||||
type PocketBase struct {
|
||||
core.App
|
||||
|
||||
devFlag bool
|
||||
dataDirFlag string
|
||||
encryptionEnvFlag string
|
||||
queryTimeout int
|
||||
hideStartBanner bool
|
||||
|
||||
// RootCmd is the main console command
|
||||
RootCmd *cobra.Command
|
||||
}
|
||||
|
||||
// Config is the PocketBase initialization config struct.
|
||||
type Config struct {
|
||||
// hide the default console server info on app startup
|
||||
HideStartBanner bool
|
||||
|
||||
// optional default values for the console flags
|
||||
DefaultDev bool
|
||||
DefaultDataDir string // if not set, it will fallback to "./pb_data"
|
||||
DefaultEncryptionEnv string
|
||||
DefaultQueryTimeout time.Duration // default to core.DefaultQueryTimeout (in seconds)
|
||||
|
||||
// optional DB configurations
|
||||
DataMaxOpenConns int // default to core.DefaultDataMaxOpenConns
|
||||
DataMaxIdleConns int // default to core.DefaultDataMaxIdleConns
|
||||
AuxMaxOpenConns int // default to core.DefaultAuxMaxOpenConns
|
||||
AuxMaxIdleConns int // default to core.DefaultAuxMaxIdleConns
|
||||
DBConnect core.DBConnectFunc // default to core.dbConnect
|
||||
}
|
||||
|
||||
// New creates a new PocketBase instance with the default configuration.
|
||||
// Use [NewWithConfig] if you want to provide a custom configuration.
|
||||
//
|
||||
// Note that the application will not be initialized/bootstrapped yet,
|
||||
// aka. DB connections, migrations, app settings, etc. will not be accessible.
|
||||
// Everything will be initialized when [PocketBase.Start] is executed.
|
||||
// If you want to initialize the application before calling [PocketBase.Start],
|
||||
// then you'll have to manually call [PocketBase.Bootstrap].
|
||||
func New() *PocketBase {
|
||||
_, isUsingGoRun := inspectRuntime()
|
||||
|
||||
return NewWithConfig(Config{
|
||||
DefaultDev: isUsingGoRun,
|
||||
})
|
||||
}
|
||||
|
||||
// NewWithConfig creates a new PocketBase instance with the provided config.
|
||||
//
|
||||
// Note that the application will not be initialized/bootstrapped yet,
|
||||
// aka. DB connections, migrations, app settings, etc. will not be accessible.
|
||||
// Everything will be initialized when [PocketBase.Start] is executed.
|
||||
// If you want to initialize the application before calling [PocketBase.Start],
|
||||
// then you'll have to manually call [PocketBase.Bootstrap].
|
||||
func NewWithConfig(config Config) *PocketBase {
|
||||
// initialize a default data directory based on the executable baseDir
|
||||
if config.DefaultDataDir == "" {
|
||||
baseDir, _ := inspectRuntime()
|
||||
config.DefaultDataDir = filepath.Join(baseDir, "pb_data")
|
||||
}
|
||||
|
||||
if config.DefaultQueryTimeout == 0 {
|
||||
config.DefaultQueryTimeout = core.DefaultQueryTimeout
|
||||
}
|
||||
|
||||
executableName := filepath.Base(os.Args[0])
|
||||
|
||||
pb := &PocketBase{
|
||||
RootCmd: &cobra.Command{
|
||||
Use: executableName,
|
||||
Short: executableName + " CLI",
|
||||
Version: Version,
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{
|
||||
UnknownFlags: true,
|
||||
},
|
||||
// no need to provide the default cobra completion command
|
||||
CompletionOptions: cobra.CompletionOptions{
|
||||
DisableDefaultCmd: true,
|
||||
},
|
||||
},
|
||||
devFlag: config.DefaultDev,
|
||||
dataDirFlag: config.DefaultDataDir,
|
||||
encryptionEnvFlag: config.DefaultEncryptionEnv,
|
||||
hideStartBanner: config.HideStartBanner,
|
||||
}
|
||||
|
||||
// replace with a colored stderr writer
|
||||
pb.RootCmd.SetErr(newErrWriter())
|
||||
|
||||
// parse base flags
|
||||
// (errors are ignored, since the full flags parsing happens on Execute())
|
||||
pb.eagerParseFlags(&config)
|
||||
|
||||
// initialize the app instance
|
||||
pb.App = core.NewBaseApp(core.BaseAppConfig{
|
||||
IsDev: pb.devFlag,
|
||||
DataDir: pb.dataDirFlag,
|
||||
EncryptionEnv: pb.encryptionEnvFlag,
|
||||
QueryTimeout: time.Duration(pb.queryTimeout) * time.Second,
|
||||
DataMaxOpenConns: config.DataMaxOpenConns,
|
||||
DataMaxIdleConns: config.DataMaxIdleConns,
|
||||
AuxMaxOpenConns: config.AuxMaxOpenConns,
|
||||
AuxMaxIdleConns: config.AuxMaxIdleConns,
|
||||
DBConnect: config.DBConnect,
|
||||
})
|
||||
|
||||
// hide the default help command (allow only `--help` flag)
|
||||
pb.RootCmd.SetHelpCommand(&cobra.Command{Hidden: true})
|
||||
|
||||
// https://github.com/pocketbase/pocketbase/issues/6136
|
||||
pb.OnBootstrap().Bind(&hook.Handler[*core.BootstrapEvent]{
|
||||
Id: ModerncDepsCheckHookId,
|
||||
Func: func(be *core.BootstrapEvent) error {
|
||||
if err := be.Next(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// run separately to avoid blocking
|
||||
app := be.App
|
||||
routine.FireAndForget(func() {
|
||||
checkModerncDeps(app)
|
||||
})
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return pb
|
||||
}
|
||||
|
||||
// Start starts the application, aka. registers the default system
|
||||
// commands (serve, superuser, version) and executes pb.RootCmd.
|
||||
func (pb *PocketBase) Start() error {
|
||||
// register system commands
|
||||
pb.RootCmd.AddCommand(cmd.NewSuperuserCommand(pb))
|
||||
pb.RootCmd.AddCommand(cmd.NewServeCommand(pb, !pb.hideStartBanner))
|
||||
|
||||
return pb.Execute()
|
||||
}
|
||||
|
||||
// Execute initializes the application (if not already) and executes
|
||||
// the pb.RootCmd with graceful shutdown support.
|
||||
//
|
||||
// This method differs from pb.Start() by not registering the default
|
||||
// system commands!
|
||||
func (pb *PocketBase) Execute() error {
|
||||
if !pb.skipBootstrap() {
|
||||
if err := pb.Bootstrap(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
done := make(chan bool, 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
|
||||
}()
|
||||
|
||||
// execute the root command
|
||||
go func() {
|
||||
// note: leave to the commands to decide whether to print their error
|
||||
pb.RootCmd.Execute()
|
||||
|
||||
done <- true
|
||||
}()
|
||||
|
||||
<-done
|
||||
|
||||
// trigger cleanups
|
||||
event := new(core.TerminateEvent)
|
||||
event.App = pb
|
||||
return pb.OnTerminate().Trigger(event, func(e *core.TerminateEvent) error {
|
||||
return e.App.ResetBootstrapState()
|
||||
})
|
||||
}
|
||||
|
||||
// eagerParseFlags parses the global app flags before calling pb.RootCmd.Execute().
|
||||
// so we can have all PocketBase flags ready for use on initialization.
|
||||
func (pb *PocketBase) eagerParseFlags(config *Config) error {
|
||||
pb.RootCmd.PersistentFlags().StringVar(
|
||||
&pb.dataDirFlag,
|
||||
"dir",
|
||||
config.DefaultDataDir,
|
||||
"the PocketBase data directory",
|
||||
)
|
||||
|
||||
pb.RootCmd.PersistentFlags().StringVar(
|
||||
&pb.encryptionEnvFlag,
|
||||
"encryptionEnv",
|
||||
config.DefaultEncryptionEnv,
|
||||
"the env variable whose value of 32 characters will be used \nas encryption key for the app settings (default none)",
|
||||
)
|
||||
|
||||
pb.RootCmd.PersistentFlags().BoolVar(
|
||||
&pb.devFlag,
|
||||
"dev",
|
||||
config.DefaultDev,
|
||||
"enable dev mode, aka. printing logs and sql statements to the console",
|
||||
)
|
||||
|
||||
pb.RootCmd.PersistentFlags().IntVar(
|
||||
&pb.queryTimeout,
|
||||
"queryTimeout",
|
||||
int(config.DefaultQueryTimeout.Seconds()),
|
||||
"the default SELECT queries timeout in seconds",
|
||||
)
|
||||
|
||||
return pb.RootCmd.ParseFlags(os.Args[1:])
|
||||
}
|
||||
|
||||
// skipBootstrap eagerly checks if the app should skip the bootstrap process:
|
||||
// - already bootstrapped
|
||||
// - is unknown command
|
||||
// - is the default help command
|
||||
// - is the default version command
|
||||
//
|
||||
// https://github.com/pocketbase/pocketbase/issues/404
|
||||
// https://github.com/pocketbase/pocketbase/discussions/1267
|
||||
func (pb *PocketBase) skipBootstrap() bool {
|
||||
flags := []string{
|
||||
"-h",
|
||||
"--help",
|
||||
"-v",
|
||||
"--version",
|
||||
}
|
||||
|
||||
if pb.IsBootstrapped() {
|
||||
return true // already bootstrapped
|
||||
}
|
||||
|
||||
cmd, _, err := pb.RootCmd.Find(os.Args[1:])
|
||||
if err != nil {
|
||||
return true // unknown command
|
||||
}
|
||||
|
||||
for _, arg := range os.Args {
|
||||
if !list.ExistInSlice(arg, flags) {
|
||||
continue
|
||||
}
|
||||
|
||||
// ensure that there is no user defined flag with the same name/shorthand
|
||||
trimmed := strings.TrimLeft(arg, "-")
|
||||
if len(trimmed) > 1 && cmd.Flags().Lookup(trimmed) == nil {
|
||||
return true
|
||||
}
|
||||
if len(trimmed) == 1 && cmd.Flags().ShorthandLookup(trimmed) == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// inspectRuntime tries to find the base executable directory and how it was run.
|
||||
//
|
||||
// note: we are using os.Args[0] and not os.Executable() since it could
|
||||
// break existing aliased binaries (eg. the community maintained homebrew package)
|
||||
func inspectRuntime() (baseDir string, withGoRun bool) {
|
||||
if strings.HasPrefix(os.Args[0], os.TempDir()) {
|
||||
// probably ran with go run
|
||||
withGoRun = true
|
||||
baseDir, _ = os.Getwd()
|
||||
} else {
|
||||
// probably ran with go build
|
||||
withGoRun = false
|
||||
baseDir = filepath.Dir(os.Args[0])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// newErrWriter returns a red colored stderr writter.
|
||||
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 coloredWriter struct {
|
||||
w io.Writer
|
||||
c *color.Color
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue