diff --git a/pocketbase.go b/pocketbase.go index 5f47bc69..67c57b2b 100644 --- a/pocketbase.go +++ b/pocketbase.go @@ -26,50 +26,60 @@ type appWrapper struct { // PocketBase defines a PocketBase app launcher. // -// It implements [core.App] via embedding and all of interface methods +// 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 { *appWrapper - // RootCmd is the main cli command + debugFlag bool + dataDirFlag string + encryptionEnvFlag string + hideStartBanner bool + + // RootCmd is the main console command RootCmd *cobra.Command - - // console flags - debugFlag bool - dataDirFlag string - encryptionEnv string - - // console flag fallback values - defaultDebug bool - defaultDataDir string - defaultEncryptionEnv string - - // serve start banner - showStartBanner bool } -// New creates a new PocketBase instance. +// Config is the PocketBase initialization config struct. +type Config struct { + // optional default values for the console flags + DefaultDebug bool + DefaultDataDir string // if not set, it will fallback to "./pb_data" + DefaultEncryptionEnv string + + // hide the default console server info on app startup + HideStartBanner bool +} + +// 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 Start() is executed. -// If you want to initialize the application before calling Start(), -// then you'll have to manually call Bootstrap(). +// Everything will be initialized when [Start()] is executed. +// If you want to initialize the application before calling [Start()], +// then you'll have to manually call [Bootstrap()]. func New() *PocketBase { - // try to find the base executable directory and how it was run - var withGoRun bool - var baseDir string - 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]) - } + _, isUsingGoRun := inspectRuntime() - defaultDir := filepath.Join(baseDir, "pb_data") + return NewWithConfig(Config{ + DefaultDebug: 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 [Start()] is executed. +// If you want to initialize the application before calling [Start()], +// then you'll have to manually call [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") + } pb := &PocketBase{ RootCmd: &cobra.Command{ @@ -84,19 +94,20 @@ func New() *PocketBase { DisableDefaultCmd: true, }, }, - defaultDebug: withGoRun, - defaultDataDir: defaultDir, - defaultEncryptionEnv: "", - showStartBanner: true, + debugFlag: config.DefaultDebug, + dataDirFlag: config.DefaultDataDir, + encryptionEnvFlag: config.DefaultEncryptionEnv, + hideStartBanner: config.HideStartBanner, } // parse base flags // (errors are ignored, since the full flags parsing happens on Execute()) - pb.eagerParseFlags() + pb.eagerParseFlags(config) + // initialize the app instance pb.appWrapper = &appWrapper{core.NewBaseApp( pb.dataDirFlag, - pb.encryptionEnv, + pb.encryptionEnvFlag, pb.debugFlag, )} @@ -111,35 +122,11 @@ func New() *PocketBase { return pb } -// DefaultDebug sets the default --debug flag value. -func (pb *PocketBase) DefaultDebug(val bool) *PocketBase { - pb.defaultDebug = val - return pb -} - -// DefaultDataDir sets the default --dir flag value. -func (pb *PocketBase) DefaultDataDir(val string) *PocketBase { - pb.defaultDataDir = val - return pb -} - -// DefaultEncryptionEnv sets the default --encryptionEnv flag value. -func (pb *PocketBase) DefaultEncryptionEnv(val string) *PocketBase { - pb.defaultEncryptionEnv = val - return pb -} - -// ShowStartBanner shows/hides the web server start banner. -func (pb *PocketBase) ShowStartBanner(val bool) *PocketBase { - pb.showStartBanner = val - return pb -} - // Start starts the application, aka. registers the default system // commands (serve, migrate, version) and executes pb.RootCmd. func (pb *PocketBase) Start() error { // register system commands - pb.RootCmd.AddCommand(cmd.NewServeCommand(pb, pb.showStartBanner)) + pb.RootCmd.AddCommand(cmd.NewServeCommand(pb, !pb.hideStartBanner)) pb.RootCmd.AddCommand(cmd.NewMigrateCommand(pb)) return pb.Execute() @@ -184,27 +171,41 @@ func (pb *PocketBase) onTerminate() error { // 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() error { +func (pb *PocketBase) eagerParseFlags(config Config) error { pb.RootCmd.PersistentFlags().StringVar( &pb.dataDirFlag, "dir", - pb.defaultDataDir, + config.DefaultDataDir, "the PocketBase data directory", ) pb.RootCmd.PersistentFlags().StringVar( - &pb.encryptionEnv, + &pb.encryptionEnvFlag, "encryptionEnv", - pb.defaultEncryptionEnv, - "the env variable whose value of 32 chars will be used \nas encryption key for the app settings (default none)", + 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.debugFlag, "debug", - pb.defaultDebug, + config.DefaultDebug, "enable debug mode, aka. showing more detailed logs", ) return pb.RootCmd.ParseFlags(os.Args[1:]) } + +// tries to find the base executable directory and how it was run +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 +} diff --git a/pocketbase_test.go b/pocketbase_test.go index 68a006fa..10ba6a54 100644 --- a/pocketbase_test.go +++ b/pocketbase_test.go @@ -6,14 +6,19 @@ import ( ) func TestNew(t *testing.T) { - testDir := "./pb_test_data_dir" - defer os.RemoveAll(testDir) + // copy os.Args + originalArgs := []string{} + copy(originalArgs, os.Args) + defer func() { + // restore os.Args + copy(os.Args, originalArgs) + }() - // reset os.Args + // change os.Args os.Args = os.Args[0:1] os.Args = append( os.Args, - "--dir="+testDir, + "--dir=test_dir", "--encryptionEnv=test_encryption_env", "--debug=true", ) @@ -32,8 +37,8 @@ func TestNew(t *testing.T) { t.Fatal("Expected appWrapper to be initialized, got nil") } - if app.DataDir() != testDir { - t.Fatalf("Expected app.DataDir() %q, got %q", testDir, app.DataDir()) + if app.DataDir() != "test_dir" { + t.Fatalf("Expected app.DataDir() %q, got %q", "test_dir", app.DataDir()) } if app.EncryptionEnv() != "test_encryption_env" { @@ -45,52 +50,93 @@ func TestNew(t *testing.T) { } } -func TestDefaultDebug(t *testing.T) { - app := New() +func TestNewWithConfig(t *testing.T) { + app := NewWithConfig(Config{ + DefaultDebug: true, + DefaultDataDir: "test_dir", + DefaultEncryptionEnv: "test_encryption_env", + HideStartBanner: true, + }) - app.DefaultDebug(true) - if app.defaultDebug != true { - t.Fatalf("Expected defaultDebug %v, got %v", true, app.defaultDebug) + if app == nil { + t.Fatal("Expected initialized PocketBase instance, got nil") } - app.DefaultDebug(false) - if app.defaultDebug != false { - t.Fatalf("Expected defaultDebug %v, got %v", false, app.defaultDebug) + if app.RootCmd == nil { + t.Fatal("Expected RootCmd to be initialized, got nil") + } + + if app.appWrapper == nil { + t.Fatal("Expected appWrapper to be initialized, got nil") + } + + if app.hideStartBanner != true { + t.Fatal("Expected app.hideStartBanner to be true, got false") + } + + if app.DataDir() != "test_dir" { + t.Fatalf("Expected app.DataDir() %q, got %q", "test_dir", app.DataDir()) + } + + if app.EncryptionEnv() != "test_encryption_env" { + t.Fatalf("Expected app.DataDir() %q, got %q", "test_encryption_env", app.EncryptionEnv()) + } + + if app.IsDebug() != true { + t.Fatal("Expected app.IsDebug() true, got false") } } -func TestDefaultDataDir(t *testing.T) { - app := New() +func TestNewWithConfigAndFlags(t *testing.T) { + // copy os.Args + originalArgs := []string{} + copy(originalArgs, os.Args) + defer func() { + // restore os.Args + copy(os.Args, originalArgs) + }() - expected := "test_default" + // change os.Args + os.Args = os.Args[0:1] + os.Args = append( + os.Args, + "--dir=test_dir_flag", + "--encryptionEnv=test_encryption_env_flag", + "--debug=false", + ) - app.DefaultDataDir(expected) - if app.defaultDataDir != expected { - t.Fatalf("Expected defaultDataDir %v, got %v", expected, app.defaultDataDir) - } -} - -func TestDefaultEncryptionEnv(t *testing.T) { - app := New() - - expected := "test_env" - - app.DefaultEncryptionEnv(expected) - if app.defaultEncryptionEnv != expected { - t.Fatalf("Expected defaultEncryptionEnv %v, got %v", expected, app.defaultEncryptionEnv) - } -} - -func TestShowStartBanner(t *testing.T) { - app := New() - - app.ShowStartBanner(true) - if app.showStartBanner != true { - t.Fatalf("Expected showStartBanner %v, got %v", true, app.showStartBanner) - } - - app.ShowStartBanner(false) - if app.showStartBanner != false { - t.Fatalf("Expected showStartBanner %v, got %v", false, app.showStartBanner) + app := NewWithConfig(Config{ + DefaultDebug: true, + DefaultDataDir: "test_dir", + DefaultEncryptionEnv: "test_encryption_env", + HideStartBanner: true, + }) + + if app == nil { + t.Fatal("Expected initialized PocketBase instance, got nil") + } + + if app.RootCmd == nil { + t.Fatal("Expected RootCmd to be initialized, got nil") + } + + if app.appWrapper == nil { + t.Fatal("Expected appWrapper to be initialized, got nil") + } + + if app.hideStartBanner != true { + t.Fatal("Expected app.hideStartBanner to be true, got false") + } + + if app.DataDir() != "test_dir_flag" { + t.Fatalf("Expected app.DataDir() %q, got %q", "test_dir_flag", app.DataDir()) + } + + if app.EncryptionEnv() != "test_encryption_env_flag" { + t.Fatalf("Expected app.DataDir() %q, got %q", "test_encryption_env_flag", app.EncryptionEnv()) + } + + if app.IsDebug() != false { + t.Fatal("Expected app.IsDebug() false, got true") } }