Files
ersteller/starter/create/create.go
T
2025-09-14 16:34:52 +02:00

256 lines
7.4 KiB
Go

package create
import (
. "ersteller-lib"
"ersteller-lib/starter/env"
"fmt"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strings"
)
type DatabaseType string
const (
DatabaseTypePostgres DatabaseType = "postgres"
DataBaseTypeSqlite DatabaseType = "sqlite"
)
type Params struct {
DbType DatabaseType
ProjectDir string
ModuleName string
GoPath string
OverwriteEnv bool
}
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")
}
thisDir := GetPathToThisDir()
return StarterCreator{
params: params,
thisDir: thisDir,
}
}
func GetPathToThisDir() string {
// 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 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 <ProjectDir>/main.go
targetPath := filepath.Join(s.params.ProjectDir, "main.go")
content = s.replaceImports(content)
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")
//s.copyGoMod()
//s.executeGoModTidy()
s.executeGetPrismaClient()
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")
if s.params.DbType == DataBaseTypeSqlite {
s.executeDbPush()
}
s.copyFile("../.gitignore", ".gitignore")
s.createEnvironment()
directories := []string{
"index",
"page",
"routes",
"static",
"ent",
}
for _, dir := range directories {
s.copyDirectoryRecursive(path.Join(s.thisDir, "..", dir), path.Join(s.params.ProjectDir, dir))
}
s.copySchemaFiles()
}
func (s StarterCreator) copySchemaFiles() {
s.copyFile("../../schema/ent/pagination_query.tmpl", "ent/pagination_query.tmpl")
content, err := os.ReadFile(filepath.Join(s.thisDir, "../generate_schema.go"))
must(err)
//content = s.replaceImports(content)
must(os.WriteFile(filepath.Join(s.params.ProjectDir, "generate_schema.go"), content, 0o644))
}
func (s StarterCreator) createEnvironment() {
if err := os.MkdirAll(s.params.ProjectDir+"/env", 0o755); err != nil {
log.Printf("StarterCreator.Create: failed to create env target directory%v", err)
return
}
//s.copyFile("../env/environment.go", "env/environment.go")
content, err := os.ReadFile(filepath.Join(s.thisDir, "../env/environment.go"))
must(err)
must(os.WriteFile(filepath.Join(s.params.ProjectDir, "env/environment.go"), content, 0o644))
must(env.GenerateEnvFile(s.params.ProjectDir, s.params.OverwriteEnv))
}
func (s StarterCreator) executeDbPush() {
// 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)
must(os.WriteFile(filepath.Join(s.params.ProjectDir, dst), content, 0o644))
}
func (s StarterCreator) copyDirectoryRecursive(src string, dst string) {
must(os.MkdirAll(dst, 0o755))
entries, err := os.ReadDir(src)
must(err)
for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
Debug("copying srcPath: ", srcPath, " to dstPath: ", dstPath)
if entry.IsDir() {
s.copyDirectoryRecursive(srcPath, dstPath)
continue
}
content, err := os.ReadFile(srcPath)
content = s.replaceImports(content)
must(err)
must(os.WriteFile(filepath.Join(dstPath), 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)
contentStr = strings.ReplaceAll(contentStr, " @db.Timestamptz(3)", "")
contentStr = strings.ReplaceAll(contentStr, " @db.Timestamp(6)", "")
content = []byte(contentStr)
}
must(os.WriteFile(filepath.Join(s.params.ProjectDir, "schema.prisma"), content, 0o644))
}
func (s StarterCreator) copyGoMod() {
content, err := os.ReadFile(filepath.Join(s.thisDir, "../go.mod.template"))
must(err)
contentString := InlineTemplate(string(content), struct {
ModuleName string
}{
ModuleName: s.params.ModuleName,
})
must(os.WriteFile(filepath.Join(s.params.ProjectDir, "go.mod"), []byte(contentString), 0o644))
}
func (s StarterCreator) executeGoModTidy() {
goCommand := s.getGoCommand()
cmd := exec.Command(goCommand, "mod", "tidy")
cmd.Dir = s.params.ProjectDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
must(cmd.Run())
}
func (s StarterCreator) getGoCommand() string {
goCommand := "go"
if s.params.GoPath != "" {
goCommand = s.params.GoPath
}
return goCommand
}
func (s StarterCreator) executeGetPrismaClient() {
goCommand := s.getGoCommand()
cmd := exec.Command(goCommand, "get", "github.com/steebchen/prisma-client-go")
cmd.Dir = s.params.ProjectDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
must(cmd.Run())
}
func (s StarterCreator) replaceImports(content []byte) []byte {
contentString := string(content)
contentString = strings.ReplaceAll(contentString, "\"ersteller-lib/starter/", fmt.Sprint("\"", s.params.ModuleName, "/"))
return []byte(contentString)
}
func must(err error) {
if err != nil {
panic(err)
}
}