From c184fb5541418e60cf9316d5fb4e0ccddd21e6df Mon Sep 17 00:00:00 2001 From: Clancy Date: Wed, 5 Aug 2026 15:49:58 +0300 Subject: [PATCH 1/2] feat(config): add support for default yml config file, along with JSON 1- Update config stuct to take ClientName, and EmbedMigrations, for more flexable config 2- Add guard for when more than one config file exists, no matter which format 3- Update HandleInit() to parse yml, and write config template for yml with guiding comments --- cli/commands.go | 2 +- cli/getConfig.go | 73 ++++++++++++++++++++++++++---------- cli/handleGenerate.go | 19 ++++++---- cli/handleInit.go | 87 ++++++++++++++++++++++++++++++++++++++----- go.mod | 2 +- integration/phi.yml | 15 ++++++++ 6 files changed, 160 insertions(+), 38 deletions(-) create mode 100644 integration/phi.yml diff --git a/cli/commands.go b/cli/commands.go index c065e0c..08cecc7 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -24,7 +24,7 @@ func init() { Aliases: []string{"-i"}, Description: "Creates the config file in the root directory", Callback: func(args []string) { - handleInit() + handleInit(args) }, }, { diff --git a/cli/getConfig.go b/cli/getConfig.go index 74361c0..ad43261 100644 --- a/cli/getConfig.go +++ b/cli/getConfig.go @@ -5,6 +5,9 @@ import ( "log" "os" "slices" + "strings" + + "gopkg.in/yaml.v3" ) var LogLevels = []string{ @@ -17,46 +20,78 @@ var LogLevels = []string{ } type Config struct { - Database DatabaseConfig `json:"database"` - Schema string `json:"schema"` - Output OutputConfig `json:"output"` - Log []string `json:"log"` + ClientName string `json:"client_name" yaml:"client_name"` + EmbedMigrations *bool `json:"embed_migrations" yaml:"embed_migrations"` + Database DatabaseConfig `json:"database" yaml:"database"` + Schema string `json:"schema" yaml:"schema"` + Output OutputConfig `json:"output" yaml:"output"` + Log []string `json:"log" yaml:"log"` } type DatabaseConfig struct { - URLEnv string `json:"url_env"` - DirectURLEnv string `json:"direct_url_env"` + URLEnv string `json:"url_env" yaml:"url_env"` + DirectURLEnv string `json:"direct_url_env" yaml:"direct_url_env"` } type OutputConfig struct { - Client string `json:"client"` - Migrations string `json:"migrations"` + Client string `json:"client" yaml:"client"` + Migrations string `json:"migrations" yaml:"migrations"` } func GetConfig() *Config { - var config Config - configFile, err := os.ReadFile("phi.json") + var foundFiles []string + candidateFiles := []string{"phi.yml", "phi.yaml", "phi.json"} + + for _, f := range candidateFiles { + if _, err := os.Stat(f); err == nil { + foundFiles = append(foundFiles, f) + } + } + + if len(foundFiles) > 1 { + log.Fatalf("multiple configuration files found (%s). Please keep only one configuration file in the project directory.", strings.Join(foundFiles, ", ")) + return nil + } + + if len(foundFiles) == 0 { + log.Fatal("configuration file not found (expected phi.yml, phi.yaml, or phi.json)") + return nil + } + + foundFile := foundFiles[0] + configFile, err := os.ReadFile(foundFile) if err != nil { - log.Fatal("phi.json not found") + log.Fatalf("failed to read %s: %v", foundFile, err) return nil } - err = json.Unmarshal(configFile, &config) + var config Config + if strings.HasSuffix(foundFile, ".json") { + err = json.Unmarshal(configFile, &config) + } else { + err = yaml.Unmarshal(configFile, &config) + } + if err != nil { - log.Fatal(err) + log.Fatalf("failed to parse %s: %v", foundFile, err) return nil } - // hasAll := false + + // Apply default values if omitted + if config.ClientName == "" { + config.ClientName = "phi" + } + if config.EmbedMigrations == nil { + defaultEmbed := true + config.EmbedMigrations = &defaultEmbed + } + for _, l := range config.Log { if !slices.Contains(LogLevels, l) && l != "all" { - log.Fatalf("invalid log level in phi.json: %q (must be one of: query, info, warn, error, all)", l) + log.Fatalf("invalid log level in %s: %q (must be one of: query, info, warn, error, all)", foundFile, l) return nil } } - // if hasAll && len(config.Log) > 1 { - // log.Fatal("invalid log configuration: 'all' must be the only log level specified") - // return nil - // } return &config } diff --git a/cli/handleGenerate.go b/cli/handleGenerate.go index 11d7dfb..7907a37 100644 --- a/cli/handleGenerate.go +++ b/cli/handleGenerate.go @@ -34,16 +34,21 @@ func handleGenerate() { return } - relDir, err := filepath.Rel(config.Output.Client, config.Output.Migrations) var embedRelDir string - if err == nil && !strings.HasPrefix(relDir, "..") && !filepath.IsAbs(relDir) { - embedRelDir = filepath.ToSlash(filepath.Join(relDir, "*.sql")) - } else { - fmt.Printf("[WARNING]: Migrations directory %q is not a subdirectory of client output directory %q. Go's //go:embed does not support parent directory paths ('..'). Embedded migrations will be disabled.\n", - config.Output.Migrations, config.Output.Client) + if config.EmbedMigrations == nil || *config.EmbedMigrations { + relDir, err := filepath.Rel(config.Output.Client, config.Output.Migrations) + if err == nil && !strings.HasPrefix(relDir, "..") && !filepath.IsAbs(relDir) { + embedRelDir = filepath.ToSlash(filepath.Join(relDir, "*.sql")) + } else { + fmt.Printf("[WARNING]: Migrations directory %q is not a subdirectory of client output directory %q. Go's //go:embed does not support parent directory paths ('..'). Embedded migrations will be disabled.\n", + config.Output.Migrations, config.Output.Client) + } } - pkgName := filepath.Base(config.Output.Client) + pkgName := config.ClientName + if pkgName == "" { + pkgName = filepath.Base(config.Output.Client) + } if pkgName == "." || pkgName == "" { pkgName = "phi" } diff --git a/cli/handleInit.go b/cli/handleInit.go index e997ff9..4791878 100644 --- a/cli/handleInit.go +++ b/cli/handleInit.go @@ -3,29 +3,96 @@ package cli import ( "fmt" "os" + "path/filepath" + "strings" ) -func handleInit() { - err := os.WriteFile("phi.json", []byte(configFileContent), 0644) - if err != nil { - fmt.Printf("Error writing phi.json: %v\n", err) +func handleInit(args []string) { + format := "yml" + targetDir := "." + + if len(args) == 1 { + arg := strings.ToLower(args[0]) + if arg == "yml" || arg == "yaml" || arg == "json" { + format = arg + } else { + targetDir = args[0] + } + } else if len(args) >= 2 { + arg0 := strings.ToLower(args[0]) + if arg0 == "yml" || arg0 == "yaml" || arg0 == "json" { + format = arg0 + targetDir = args[1] + } else { + targetDir = args[0] + } + } + + if err := os.MkdirAll(targetDir, 0755); err != nil { + fmt.Printf("Error creating directory %s: %v\n", targetDir, err) + os.Exit(1) + } + + filename := "phi." + format + outPath := filepath.Join(targetDir, filename) + + var content string + if format == "json" { + content = jsonConfigTemplate + } else { + content = ymlConfigTemplate + } + + if err := os.WriteFile(outPath, []byte(strings.TrimSpace(content)+"\n"), 0644); err != nil { + fmt.Printf("Error writing %s: %v\n", outPath, err) os.Exit(1) } - fmt.Println("creating phi.json ....") + + fmt.Printf("Created configuration file: %s\n", outPath) } -var configFileContent string = ` +var ymlConfigTemplate string = ` +# Name of the generated Go client package (default: "phi") +client_name: phi + +# Enable Go 1.16+ embedded migrations (//go:embed) inside client +embed_migrations: true + +database: + # Environment variable for runtime client database connections + url_env: DATABASE_URL + # Environment variable for migration direct DDL connections + direct_url_env: DATABASE_DIRECT_URL + +# Path to your Prisma schema definition file +schema: ./schema.prisma + +output: + # Output directory for generated Go client code + client: ./phi + # Output directory for Goose-compatible .sql migration files + migrations: ./phi/migrations + +# Logging levels: none, query, warn, error, info, all +log: + - none +` + +var jsonConfigTemplate string = ` { + "client_name": "phi", + "embed_migrations": true, "database": { "url_env": "DATABASE_URL", "direct_url_env": "DATABASE_DIRECT_URL" }, - "schema": "./schema.prisma", - "output": { "client": "./phi", "migrations": "./phi/migrations" - } + }, + "log": [ + "none" + ] } - ` +` diff --git a/go.mod b/go.mod index 3b02fb6..a05a464 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/lib/pq v1.12.3 github.com/pressly/goose/v3 v3.27.1 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 + gopkg.in/yaml.v3 v3.0.1 modernc.org/sqlite v1.49.1 ) @@ -33,7 +34,6 @@ require ( golang.org/x/sync v0.21.0 // indirect golang.org/x/sys v0.45.0 // indirect golang.org/x/text v0.39.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.72.1 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/integration/phi.yml b/integration/phi.yml new file mode 100644 index 0000000..75b4bba --- /dev/null +++ b/integration/phi.yml @@ -0,0 +1,15 @@ +client_name: phi +embed_migrations: true + +database: + url_env: DATABASE_URL + direct_url_env: DATABASE_DIRECT_URL + +schema: ./schema.prisma + +output: + client: ./phi + migrations: ./phi/migrations + +log: + - none From 596d8e83bc68519b78d292f345490a1d7d2c1863 Mon Sep 17 00:00:00 2001 From: Clancy Date: Wed, 5 Aug 2026 15:59:23 +0300 Subject: [PATCH 2/2] chore(phi.json): Add phi.json for deletion for the CI to run right --- integration/phi.json | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 integration/phi.json diff --git a/integration/phi.json b/integration/phi.json deleted file mode 100644 index 727f13c..0000000 --- a/integration/phi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "database": { - "url_env": "DATABASE_URL", - "direct_url_env": "DATABASE_DIRECT_URL" - }, - - "schema": "./schema.prisma", - - "output": { - "client": "./phi", - "migrations": "./phi/migrations" - }, - "log":["none"] -} \ No newline at end of file