From 19a2cc1fe35f423486deac77473438c9f42a168e Mon Sep 17 00:00:00 2001 From: Hein Date: Thu, 10 Sep 2026 22:08:49 +0200 Subject: [PATCH 1/2] feat(writers): stamp RelSpec version in generated headers; preserve DBML column order - Add pkg/buildinfo with Version/BuildDate (set via ldflags, VCS fallback); cmd/relspec now sources version info from it - Emit "RelSpec (built: )" in generated file headers for bun, gorm, drizzle, pgsql (incl. migration), mssql, sqlite writers - pgsql writer: getSortedColumns now sorts by Sequence then Name so the streaming WriteSchema/WriteDatabase path preserves source column order - dbml reader: mergeTable re-bases merged-in column Sequence values past the existing max, fixing colliding sequences (and alphabetical fallback) when a table is split across multiple DBML files - Tests for bun/gorm header + column order, and dbml multi-file merge ordering --- Makefile | 2 +- cmd/relspec/root.go | 40 +++------------------- pkg/buildinfo/buildinfo.go | 49 +++++++++++++++++++++++++++ pkg/readers/dbml/reader.go | 36 ++++++++++++++++++-- pkg/readers/dbml/reader_test.go | 16 +++++++++ pkg/writers/bun/template_data.go | 3 ++ pkg/writers/bun/templates.go | 3 +- pkg/writers/bun/writer_test.go | 37 ++++++++++++++++++++ pkg/writers/drizzle/template_data.go | 11 +++--- pkg/writers/drizzle/templates.go | 3 +- pkg/writers/gorm/template_data.go | 3 ++ pkg/writers/gorm/templates.go | 3 +- pkg/writers/gorm/writer_test.go | 40 ++++++++++++++++++++++ pkg/writers/mssql/writer.go | 5 +-- pkg/writers/pgsql/migration_writer.go | 3 +- pkg/writers/pgsql/writer.go | 22 ++++-------- pkg/writers/sqlite/writer.go | 3 +- 17 files changed, 214 insertions(+), 65 deletions(-) create mode 100644 pkg/buildinfo/buildinfo.go diff --git a/Makefile b/Makefile index 2e5752e..2cb19fa 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ GOVULNCHECK = go run golang.org/x/vuln/cmd/govulncheck@latest # Version information VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") BUILD_DATE := $(shell date -u +"%Y-%m-%d %H:%M:%S UTC") -LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.buildDate=$(BUILD_DATE)' +LDFLAGS := -X 'git.warky.dev/wdevs/relspecgo/pkg/buildinfo.Version=$(VERSION)' -X 'git.warky.dev/wdevs/relspecgo/pkg/buildinfo.BuildDate=$(BUILD_DATE)' # Auto-detect container runtime (Docker or Podman) CONTAINER_RUNTIME := $(shell \ diff --git a/cmd/relspec/root.go b/cmd/relspec/root.go index 2aded30..16f7673 100644 --- a/cmd/relspec/root.go +++ b/cmd/relspec/root.go @@ -2,52 +2,22 @@ package main import ( "fmt" - "runtime/debug" - "time" + "git.warky.dev/wdevs/relspecgo/pkg/buildinfo" "github.com/spf13/cobra" ) +// version/buildDate mirror pkg/buildinfo so existing call sites keep working. +// The actual values are set there via ldflags (see Makefile). var ( - // Version information, set via ldflags during build - version = "dev" - buildDate = "unknown" + version = buildinfo.Version + buildDate = buildinfo.BuildDate prisma7 bool noVersion bool silent bool strictDirectives bool ) -func init() { - // If version wasn't set via ldflags, try to get it from build info - if version == "dev" { - if info, ok := debug.ReadBuildInfo(); ok { - // Try to get version from VCS - var vcsRevision, vcsTime string - for _, setting := range info.Settings { - switch setting.Key { - case "vcs.revision": - if len(setting.Value) >= 7 { - vcsRevision = setting.Value[:7] - } - case "vcs.time": - vcsTime = setting.Value - } - } - - if vcsRevision != "" { - version = vcsRevision - } - - if vcsTime != "" { - if t, err := time.Parse(time.RFC3339, vcsTime); err == nil { - buildDate = t.UTC().Format("2006-01-02 15:04:05 UTC") - } - } - } - } -} - var rootCmd = &cobra.Command{ Use: "relspec", Short: "RelSpec - Database schema conversion and analysis tool", diff --git a/pkg/buildinfo/buildinfo.go b/pkg/buildinfo/buildinfo.go new file mode 100644 index 0000000..d556c08 --- /dev/null +++ b/pkg/buildinfo/buildinfo.go @@ -0,0 +1,49 @@ +// Package buildinfo exposes the RelSpec version and build date so that both the +// CLI and the schema writers can stamp generated output with the same values. +package buildinfo + +import ( + "fmt" + "runtime/debug" + "time" +) + +// Version and BuildDate are set via -ldflags at build time (see Makefile). When +// built without ldflags they are backfilled from the Go module build info. +var ( + Version = "dev" + BuildDate = "unknown" +) + +func init() { + if Version != "dev" { + return + } + info, ok := debug.ReadBuildInfo() + if !ok { + return + } + var rev, vcsTime string + for _, s := range info.Settings { + switch s.Key { + case "vcs.revision": + if len(s.Value) >= 7 { + rev = s.Value[:7] + } + case "vcs.time": + vcsTime = s.Value + } + } + if rev != "" { + Version = rev + } + if t, err := time.Parse(time.RFC3339, vcsTime); err == nil { + BuildDate = t.UTC().Format("2006-01-02 15:04:05 UTC") + } +} + +// GeneratedComment returns the one-line provenance string embedded in generated +// files, e.g. "RelSpec dev (built: unknown)". +func GeneratedComment() string { + return fmt.Sprintf("RelSpec %s (built: %s)", Version, BuildDate) +} diff --git a/pkg/readers/dbml/reader.go b/pkg/readers/dbml/reader.go index 5326413..664399c 100644 --- a/pkg/readers/dbml/reader.go +++ b/pkg/readers/dbml/reader.go @@ -305,9 +305,39 @@ func sortDBMLFiles(files []string) []string { // Merges: Columns (map), Constraints (map), Indexes (map), Relationships (map) // Uses first non-empty Description func mergeTable(baseTable, fileTable *models.Table) { - // Merge columns (map naturally merges - later keys overwrite) - for key, col := range fileTable.Columns { - baseTable.Columns[key] = col + // Merge columns. Each file numbers its own columns from 1, so a table split + // across files would otherwise end up with colliding Column.Sequence values + // and writers would fall back to alphabetical order. Re-base the incoming + // file's new columns after the highest sequence already present, preserving + // their in-file order. Columns that overwrite an existing key keep the + // original position. + var maxSeq uint + for _, col := range baseTable.Columns { + if col.Sequence > maxSeq { + maxSeq = col.Sequence + } + } + + incoming := make([]*models.Column, 0, len(fileTable.Columns)) + for _, col := range fileTable.Columns { + incoming = append(incoming, col) + } + sort.Slice(incoming, func(i, j int) bool { + if incoming[i].Sequence != incoming[j].Sequence { + return incoming[i].Sequence < incoming[j].Sequence + } + return incoming[i].Name < incoming[j].Name + }) + + var added uint + for _, col := range incoming { + if existing, ok := baseTable.Columns[col.Name]; ok { + col.Sequence = existing.Sequence + } else { + added++ + col.Sequence = maxSeq + added + } + baseTable.Columns[col.Name] = col } // Merge constraints diff --git a/pkg/readers/dbml/reader_test.go b/pkg/readers/dbml/reader_test.go index fa45e05..be98ae4 100644 --- a/pkg/readers/dbml/reader_test.go +++ b/pkg/readers/dbml/reader_test.go @@ -652,6 +652,22 @@ func TestReadDirectory_TableMerging(t *testing.T) { if emailCol.Type != "varchar(255)" { t.Errorf("Expected email type 'varchar(255)', got '%s'", emailCol.Type) } + + // Merged columns must keep declaration order (file 1: id, email; file 3: + // name, created_at) via strictly increasing, non-colliding Sequence values + // so downstream writers do not fall back to alphabetical order. + order := []string{"id", "email", "name", "created_at"} + var prev uint + for i, name := range order { + col := usersTable.Columns[name] + if col.Sequence == 0 { + t.Fatalf("column %q has zero Sequence after merge", name) + } + if i > 0 && col.Sequence <= prev { + t.Errorf("column %q Sequence %d not greater than previous %d (order not preserved across files)", name, col.Sequence, prev) + } + prev = col.Sequence + } } func TestReadDirectory_CommentedRefsLast(t *testing.T) { diff --git a/pkg/writers/bun/template_data.go b/pkg/writers/bun/template_data.go index b5deca5..ac0c756 100644 --- a/pkg/writers/bun/template_data.go +++ b/pkg/writers/bun/template_data.go @@ -6,6 +6,7 @@ import ( "sort" "strings" + "git.warky.dev/wdevs/relspecgo/pkg/buildinfo" "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/writers" ) @@ -13,6 +14,7 @@ import ( // TemplateData represents the data passed to the template for code generation type TemplateData struct { PackageName string + GeneratedBy string Imports []string Models []*ModelData Config *MethodConfig @@ -165,6 +167,7 @@ func NewTemplateData(packageName string, config *MethodConfig) *TemplateData { return &TemplateData{ PackageName: packageName, + GeneratedBy: buildinfo.GeneratedComment(), Imports: make([]string, 0), Models: make([]*ModelData, 0), Config: config, diff --git a/pkg/writers/bun/templates.go b/pkg/writers/bun/templates.go index dbea7f6..0c2ba6e 100644 --- a/pkg/writers/bun/templates.go +++ b/pkg/writers/bun/templates.go @@ -7,7 +7,8 @@ import ( // modelTemplate defines the template for generating Bun models const modelTemplate = `// Code generated by relspecgo. DO NOT EDIT. -package {{.PackageName}} +{{if .GeneratedBy}}// {{.GeneratedBy}} +{{end}}package {{.PackageName}} {{if .Imports -}} import ( diff --git a/pkg/writers/bun/writer_test.go b/pkg/writers/bun/writer_test.go index 583b8bf..f7dec21 100644 --- a/pkg/writers/bun/writer_test.go +++ b/pkg/writers/bun/writer_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "git.warky.dev/wdevs/relspecgo/pkg/buildinfo" "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/writers" ) @@ -120,6 +121,42 @@ func TestWriter_WriteTable_MultilineDescriptionProducesValidGo(t *testing.T) { } } +func TestWriter_WriteTable_EmbedsRelspecVersionHeader(t *testing.T) { + table := models.InitTable("users", "public") + table.Columns["id"] = &models.Column{Name: "id", Type: "bigint", IsPrimaryKey: true, NotNull: true, Sequence: 1} + + outputPath := filepath.Join(t.TempDir(), "users.go") + writer := NewWriter(&writers.WriterOptions{OutputPath: outputPath, PackageName: "models"}) + if err := writer.WriteTable(table); err != nil { + t.Fatalf("WriteTable() error = %v", err) + } + + generated, err := os.ReadFile(outputPath) + if err != nil { + t.Fatalf("failed to read generated code: %v", err) + } + src := string(generated) + + if _, err := parser.ParseFile(token.NewFileSet(), outputPath, generated, parser.AllErrors); err != nil { + t.Fatalf("generated code is invalid Go: %v\n%s", err, src) + } + + wantHeader := "// " + buildinfo.GeneratedComment() + if !strings.Contains(src, wantHeader) { + t.Errorf("generated code missing RelSpec version header %q\n%s", wantHeader, src) + } + // The provenance comment must sit between the "Code generated" marker and the package clause. + genIdx := strings.Index(src, "// Code generated by relspecgo. DO NOT EDIT.") + hdrIdx := strings.Index(src, wantHeader) + pkgIdx := strings.Index(src, "package models") + if genIdx < 0 || hdrIdx < 0 || pkgIdx < 0 || !(genIdx < hdrIdx && hdrIdx < pkgIdx) { + t.Errorf("RelSpec version header is misplaced (gen=%d hdr=%d pkg=%d)\n%s", genIdx, hdrIdx, pkgIdx, src) + } + if !strings.Contains(wantHeader, "RelSpec ") || !strings.Contains(wantHeader, "(built: ") { + t.Errorf("version header not in expected 'RelSpec (built: )' form: %q", wantHeader) + } +} + func TestWriter_WriteDatabase_MultiFile(t *testing.T) { // Create a database with two tables db := models.InitDatabase("testdb") diff --git a/pkg/writers/drizzle/template_data.go b/pkg/writers/drizzle/template_data.go index 060e4cf..18f2804 100644 --- a/pkg/writers/drizzle/template_data.go +++ b/pkg/writers/drizzle/template_data.go @@ -4,14 +4,16 @@ import ( "sort" "strings" + "git.warky.dev/wdevs/relspecgo/pkg/buildinfo" "git.warky.dev/wdevs/relspecgo/pkg/models" ) // TemplateData represents the data passed to the template for code generation type TemplateData struct { - Imports []string - Enums []*EnumData - Tables []*TableData + GeneratedBy string + Imports []string + Enums []*EnumData + Tables []*TableData } // EnumData represents an enum in the schema @@ -59,7 +61,8 @@ type IndexData struct { // NewTemplateData creates a new TemplateData func NewTemplateData() *TemplateData { return &TemplateData{ - Imports: make([]string, 0), + GeneratedBy: buildinfo.GeneratedComment(), + Imports: make([]string, 0), Enums: make([]*EnumData, 0), Tables: make([]*TableData, 0), } diff --git a/pkg/writers/drizzle/templates.go b/pkg/writers/drizzle/templates.go index 6cc0c3a..9405936 100644 --- a/pkg/writers/drizzle/templates.go +++ b/pkg/writers/drizzle/templates.go @@ -7,7 +7,8 @@ import ( // schemaTemplate defines the template for generating Drizzle schemas const schemaTemplate = `// Code generated by relspecgo. DO NOT EDIT. -{{range .Imports}}{{.}} +{{if .GeneratedBy}}// {{.GeneratedBy}} +{{end}}{{range .Imports}}{{.}} {{end}} {{if .Enums}} // Enums diff --git a/pkg/writers/gorm/template_data.go b/pkg/writers/gorm/template_data.go index 6f6b9ef..444bfaa 100644 --- a/pkg/writers/gorm/template_data.go +++ b/pkg/writers/gorm/template_data.go @@ -4,6 +4,7 @@ import ( "sort" "strings" + "git.warky.dev/wdevs/relspecgo/pkg/buildinfo" "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/writers" ) @@ -11,6 +12,7 @@ import ( // TemplateData represents the data passed to the template for code generation type TemplateData struct { PackageName string + GeneratedBy string Imports []string Models []*ModelData Config *MethodConfig @@ -79,6 +81,7 @@ func NewTemplateData(packageName string, config *MethodConfig) *TemplateData { return &TemplateData{ PackageName: packageName, + GeneratedBy: buildinfo.GeneratedComment(), Imports: make([]string, 0), Models: make([]*ModelData, 0), Config: config, diff --git a/pkg/writers/gorm/templates.go b/pkg/writers/gorm/templates.go index b337d40..68c0cdd 100644 --- a/pkg/writers/gorm/templates.go +++ b/pkg/writers/gorm/templates.go @@ -7,7 +7,8 @@ import ( // modelTemplate defines the template for generating GORM models const modelTemplate = `// Code generated by relspecgo. DO NOT EDIT. -package {{.PackageName}} +{{if .GeneratedBy}}// {{.GeneratedBy}} +{{end}}package {{.PackageName}} {{if .Imports -}} import ( diff --git a/pkg/writers/gorm/writer_test.go b/pkg/writers/gorm/writer_test.go index 8122cf3..f459cff 100644 --- a/pkg/writers/gorm/writer_test.go +++ b/pkg/writers/gorm/writer_test.go @@ -112,6 +112,46 @@ func TestWriter_WriteTable_MultilineDescriptionProducesValidGo(t *testing.T) { } } +func TestWriter_WriteTable_ColumnOrderFollowsSequence(t *testing.T) { + // Column.Sequence carries the source (e.g. DBML) declaration order; the + // generated struct fields must follow it, not fall back to alphabetical. + table := models.InitTable("widget", "public") + table.Columns["zeta"] = &models.Column{Name: "zeta", Type: "varchar", Length: 50, Sequence: 1} + table.Columns["alpha"] = &models.Column{Name: "alpha", Type: "bigint", NotNull: true, IsPrimaryKey: true, AutoIncrement: true, Sequence: 2} + table.Columns["mid_field"] = &models.Column{Name: "mid_field", Type: "integer", Sequence: 3} + table.Columns["beta"] = &models.Column{Name: "beta", Type: "varchar", Length: 100, Sequence: 4} + + outputPath := filepath.Join(t.TempDir(), "widget.go") + writer := NewWriter(&writers.WriterOptions{OutputPath: outputPath, PackageName: "models"}) + if err := writer.WriteTable(table); err != nil { + t.Fatalf("WriteTable() error = %v", err) + } + + generated, err := os.ReadFile(outputPath) + if err != nil { + t.Fatalf("failed to read generated code: %v", err) + } + src := string(generated) + if _, err := parser.ParseFile(token.NewFileSet(), outputPath, generated, parser.AllErrors); err != nil { + t.Fatalf("generated code is invalid Go: %v\n%s", err, src) + } + + positions := make([]int, 0, 4) + for _, field := range []string{"Zeta ", "Alpha ", "MidField ", "Beta "} { + idx := strings.Index(src, "\t"+field) + if idx < 0 { + t.Fatalf("field %q missing from generated struct:\n%s", field, src) + } + positions = append(positions, idx) + } + for i := 1; i < len(positions); i++ { + if positions[i] <= positions[i-1] { + t.Errorf("struct fields not in Sequence order (want zeta, alpha, mid_field, beta):\n%s", src) + break + } + } +} + func TestWriter_WriteDatabase_MultiFile(t *testing.T) { // Create a database with two tables db := models.InitDatabase("testdb") diff --git a/pkg/writers/mssql/writer.go b/pkg/writers/mssql/writer.go index af39bf0..be691b8 100644 --- a/pkg/writers/mssql/writer.go +++ b/pkg/writers/mssql/writer.go @@ -11,6 +11,7 @@ import ( _ "github.com/microsoft/go-mssqldb" // MSSQL driver + "git.warky.dev/wdevs/relspecgo/pkg/buildinfo" "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/mssql" "git.warky.dev/wdevs/relspecgo/pkg/writers" @@ -68,7 +69,7 @@ func (w *Writer) WriteDatabase(db *models.Database) error { // Write header comment fmt.Fprintf(w.writer, "-- MSSQL Database Schema\n") fmt.Fprintf(w.writer, "-- Database: %s\n", db.Name) - fmt.Fprintf(w.writer, "-- Generated by RelSpec\n\n") + fmt.Fprintf(w.writer, "-- Generated by %s\n\n", buildinfo.GeneratedComment()) // Process each schema in the database for _, schema := range db.Schemas { @@ -477,7 +478,7 @@ func (w *Writer) executeDatabaseSQL(db *models.Database, connString string) erro statements := []string{} statements = append(statements, "-- MSSQL Database Schema") statements = append(statements, fmt.Sprintf("-- Database: %s", db.Name)) - statements = append(statements, "-- Generated by RelSpec") + statements = append(statements, "-- Generated by "+buildinfo.GeneratedComment()) for _, schema := range db.Schemas { if err := w.generateSchemaStatements(schema, &statements); err != nil { diff --git a/pkg/writers/pgsql/migration_writer.go b/pkg/writers/pgsql/migration_writer.go index 678ae73..77a1bde 100644 --- a/pkg/writers/pgsql/migration_writer.go +++ b/pkg/writers/pgsql/migration_writer.go @@ -7,6 +7,7 @@ import ( "sort" "strings" + "git.warky.dev/wdevs/relspecgo/pkg/buildinfo" "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/pgsql" "git.warky.dev/wdevs/relspecgo/pkg/writers" @@ -143,7 +144,7 @@ func (w *MigrationWriter) WriteMigration(model, current *models.Database) error // Write header fmt.Fprintf(w.writer, "-- PostgreSQL Migration Script\n") - fmt.Fprintf(w.writer, "-- Generated by RelSpec\n") + fmt.Fprintf(w.writer, "-- Generated by %s\n", buildinfo.GeneratedComment()) fmt.Fprintf(w.writer, "-- Source: %s -> %s\n", current.Name, model.Name) if w.options.ContinueOnError { fmt.Fprintf(w.writer, "\\set ON_ERROR_STOP off\n") diff --git a/pkg/writers/pgsql/writer.go b/pkg/writers/pgsql/writer.go index 93b1d9f..a184140 100644 --- a/pkg/writers/pgsql/writer.go +++ b/pkg/writers/pgsql/writer.go @@ -12,6 +12,7 @@ import ( "sync" "time" + "git.warky.dev/wdevs/relspecgo/pkg/buildinfo" "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/pgsql" "git.warky.dev/wdevs/relspecgo/pkg/writers" @@ -101,7 +102,7 @@ func (w *Writer) WriteDatabase(db *models.Database) error { // Write header comment fmt.Fprintf(w.writer, "-- PostgreSQL Database Schema\n") fmt.Fprintf(w.writer, "-- Database: %s\n", db.Name) - fmt.Fprintf(w.writer, "-- Generated by RelSpec\n") + fmt.Fprintf(w.writer, "-- Generated by %s\n", buildinfo.GeneratedComment()) if w.options.ContinueOnError { fmt.Fprintf(w.writer, "\\set ON_ERROR_STOP off\n") } @@ -125,7 +126,7 @@ func (w *Writer) GenerateDatabaseStatements(db *models.Database) ([]string, erro // Add header comment statements = append(statements, "-- PostgreSQL Database Schema") statements = append(statements, fmt.Sprintf("-- Database: %s", db.Name)) - statements = append(statements, "-- Generated by RelSpec") + statements = append(statements, "-- Generated by "+buildinfo.GeneratedComment()) // Process each schema in the database for _, schema := range db.Schemas { @@ -555,7 +556,7 @@ func (w *Writer) GenerateAddColumnsForDatabase(db *models.Database) ([]string, e statements = append(statements, "-- Add missing columns to existing tables") statements = append(statements, fmt.Sprintf("-- Database: %s", db.Name)) - statements = append(statements, "-- Generated by RelSpec") + statements = append(statements, "-- Generated by "+buildinfo.GeneratedComment()) for _, schema := range db.Schemas { schemaStatements, err := w.GenerateAddColumnStatements(schema) @@ -1446,19 +1447,10 @@ func (w *Writer) writeComments(schema *models.Schema) error { // Helper functions -// getSortedColumns returns columns sorted by name +// getSortedColumns returns columns sorted by Sequence then Name, preserving the +// original column order from the source schema for deterministic output. func getSortedColumns(columns map[string]*models.Column) []*models.Column { - names := make([]string, 0, len(columns)) - for name := range columns { - names = append(names, name) - } - sort.Strings(names) - - sorted := make([]*models.Column, 0, len(columns)) - for _, name := range names { - sorted = append(sorted, columns[name]) - } - return sorted + return sortColumns(columns) } // isIntegerType checks if a column type is an integer type diff --git a/pkg/writers/sqlite/writer.go b/pkg/writers/sqlite/writer.go index 3e93663..01b1c79 100644 --- a/pkg/writers/sqlite/writer.go +++ b/pkg/writers/sqlite/writer.go @@ -10,6 +10,7 @@ import ( _ "modernc.org/sqlite" // SQLite driver + "git.warky.dev/wdevs/relspecgo/pkg/buildinfo" "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/writers" ) @@ -72,7 +73,7 @@ func (w *Writer) writeContent(db *models.Database) error { // Write header comment fmt.Fprintf(w.writer, "-- SQLite Database Schema\n") fmt.Fprintf(w.writer, "-- Database: %s\n", db.Name) - fmt.Fprintf(w.writer, "-- Generated by RelSpec\n") + fmt.Fprintf(w.writer, "-- Generated by %s\n", buildinfo.GeneratedComment()) fmt.Fprintf(w.writer, "-- Note: SQLite has no schema concept; non-default schema names are flattened into table name prefixes (e.g., auth.sessions -> auth_sessions)\n\n") // Enable foreign keys From ca226e83df3e44d8c745fe3a6436819d3f85cd13 Mon Sep 17 00:00:00 2001 From: Hein Date: Thu, 10 Sep 2026 22:21:40 +0200 Subject: [PATCH 2/2] feat(release): update release process to include linting and testing --- Makefile | 2 +- cmd/relspec/main.go | 2 ++ cmd/relspec/root.go | 3 ++- cmd/relspec/version.go | 7 +++++-- pkg/buildinfo/buildinfo.go | 16 ++++++++++++++++ pkg/writers/drizzle/template_data.go | 4 ++-- 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 2cb19fa..96725b3 100644 --- a/Makefile +++ b/Makefile @@ -207,7 +207,7 @@ docker-test-integration: docker-up ## Start DB and run integration tests $(GOTEST) -v ./pkg/readers/pgsql/ -count=1 || (make docker-down && exit 1) @make docker-down -release: ## Create and push a new release tag (auto-increments patch version) +release: lint fmt-check test build ## Run lint, format check, tests, build, then create and push a new release tag @echo "Creating new release..." @latest_tag=$$(git describe --tags --abbrev=0 2>/dev/null || echo ""); \ if [ -z "$$latest_tag" ]; then \ diff --git a/cmd/relspec/main.go b/cmd/relspec/main.go index 92b0cac..794c5b9 100644 --- a/cmd/relspec/main.go +++ b/cmd/relspec/main.go @@ -5,6 +5,8 @@ import ( "os" ) +// asciiLogo (see version.go) is printed by the `version` command. + func main() { args := os.Args[1:] isSilent := hasSilentFlag(args) diff --git a/cmd/relspec/root.go b/cmd/relspec/root.go index 16f7673..13de477 100644 --- a/cmd/relspec/root.go +++ b/cmd/relspec/root.go @@ -3,8 +3,9 @@ package main import ( "fmt" - "git.warky.dev/wdevs/relspecgo/pkg/buildinfo" "github.com/spf13/cobra" + + "git.warky.dev/wdevs/relspecgo/pkg/buildinfo" ) // version/buildDate mirror pkg/buildinfo so existing call sites keep working. diff --git a/cmd/relspec/version.go b/cmd/relspec/version.go index 0ef38f8..606a6b4 100644 --- a/cmd/relspec/version.go +++ b/cmd/relspec/version.go @@ -4,13 +4,16 @@ import ( "fmt" "github.com/spf13/cobra" + + "git.warky.dev/wdevs/relspecgo/pkg/buildinfo" ) var versionCmd = &cobra.Command{ Use: "version", Short: "Print version information", Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("RelSpec %s\n", version) - fmt.Printf("Built: %s\n", buildDate) + fmt.Print(buildinfo.AsciiLogo) + fmt.Printf("RelSpec %s\n", buildinfo.Version) + fmt.Printf("Built: %s\n", buildinfo.BuildDate) }, } diff --git a/pkg/buildinfo/buildinfo.go b/pkg/buildinfo/buildinfo.go index d556c08..47b1da0 100644 --- a/pkg/buildinfo/buildinfo.go +++ b/pkg/buildinfo/buildinfo.go @@ -47,3 +47,19 @@ func init() { func GeneratedComment() string { return fmt.Sprintf("RelSpec %s (built: %s)", Version, BuildDate) } + +const AsciiLogo = ` +██████╗ ███████╗██╗ ███████╗██████╗ ███████╗ ██████╗ +██╔══██╗██╔════╝██║ ██╔════╝██╔══██╗██╔════╝██╔════╝ +██████╔╝█████╗ ██║ ███████╗██████╔╝█████╗ ██║ +██╔══██╗██╔══╝ ██║ ╚════██║██╔═══╝ ██╔══╝ ██║ +██║ ██║███████╗███████╗███████║██║ ███████╗╚██████╗ +╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚══════╝ ╚═════╝ +[ IN ] ──▶ [ RELSPEC ] ──▶ [ OUT ] +╔══════════════════════════════════════╗ +║ ║ +║ © WARKY DEVS ║ +║ Author: Hein (hein@warky.dev) ║ +║ ║ +╚══════════════════════════════════════╝ +` diff --git a/pkg/writers/drizzle/template_data.go b/pkg/writers/drizzle/template_data.go index 18f2804..4468c38 100644 --- a/pkg/writers/drizzle/template_data.go +++ b/pkg/writers/drizzle/template_data.go @@ -63,8 +63,8 @@ func NewTemplateData() *TemplateData { return &TemplateData{ GeneratedBy: buildinfo.GeneratedComment(), Imports: make([]string, 0), - Enums: make([]*EnumData, 0), - Tables: make([]*TableData, 0), + Enums: make([]*EnumData, 0), + Tables: make([]*TableData, 0), } }