From ed75fb5579e0bf1e35f2490e260251f7a9876d8d Mon Sep 17 00:00:00 2001 From: Owais Jamil Date: Mon, 23 Mar 2026 23:16:50 -0500 Subject: [PATCH] feat: local dev flag --- .gitignore | 2 + packages/api/internal/config/config.go | 28 +++++++++- packages/api/internal/config/config_test.go | 59 +++++++++++++++++++++ packages/api/main.go | 46 ++++++++-------- 4 files changed, 113 insertions(+), 22 deletions(-) create mode 100644 packages/api/internal/config/config_test.go diff --git a/.gitignore b/.gitignore index f2fa9c3..f4fdc28 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ node_modules platforms plugins www + +*.db diff --git a/packages/api/internal/config/config.go b/packages/api/internal/config/config.go index 3195fa4..d2e2a0e 100644 --- a/packages/api/internal/config/config.go +++ b/packages/api/internal/config/config.go @@ -35,7 +35,12 @@ type Config struct { AdminAuthToken string } -func Load() (*Config, error) { +type LoadOptions struct { + Local bool + WorkDir string +} + +func Load(opts LoadOptions) (*Config, error) { loadDotEnv() cfg := &Config{ @@ -63,6 +68,16 @@ func Load() (*Config, error) { EnableAdminEndpoints: envBool("ENABLE_ADMIN_ENDPOINTS", false), } + if opts.Local { + dbURL, err := localDatabaseURL(opts.WorkDir) + if err != nil { + return nil, err + } + cfg.TursoURL = dbURL + cfg.TursoToken = "" + cfg.LogFormat = "text" + } + var errs []error if cfg.TursoURL == "" { errs = append(errs, errors.New("TURSO_DATABASE_URL is required")) @@ -76,6 +91,17 @@ func Load() (*Config, error) { return cfg, nil } +func localDatabaseURL(workDir string) (string, error) { + if strings.TrimSpace(workDir) == "" { + var err error + workDir, err = os.Getwd() + if err != nil { + return "", err + } + } + return "file:" + filepath.Join(workDir, "twister-dev.db"), nil +} + func loadDotEnv() { seen := map[string]bool{} candidates := make([]string, 0, 8) diff --git a/packages/api/internal/config/config_test.go b/packages/api/internal/config/config_test.go new file mode 100644 index 0000000..91a60d9 --- /dev/null +++ b/packages/api/internal/config/config_test.go @@ -0,0 +1,59 @@ +package config + +import ( + "os" + "path/filepath" + "testing" +) + +func TestLoadRequiresRemoteTursoConfigurationByDefault(t *testing.T) { + t.Setenv("TURSO_DATABASE_URL", "") + t.Setenv("TURSO_AUTH_TOKEN", "") + + _, err := Load(LoadOptions{}) + if err == nil { + t.Fatal("expected missing Turso config to fail") + } +} + +func TestLoadLocalOverridesRemoteDatabaseAndLogging(t *testing.T) { + workDir := t.TempDir() + t.Setenv("TURSO_DATABASE_URL", "libsql://example.turso.io") + t.Setenv("TURSO_AUTH_TOKEN", "secret") + t.Setenv("LOG_FORMAT", "json") + + cfg, err := Load(LoadOptions{Local: true, WorkDir: workDir}) + if err != nil { + t.Fatalf("load config: %v", err) + } + + wantURL := "file:" + filepath.Join(workDir, "twister-dev.db") + if cfg.TursoURL != wantURL { + t.Fatalf("TursoURL: got %q, want %q", cfg.TursoURL, wantURL) + } + if cfg.TursoToken != "" { + t.Fatalf("TursoToken: got %q, want empty", cfg.TursoToken) + } + if cfg.LogFormat != "text" { + t.Fatalf("LogFormat: got %q, want %q", cfg.LogFormat, "text") + } +} + +func TestLoadLocalUsesCurrentWorkingDirectoryWhenUnset(t *testing.T) { + wd, err := os.Getwd() + if err != nil { + t.Fatalf("getwd: %v", err) + } + t.Setenv("TURSO_DATABASE_URL", "") + t.Setenv("TURSO_AUTH_TOKEN", "") + + cfg, err := Load(LoadOptions{Local: true}) + if err != nil { + t.Fatalf("load config: %v", err) + } + + wantURL := "file:" + filepath.Join(wd, "twister-dev.db") + if cfg.TursoURL != wantURL { + t.Fatalf("TursoURL: got %q, want %q", cfg.TursoURL, wantURL) + } +} diff --git a/packages/api/main.go b/packages/api/main.go index 841e936..a779a25 100644 --- a/packages/api/main.go +++ b/packages/api/main.go @@ -28,6 +28,8 @@ var ( ) func main() { + var local bool + root := &cobra.Command{ Use: "twister", Short: "Tangled search service", @@ -36,14 +38,16 @@ func main() { SilenceErrors: true, } + root.PersistentFlags().BoolVar(&local, "local", false, "Use a local twister-dev.db database and text logs for development") + root.AddCommand( - newAPICmd(), - newIndexerCmd(), - newBackfillCmd(), - newEmbedWorkerCmd(), - newReindexCmd(), - newReembedCmd(), - newHealthcheckCmd(), + newAPICmd(&local), + newIndexerCmd(&local), + newBackfillCmd(&local), + newEmbedWorkerCmd(&local), + newReindexCmd(&local), + newReembedCmd(&local), + newHealthcheckCmd(&local), ) if err := root.Execute(); err != nil { @@ -63,13 +67,13 @@ func baseContext() (context.Context, context.CancelFunc) { return ctx, cancel } -func newAPICmd() *cobra.Command { +func newAPICmd(local *bool) *cobra.Command { return &cobra.Command{ Use: "api", Aliases: []string{"serve"}, Short: "Start the HTTP search API", RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := config.Load() + cfg, err := config.Load(config.LoadOptions{Local: *local}) if err != nil { return fmt.Errorf("config: %w", err) } @@ -103,12 +107,12 @@ func newAPICmd() *cobra.Command { } } -func newIndexerCmd() *cobra.Command { +func newIndexerCmd(local *bool) *cobra.Command { return &cobra.Command{ Use: "indexer", Short: "Start the Tap consumer and indexer", RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := config.Load() + cfg, err := config.Load(config.LoadOptions{Local: *local}) if err != nil { return fmt.Errorf("config: %w", err) } @@ -176,12 +180,12 @@ func newIndexerCmd() *cobra.Command { } } -func newEmbedWorkerCmd() *cobra.Command { +func newEmbedWorkerCmd(local *bool) *cobra.Command { return &cobra.Command{ Use: "embed-worker", Short: "Start the async embedding worker", RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := config.Load() + cfg, err := config.Load(config.LoadOptions{Local: *local}) if err != nil { return fmt.Errorf("config: %w", err) } @@ -196,14 +200,14 @@ func newEmbedWorkerCmd() *cobra.Command { } } -func newBackfillCmd() *cobra.Command { +func newBackfillCmd(local *bool) *cobra.Command { var opts backfill.Options cmd := &cobra.Command{ Use: "backfill", Short: "Discover users from seeds and register repos for Tap backfill", RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := config.Load() + cfg, err := config.Load(config.LoadOptions{Local: *local}) if err != nil { return fmt.Errorf("config: %w", err) } @@ -259,12 +263,12 @@ func newBackfillCmd() *cobra.Command { return cmd } -func newReindexCmd() *cobra.Command { +func newReindexCmd(local *bool) *cobra.Command { return &cobra.Command{ Use: "reindex", Short: "Re-normalize and upsert all documents", RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := config.Load() + cfg, err := config.Load(config.LoadOptions{Local: *local}) if err != nil { return fmt.Errorf("config: %w", err) } @@ -275,12 +279,12 @@ func newReindexCmd() *cobra.Command { } } -func newReembedCmd() *cobra.Command { +func newReembedCmd(local *bool) *cobra.Command { return &cobra.Command{ Use: "reembed", Short: "Re-generate all embeddings", RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := config.Load() + cfg, err := config.Load(config.LoadOptions{Local: *local}) if err != nil { return fmt.Errorf("config: %w", err) } @@ -291,12 +295,12 @@ func newReembedCmd() *cobra.Command { } } -func newHealthcheckCmd() *cobra.Command { +func newHealthcheckCmd(local *bool) *cobra.Command { return &cobra.Command{ Use: "healthcheck", Short: "One-shot health probe", RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := config.Load() + cfg, err := config.Load(config.LoadOptions{Local: *local}) if err != nil { return fmt.Errorf("config: %w", err) } -- 2.51.2