diff --git a/scripts/db-push.sh b/scripts/db-push.sh new file mode 100755 index 0000000..653b48b --- /dev/null +++ b/scripts/db-push.sh @@ -0,0 +1,2 @@ +#!/bin/bash +go run github.com/steebchen/prisma-client-go db push --skip-generate diff --git a/starter/create/create.go b/starter/create/create.go new file mode 100644 index 0000000..834da63 --- /dev/null +++ b/starter/create/create.go @@ -0,0 +1,144 @@ +package create + +import ( + . "ersteller-lib" + "log" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +type DatabaseType string + +const ( + DatabaseTypePostgres DatabaseType = "postgres" + DataBaseTypeSqlite DatabaseType = "sqlite" +) + +type Params struct { + DbType DatabaseType + ProjectDir string +} + +type StarterCreator struct { + params Params + thisDir string +} + +func NewStarterCreator(params Params) StarterCreator { + // Validate target directory parameter + if params.ProjectDir == "" { + log.Printf("StarterCreator.Create: ProjectDir is empty; nothing to do") + panic("ProjectDir is empty; nothing to do") + } + + // Resolve the path to the starter main.go located at ../main.go relative to this file + _, thisFile, _, ok := runtime.Caller(0) + if !ok { + log.Printf("StarterCreator.Create: unable to resolve current file location via runtime.Caller") + panic("unable to resolve current file location via runtime.Caller") + } + thisDir := filepath.Dir(thisFile) + return StarterCreator{ + params: params, + thisDir: thisDir, + } +} + +// Create reads the starter main.go file bundled with this package +// and writes it into the target project directory as main.go. +// Minimal, side-effecting function with internal error logging to keep API unchanged. +func (s StarterCreator) Create() { + Debug("StarterCreator.Create") + starterMainPath := filepath.Join(s.thisDir, "..", "main.go") + + // Read starter main.go + content, err := os.ReadFile(starterMainPath) + if err != nil { + log.Printf("StarterCreator.Create: failed to read starter main.go at %s: %v", starterMainPath, err) + return + } + + // Ensure target directory exists + if err := os.MkdirAll(s.params.ProjectDir, 0o755); err != nil { + log.Printf("StarterCreator.Create: failed to create target directory %s: %v", s.params.ProjectDir, err) + return + } + + // Write to /main.go + targetPath := filepath.Join(s.params.ProjectDir, "main.go") + must(os.WriteFile(targetPath, content, 0o644)) + log.Printf("StarterCreator.Create: wrote starter main.go to %s", targetPath) + + s.copyFile("../go.work.template", "go.work") + s.copyFile("../../template.air.toml", ".air.toml") + + if s.params.DbType == DataBaseTypeSqlite { + must(os.MkdirAll(filepath.Join(s.params.ProjectDir, "db"), 0o755)) + } + s.copySchema() + + must(os.MkdirAll(filepath.Join(s.params.ProjectDir, "scripts"), 0o755)) + s.copyFile("../../scripts/db-push.sh", "scripts/db-push.sh") + + // After copying create_db.sh, run it with the project directory as the root + scriptPath := filepath.Join(s.params.ProjectDir, "scripts", "db-push.sh") + if runtime.GOOS == "windows" { + log.Printf("StarterCreator.Create: skipping create_db.sh execution on Windows") + return + } + if err := os.Chmod(scriptPath, 0o755); err != nil { + log.Printf("StarterCreator.Create: failed to make create_db.sh executable: %v", err) + return + } + + cmd := exec.Command("/bin/sh", scriptPath) + cmd.Dir = s.params.ProjectDir + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + log.Printf("StarterCreator.Create: failed to run create_db.sh: %v", err) + } else { + log.Printf("StarterCreator.Create: successfully ran create_db.sh in %s", s.params.ProjectDir) + } +} + +func (s StarterCreator) copyFile(src string, dst string) { + content, err := os.ReadFile(filepath.Join(s.thisDir, src)) + must(err) + + // If we are copying the Prisma schema and the target DB is sqlite, + // replace the datasource block accordingly. + if dst == "schema.prisma" && s.params.DbType == DataBaseTypeSqlite { + pgBlock := "datasource db {\n provider = \"postgresql\"\n url = env(\"DATABASE_URL\")\n}\n" + sqliteBlock := "datasource db {\n provider = \"sqlite\"\n url = \"file:./db/sqlite.db\"\n}\n" + contentStr := string(content) + contentStr = strings.Replace(contentStr, pgBlock, sqliteBlock, 1) + content = []byte(contentStr) + } + + must(os.WriteFile(filepath.Join(s.params.ProjectDir, dst), content, 0o644)) +} + +func (s StarterCreator) copySchema() { + content, err := os.ReadFile(filepath.Join(s.thisDir, "../../schema_template.prisma")) + must(err) + + if s.params.DbType == DataBaseTypeSqlite { + pgBlock := "datasource db {\n provider = \"postgresql\"\n url = env(\"DATABASE_URL\")\n}\n" + sqliteBlock := "datasource db {\n provider = \"sqlite\"\n url = \"file:./db/sqlite.db\"\n}\n" + contentStr := string(content) + contentStr = strings.Replace(contentStr, pgBlock, sqliteBlock, 1) + content = []byte(contentStr) + } + + must(os.WriteFile(filepath.Join(s.params.ProjectDir, "schema.prisma"), content, 0o644)) +} + +func must(err error) { + if err != nil { + panic(err) + } +} diff --git a/starter/go.work.template b/starter/go.work.template new file mode 100644 index 0000000..24fb2d4 --- /dev/null +++ b/starter/go.work.template @@ -0,0 +1,6 @@ +go 1.25.0 + +use ( + . + ../ersteller-lib +)