diff --git a/README.md b/README.md index ebe4b08..5e7309a 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,13 @@ func downCreateUsersTable(c schema.Context) error { ### Running Migrations -Create a CLI tool to manage migrations within your Go application: +For a complete CLI setup example, see [examples/basic](examples/basic/). For quick setup, use the CLI helpers below. + +### CLI Helpers + +For simpler CLI integration, use the pre-built CLI helpers: + +#### Using urfave/cli ```go package main @@ -68,131 +74,66 @@ import ( "log" "os" - "github.com/akfaiz/migris" - _ "github.com/akfaiz/migris/examples/basic/migrations" // Import migrations directory + "github.com/akfaiz/migris/extra/migriscli" _ "github.com/jackc/pgx/v5/stdlib" - "github.com/joho/godotenv" - "github.com/urfave/cli/v3" ) -const migrationDir = "migrations" - -func loadDatabaseURL() string { - err := godotenv.Load() +func main() { + db, err := sql.Open("pgx", os.Getenv("DATABASE_URL")) if err != nil { - log.Fatal("Error loading .env file") - } - databaseURL := os.Getenv("DATABASE_URL") - if databaseURL == "" { - log.Fatal("DATABASE_URL is not set in the environment") + log.Fatal(err) } - return databaseURL -} + defer db.Close() -func createMigrator(dryRun bool) (*migris.Migrate, error) { - databaseURL := loadDatabaseURL() - db, err := sql.Open("pgx", databaseURL) - if err != nil { - return nil, err + cfg := migriscli.Config{ + DB: db, + Dialect: "pgx", + MigrationsDir: "./migrations", } - options := []migris.Option{ - migris.WithDB(db), - migris.WithMigrationDir(migrationDir), + cmd := migriscli.NewCLI(cfg) + if err := cmd.Run(context.Background(), os.Args); err != nil { + log.Fatal(err) } +} +``` - if dryRun { - options = append(options, migris.WithDryRun()) - } +#### Using Cobra - return migris.New("pgx", options...) -} +```go +package main + +import ( + "database/sql" + "log" + "os" + + "github.com/akfaiz/migris/extra/migriscobra" + _ "github.com/jackc/pgx/v5/stdlib" +) func main() { + db, err := sql.Open("pgx", os.Getenv("DATABASE_URL")) + if err != nil { + log.Fatal(err) + } + defer db.Close() - cmd := &cli.Command{ - Name: "migrate", - Usage: "Migration tool", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "dry-run", - Aliases: []string{"d"}, - Usage: "Run migrations in dry-run mode (print SQL without executing)", - }, - }, - Commands: []*cli.Command{ - { - Name: "create", - Usage: "Create a new migration file", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "name", - Aliases: []string{"n"}, - Usage: "Name of the migration", - Required: true, - }, - }, - Action: func(ctx context.Context, c *cli.Command) error { - migrator, err := createMigrator(false) - if err != nil { - return err - } - return migrator.Create(c.String("name")) - }, - }, - { - Name: "up", - Usage: "Run all pending migrations", - Action: func(ctx context.Context, c *cli.Command) error { - migrator, err := createMigrator(c.Bool("dry-run")) - if err != nil { - return err - } - return migrator.UpContext(ctx) - }, - }, - { - Name: "reset", - Usage: "Rollback all migrations", - Action: func(ctx context.Context, c *cli.Command) error { - migrator, err := createMigrator(c.Bool("dry-run")) - if err != nil { - return err - } - return migrator.ResetContext(ctx) - }, - }, - { - Name: "down", - Usage: "Rollback the last migration", - Action: func(ctx context.Context, c *cli.Command) error { - migrator, err := createMigrator(c.Bool("dry-run")) - if err != nil { - return err - } - return migrator.DownContext(ctx) - }, - }, - { - Name: "status", - Usage: "Show the status of migrations", - Action: func(ctx context.Context, c *cli.Command) error { - migrator, err := createMigrator(false) - if err != nil { - return err - } - return migrator.StatusContext(ctx) - }, - }, - }, + cfg := migriscobra.Config{ + DB: db, + Dialect: "pgx", + MigrationsDir: "./migrations", } - if err := cmd.Run(context.Background(), os.Args); err != nil { - log.Printf("Error running app: %v\n", err) - os.Exit(1) + + cmd := migriscobra.NewCLI(cfg) + if err := cmd.Execute(); err != nil { + log.Fatal(err) } } ``` +Both CLI helpers support all migration commands: `create`, `up`, `up-to`, `down`, `down-to`, `reset`, `status` with `--dry-run` support. + ## Schema Builder API The schema builder provides a fluent interface for defining database schemas: @@ -253,24 +194,6 @@ Dry-run mode shows: - Execution timing and summary statistics - Clear indication that no database changes are made -## Dry-Run Mode - -Enable dry-run mode to preview migrations without executing them: - -```go -migrator, err := migris.New("pgx", - migris.WithDB(db), - migris.WithMigrationDir(migrationDir), - migris.WithDryRun(true), // Enable dry-run mode -) -``` - -Dry-run mode automatically displays: - -- Migration progress and status -- All generated SQL statements -- Summary of pending migrations - ## Database Support Currently supported databases: diff --git a/examples/migriscli/go.mod b/examples/migriscli/go.mod new file mode 100644 index 0000000..d9ac7d2 --- /dev/null +++ b/examples/migriscli/go.mod @@ -0,0 +1,32 @@ +module github.com/akfaiz/migris/examples/migriscli + +go 1.24.0 + +require ( + github.com/akfaiz/migris v0.4.0 + github.com/akfaiz/migris/extra/migriscli v0.0.0 + github.com/jackc/pgx/v5 v5.8.0 + github.com/joho/godotenv v1.5.1 +) + +require ( + github.com/fatih/color v1.18.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mfridman/interpolate v0.0.2 // indirect + github.com/pressly/goose/v3 v3.26.0 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/urfave/cli/v3 v3.6.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect +) + +replace github.com/akfaiz/migris => ../.. + +replace github.com/akfaiz/migris/extra/migriscli => ../../extra/migriscli diff --git a/examples/migriscli/go.sum b/examples/migriscli/go.sum new file mode 100644 index 0000000..c142b9c --- /dev/null +++ b/examples/migriscli/go.sum @@ -0,0 +1,71 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= +github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= +github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM= +github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= +github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo= +github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= +modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= +modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= diff --git a/examples/migriscli/main.go b/examples/migriscli/main.go new file mode 100644 index 0000000..a3715d4 --- /dev/null +++ b/examples/migriscli/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "context" + "database/sql" + "log" + "os" + + _ "github.com/akfaiz/migris/examples/migriscli/migrations" // Import migrations directory + "github.com/akfaiz/migris/extra/migriscli" + _ "github.com/jackc/pgx/v5/stdlib" + "github.com/joho/godotenv" +) + +const migrationDir = "migrations" + +func loadDatabaseURL() string { + err := godotenv.Load() + if err != nil { + log.Fatal("Error loading .env file") + } + databaseURL := os.Getenv("DATABASE_URL") + if databaseURL == "" { + log.Fatal("DATABASE_URL is not set in the environment") + } + return databaseURL +} + +func main() { + databaseURL := loadDatabaseURL() + db, err := sql.Open("pgx", databaseURL) + if err != nil { + log.Fatalf("Failed to open database: %v", err) + } + defer db.Close() + + cmd := migriscli.NewCLI(migriscli.Config{ + DB: db, + Dialect: "pgx", + MigrationsDir: migrationDir, + }) + err = cmd.Run(context.Background(), os.Args) + if err != nil { + log.Fatalf("Command failed: %v", err) + } +} diff --git a/examples/migriscli/migrations/20250904164848_create_users_table.go b/examples/migriscli/migrations/20250904164848_create_users_table.go new file mode 100644 index 0000000..3ac0e79 --- /dev/null +++ b/examples/migriscli/migrations/20250904164848_create_users_table.go @@ -0,0 +1,23 @@ +package migrations + +import ( + "github.com/akfaiz/migris" + "github.com/akfaiz/migris/schema" +) + +func init() { + migris.AddMigrationContext(upCreateUsersTable, downCreateUsersTable) +} + +func upCreateUsersTable(c schema.Context) error { + return schema.Create(c, "users", func(table *schema.Blueprint) { + table.Increments("id").Primary() + table.String("username") + table.String("email").Unique() + table.Timestamp("created_at").UseCurrent() + }) +} + +func downCreateUsersTable(c schema.Context) error { + return schema.DropIfExists(c, "users") +} diff --git a/examples/migriscli/migrations/20250904165150_create_posts_table.go b/examples/migriscli/migrations/20250904165150_create_posts_table.go new file mode 100644 index 0000000..b2cf69f --- /dev/null +++ b/examples/migriscli/migrations/20250904165150_create_posts_table.go @@ -0,0 +1,25 @@ +package migrations + +import ( + "github.com/akfaiz/migris" + "github.com/akfaiz/migris/schema" +) + +func init() { + migris.AddMigrationContext(upCreatePostsTable, downCreatePostsTable) +} + +func upCreatePostsTable(c schema.Context) error { + return schema.Create(c, "posts", func(table *schema.Blueprint) { + table.Increments("id").Primary() + table.String("title") + table.Text("content") + table.Integer("author_id").Unsigned() + table.Timestamp("created_at").UseCurrent() + table.Foreign("author_id").References("id").On("users") + }) +} + +func downCreatePostsTable(c schema.Context) error { + return schema.DropIfExists(c, "posts") +} diff --git a/examples/migriscli/migrations/20250904165317_create_comments_table.go b/examples/migriscli/migrations/20250904165317_create_comments_table.go new file mode 100644 index 0000000..1cdaf31 --- /dev/null +++ b/examples/migriscli/migrations/20250904165317_create_comments_table.go @@ -0,0 +1,26 @@ +package migrations + +import ( + "github.com/akfaiz/migris" + "github.com/akfaiz/migris/schema" +) + +func init() { + migris.AddMigrationContext(upCreateCommentsTable, downCreateCommentsTable) +} + +func upCreateCommentsTable(c schema.Context) error { + return schema.Create(c, "comments", func(table *schema.Blueprint) { + table.Increments("id").Primary() + table.Integer("post_id").Unsigned() + table.Integer("user_id").Unsigned() + table.Text("content") + table.Timestamp("created_at").UseCurrent() + table.Foreign("post_id").References("id").On("posts") + table.Foreign("user_id").References("id").On("users") + }) +} + +func downCreateCommentsTable(c schema.Context) error { + return schema.DropIfExists(c, "comments") +} diff --git a/examples/migriscli/migrations/20250904165421_insert_data.go b/examples/migriscli/migrations/20250904165421_insert_data.go new file mode 100644 index 0000000..f2c03ac --- /dev/null +++ b/examples/migriscli/migrations/20250904165421_insert_data.go @@ -0,0 +1,52 @@ +package migrations + +import ( + "github.com/akfaiz/migris" + "github.com/akfaiz/migris/schema" +) + +func init() { + migris.AddMigrationContext(upInsertData, downInsertData) +} + +func upInsertData(c schema.Context) error { + sqls := []string{ + `INSERT INTO users (id, username, email) +VALUES + (1, 'john_doe', 'john@example.com'), + (2, 'jane_smith', 'jane@example.com'), + (3, 'alice_wonderland', 'alice@example.com')`, + `INSERT INTO posts (id, title, content, author_id) +VALUES + (1, 'Introduction to SQL', 'SQL is a powerful language for managing databases...', 1), + (2, 'Data Modeling Techniques', 'Choosing the right data model is crucial...', 2), + (3, 'Advanced Query Optimization', 'Optimizing queries can greatly improve...', 1)`, + `INSERT INTO comments (id, post_id, user_id, content) +VALUES + (1, 1, 3, 'Great introduction! Looking forward to more.'), + (2, 1, 2, 'SQL can be a bit tricky at first, but practice helps.'), + (3, 2, 1, 'You covered normalization really well in this post.')`, + } + for _, sql := range sqls { + _, err := c.Exec(sql) + if err != nil { + return err + } + } + return nil +} + +func downInsertData(c schema.Context) error { + sqls := []string{ + "DELETE FROM comments", + "DELETE FROM posts", + "DELETE FROM users", + } + for _, sql := range sqls { + _, err := c.Exec(sql) + if err != nil { + return err + } + } + return nil +} diff --git a/examples/migriscobra/go.mod b/examples/migriscobra/go.mod new file mode 100644 index 0000000..c8376ab --- /dev/null +++ b/examples/migriscobra/go.mod @@ -0,0 +1,34 @@ +module github.com/akfaiz/migris/examples/migriscobra + +go 1.24.0 + +require ( + github.com/akfaiz/migris v0.4.0 + github.com/akfaiz/migris/extra/migriscobra v0.0.0 + github.com/jackc/pgx/v5 v5.8.0 + github.com/joho/godotenv v1.5.1 +) + +require ( + github.com/fatih/color v1.18.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mfridman/interpolate v0.0.2 // indirect + github.com/pressly/goose/v3 v3.26.0 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect +) + +replace github.com/akfaiz/migris => ../.. + +replace github.com/akfaiz/migris/extra/migriscobra => ../../extra/migriscobra diff --git a/examples/migriscobra/go.sum b/examples/migriscobra/go.sum new file mode 100644 index 0000000..07579e9 --- /dev/null +++ b/examples/migriscobra/go.sum @@ -0,0 +1,77 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= +github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= +github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM= +github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= +github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= +modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= +modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= diff --git a/examples/migriscobra/main.go b/examples/migriscobra/main.go new file mode 100644 index 0000000..f365790 --- /dev/null +++ b/examples/migriscobra/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "database/sql" + "log" + "os" + + _ "github.com/akfaiz/migris/examples/migriscobra/migrations" // Import migrations directory + "github.com/akfaiz/migris/extra/migriscobra" + _ "github.com/jackc/pgx/v5/stdlib" + "github.com/joho/godotenv" +) + +const migrationDir = "migrations" + +func loadDatabaseURL() string { + err := godotenv.Load() + if err != nil { + log.Fatal("Error loading .env file") + } + databaseURL := os.Getenv("DATABASE_URL") + if databaseURL == "" { + log.Fatal("DATABASE_URL is not set in the environment") + } + return databaseURL +} + +func main() { + databaseURL := loadDatabaseURL() + db, err := sql.Open("pgx", databaseURL) + if err != nil { + log.Fatalf("Failed to open database: %v", err) + } + defer db.Close() + + cmd := migriscobra.NewCLI(migriscobra.Config{ + DB: db, + Dialect: "pgx", + MigrationsDir: migrationDir, + }) + err = cmd.Execute() +} diff --git a/examples/migriscobra/migrations/20250904164848_create_users_table.go b/examples/migriscobra/migrations/20250904164848_create_users_table.go new file mode 100644 index 0000000..3ac0e79 --- /dev/null +++ b/examples/migriscobra/migrations/20250904164848_create_users_table.go @@ -0,0 +1,23 @@ +package migrations + +import ( + "github.com/akfaiz/migris" + "github.com/akfaiz/migris/schema" +) + +func init() { + migris.AddMigrationContext(upCreateUsersTable, downCreateUsersTable) +} + +func upCreateUsersTable(c schema.Context) error { + return schema.Create(c, "users", func(table *schema.Blueprint) { + table.Increments("id").Primary() + table.String("username") + table.String("email").Unique() + table.Timestamp("created_at").UseCurrent() + }) +} + +func downCreateUsersTable(c schema.Context) error { + return schema.DropIfExists(c, "users") +} diff --git a/examples/migriscobra/migrations/20250904165150_create_posts_table.go b/examples/migriscobra/migrations/20250904165150_create_posts_table.go new file mode 100644 index 0000000..b2cf69f --- /dev/null +++ b/examples/migriscobra/migrations/20250904165150_create_posts_table.go @@ -0,0 +1,25 @@ +package migrations + +import ( + "github.com/akfaiz/migris" + "github.com/akfaiz/migris/schema" +) + +func init() { + migris.AddMigrationContext(upCreatePostsTable, downCreatePostsTable) +} + +func upCreatePostsTable(c schema.Context) error { + return schema.Create(c, "posts", func(table *schema.Blueprint) { + table.Increments("id").Primary() + table.String("title") + table.Text("content") + table.Integer("author_id").Unsigned() + table.Timestamp("created_at").UseCurrent() + table.Foreign("author_id").References("id").On("users") + }) +} + +func downCreatePostsTable(c schema.Context) error { + return schema.DropIfExists(c, "posts") +} diff --git a/examples/migriscobra/migrations/20250904165317_create_comments_table.go b/examples/migriscobra/migrations/20250904165317_create_comments_table.go new file mode 100644 index 0000000..1cdaf31 --- /dev/null +++ b/examples/migriscobra/migrations/20250904165317_create_comments_table.go @@ -0,0 +1,26 @@ +package migrations + +import ( + "github.com/akfaiz/migris" + "github.com/akfaiz/migris/schema" +) + +func init() { + migris.AddMigrationContext(upCreateCommentsTable, downCreateCommentsTable) +} + +func upCreateCommentsTable(c schema.Context) error { + return schema.Create(c, "comments", func(table *schema.Blueprint) { + table.Increments("id").Primary() + table.Integer("post_id").Unsigned() + table.Integer("user_id").Unsigned() + table.Text("content") + table.Timestamp("created_at").UseCurrent() + table.Foreign("post_id").References("id").On("posts") + table.Foreign("user_id").References("id").On("users") + }) +} + +func downCreateCommentsTable(c schema.Context) error { + return schema.DropIfExists(c, "comments") +} diff --git a/examples/migriscobra/migrations/20250904165421_insert_data.go b/examples/migriscobra/migrations/20250904165421_insert_data.go new file mode 100644 index 0000000..f2c03ac --- /dev/null +++ b/examples/migriscobra/migrations/20250904165421_insert_data.go @@ -0,0 +1,52 @@ +package migrations + +import ( + "github.com/akfaiz/migris" + "github.com/akfaiz/migris/schema" +) + +func init() { + migris.AddMigrationContext(upInsertData, downInsertData) +} + +func upInsertData(c schema.Context) error { + sqls := []string{ + `INSERT INTO users (id, username, email) +VALUES + (1, 'john_doe', 'john@example.com'), + (2, 'jane_smith', 'jane@example.com'), + (3, 'alice_wonderland', 'alice@example.com')`, + `INSERT INTO posts (id, title, content, author_id) +VALUES + (1, 'Introduction to SQL', 'SQL is a powerful language for managing databases...', 1), + (2, 'Data Modeling Techniques', 'Choosing the right data model is crucial...', 2), + (3, 'Advanced Query Optimization', 'Optimizing queries can greatly improve...', 1)`, + `INSERT INTO comments (id, post_id, user_id, content) +VALUES + (1, 1, 3, 'Great introduction! Looking forward to more.'), + (2, 1, 2, 'SQL can be a bit tricky at first, but practice helps.'), + (3, 2, 1, 'You covered normalization really well in this post.')`, + } + for _, sql := range sqls { + _, err := c.Exec(sql) + if err != nil { + return err + } + } + return nil +} + +func downInsertData(c schema.Context) error { + sqls := []string{ + "DELETE FROM comments", + "DELETE FROM posts", + "DELETE FROM users", + } + for _, sql := range sqls { + _, err := c.Exec(sql) + if err != nil { + return err + } + } + return nil +} diff --git a/extra/migriscli/README.md b/extra/migriscli/README.md new file mode 100644 index 0000000..24d2daa --- /dev/null +++ b/extra/migriscli/README.md @@ -0,0 +1,66 @@ +# Migris CLI Helper + +A simple CLI interface for the migris database migration tool using [urfave/cli/v3](https://github.com/urfave/cli). + +## Installation + +```bash +go get github.com/akfaiz/migris/extra/migriscli +``` + +## Usage + +```go +package main + +import ( + "context" + "database/sql" + "log" + "os" + + "github.com/akfaiz/migris/extra/migriscli" + _ "github.com/jackc/pgx/v5/stdlib" +) + +func main() { + db, err := sql.Open("pgx", "postgres://user:pass@localhost/mydb") + if err != nil { + log.Fatal(err) + } + defer db.Close() + + cfg := migriscli.Config{ + DB: db, + Dialect: "pgx", + MigrationsDir: "./migrations", + } + + cmd := migriscli.NewCLI(cfg) + if err := cmd.Run(context.Background(), os.Args); err != nil { + log.Fatal(err) + } +} +``` + +## Commands + +- `create --name ` - Create a new migration file +- `up` - Apply all pending migrations +- `up-to --version ` - Apply migrations up to specific version +- `down` - Rollback the last migration +- `down-to --version ` - Rollback to specific version +- `reset` - Rollback all migrations +- `status` - Show migration status + +All migration commands support `--dry-run` to preview changes without executing them. + +## Configuration + +```go +type Config struct { + DB *sql.DB // Database connection + Dialect string // "pgx", "mysql", or "maria" + MigrationsDir string // Migration files directory +} +``` diff --git a/extra/migriscli/go.mod b/extra/migriscli/go.mod new file mode 100644 index 0000000..04e2b8c --- /dev/null +++ b/extra/migriscli/go.mod @@ -0,0 +1,23 @@ +module github.com/akfaiz/migris/extra/migriscli + +go 1.24.0 + +require ( + github.com/akfaiz/migris v0.4.0 + github.com/urfave/cli/v3 v3.6.1 +) + +require ( + github.com/fatih/color v1.18.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mfridman/interpolate v0.0.2 // indirect + github.com/pressly/goose/v3 v3.26.0 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect +) + +replace github.com/akfaiz/migris => ../.. diff --git a/extra/migriscli/go.sum b/extra/migriscli/go.sum new file mode 100644 index 0000000..934604e --- /dev/null +++ b/extra/migriscli/go.sum @@ -0,0 +1,31 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= +github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM= +github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY= +github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= +github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo= +github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/extra/migriscli/migriscli.go b/extra/migriscli/migriscli.go new file mode 100644 index 0000000..41f9004 --- /dev/null +++ b/extra/migriscli/migriscli.go @@ -0,0 +1,169 @@ +package migriscli + +import ( + "context" + "database/sql" + + "github.com/akfaiz/migris" + "github.com/urfave/cli/v3" +) + +// Config holds the configuration for the migris CLI commands. +type Config struct { + DB *sql.DB // Database connection + Dialect string // Database dialect (e.g., "pgx", "mysql", etc.) + MigrationsDir string // Directory where migration files are stored +} + +// NewCLI creates a new CLI interface for migris with subcommands. +func NewCLI(cfg Config) *cli.Command { + cmd := &cli.Command{ + Name: "migrate", + Usage: "Database migration CLI tool", + Commands: []*cli.Command{ + { + Name: "create", + Usage: "Create a new migration file", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "name", + Aliases: []string{"n"}, + Usage: "Name of the migration", + Required: true, + }, + }, + Action: func(ctx context.Context, c *cli.Command) error { + return migris.Create(cfg.MigrationsDir, c.String("name")) + }, + }, + { + Name: "up", + Usage: "Apply all up migrations", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "dry-run", + Usage: "Simulate the migration without applying changes", + }, + }, + Action: func(ctx context.Context, c *cli.Command) error { + migrator, err := createMigrator(c, cfg.DB, cfg) + if err != nil { + return err + } + return migrator.UpContext(ctx) + }, + }, + { + Name: "up-to", + Usage: "Apply migrations up to a specific version", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "dry-run", + Usage: "Simulate the migration without applying changes", + }, + &cli.Int64Flag{ + Name: "version", + Aliases: []string{"v"}, + Usage: "Target version to migrate up to", + Required: true, + }, + }, + Action: func(ctx context.Context, c *cli.Command) error { + migrator, err := createMigrator(c, cfg.DB, cfg) + if err != nil { + return err + } + return migrator.UpToContext(ctx, c.Int64("version")) + }, + }, + { + Name: "down", + Usage: "Rollback the last migration", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "dry-run", + Usage: "Simulate the migration without applying changes", + }, + }, + Action: func(ctx context.Context, c *cli.Command) error { + migrator, err := createMigrator(c, cfg.DB, cfg) + if err != nil { + return err + } + return migrator.DownContext(ctx) + }, + }, + { + Name: "down-to", + Usage: "Rollback migrations down to a specific version", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "dry-run", + Usage: "Simulate the migration without applying changes", + }, + &cli.Int64Flag{ + Name: "version", + Aliases: []string{"v"}, + Usage: "Target version to migrate down to", + Required: true, + }, + }, + Action: func(ctx context.Context, c *cli.Command) error { + migrator, err := createMigrator(c, cfg.DB, cfg) + if err != nil { + return err + } + return migrator.DownToContext(ctx, c.Int64("version")) + }, + }, + { + Name: "reset", + Usage: "Rollback all migrations", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "dry-run", + Usage: "Simulate the migration without applying changes", + }, + }, + Action: func(ctx context.Context, c *cli.Command) error { + migrator, err := createMigrator(c, cfg.DB, cfg) + if err != nil { + return err + } + return migrator.ResetContext(ctx) + }, + }, + { + Name: "status", + Usage: "Show the status of migrations", + Action: func(ctx context.Context, c *cli.Command) error { + migrator, err := createMigrator(c, cfg.DB, cfg) + if err != nil { + return err + } + return migrator.StatusContext(ctx) + }, + }, + }, + } + + return cmd +} + +func createMigrator(c *cli.Command, db *sql.DB, cfg Config) (*migris.Migrate, error) { + options := []migris.Option{ + migris.WithDB(db), + migris.WithMigrationDir(cfg.MigrationsDir), + } + + if c.Bool("dry-run") { + options = append(options, migris.WithDryRun(true)) + } + + migrator, err := migris.New(cfg.Dialect, options...) + if err != nil { + return nil, err + } + + return migrator, nil +} diff --git a/extra/migriscobra/README.md b/extra/migriscobra/README.md new file mode 100644 index 0000000..99fc600 --- /dev/null +++ b/extra/migriscobra/README.md @@ -0,0 +1,64 @@ +# Migris Cobra CLI Helper + +A CLI interface for the migris database migration tool using [Cobra](https://github.com/spf13/cobra). + +## Installation + +```bash +go get github.com/akfaiz/migris/extra/migriscobra +``` + +## Usage + +```go +package main + +import ( + "database/sql" + "log" + + "github.com/akfaiz/migris/extra/migriscobra" + _ "github.com/jackc/pgx/v5/stdlib" +) + +func main() { + db, err := sql.Open("pgx", "postgres://user:pass@localhost/mydb") + if err != nil { + log.Fatal(err) + } + defer db.Close() + + cfg := migriscobra.Config{ + DB: db, + Dialect: "pgx", + MigrationsDir: "./migrations", + } + + cmd := migriscobra.NewCLI(cfg) + if err := cmd.Execute(); err != nil { + log.Fatal(err) + } +} +``` + +## Commands + +- `create --name ` - Create a new migration file +- `up` - Apply all pending migrations +- `up-to --version ` - Apply migrations up to specific version +- `down` - Rollback the last migration +- `down-to --version ` - Rollback to specific version +- `reset` - Rollback all migrations +- `status` - Show migration status + +All migration commands support `--dry-run` to preview changes without executing them. + +## Configuration + +```go +type Config struct { + DB *sql.DB // Database connection + Dialect string // "pgx", "mysql", or "maria" + MigrationsDir string // Migration files directory +} +``` diff --git a/extra/migriscobra/go.mod b/extra/migriscobra/go.mod new file mode 100644 index 0000000..566aa60 --- /dev/null +++ b/extra/migriscobra/go.mod @@ -0,0 +1,25 @@ +module github.com/akfaiz/migris/extra/migriscobra + +go 1.24.0 + +require ( + github.com/akfaiz/migris v0.4.0 + github.com/spf13/cobra v1.8.1 +) + +require ( + github.com/fatih/color v1.18.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mfridman/interpolate v0.0.2 // indirect + github.com/pressly/goose/v3 v3.26.0 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect +) + +replace github.com/akfaiz/migris => ../.. diff --git a/extra/migriscobra/go.sum b/extra/migriscobra/go.sum new file mode 100644 index 0000000..f157642 --- /dev/null +++ b/extra/migriscobra/go.sum @@ -0,0 +1,72 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= +github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= +github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM= +github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= +github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= +modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= +modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= diff --git a/extra/migriscobra/migriscobra.go b/extra/migriscobra/migriscobra.go new file mode 100644 index 0000000..00f89d6 --- /dev/null +++ b/extra/migriscobra/migriscobra.go @@ -0,0 +1,174 @@ +package migriscobra + +import ( + "context" + "database/sql" + + "github.com/akfaiz/migris" + "github.com/spf13/cobra" +) + +// Config holds the configuration for the migris CLI commands. +type Config struct { + DB *sql.DB // Database connection + Dialect string // Database dialect (e.g., "pgx", "mysql", etc.) + MigrationsDir string // Directory where migration files are stored +} + +// NewCLI creates a new CLI interface for migris with subcommands using Cobra. +func NewCLI(cfg Config) *cobra.Command { + rootCmd := &cobra.Command{ + Use: "migrate", + Short: "Database migration CLI tool", + Long: "A powerful database migration tool powered by migris", + } + + // Add subcommands + rootCmd.AddCommand( + createCreateCommand(cfg), + createUpCommand(cfg), + createUpToCommand(cfg), + createDownCommand(cfg), + createDownToCommand(cfg), + createResetCommand(cfg), + createStatusCommand(cfg), + ) + + return rootCmd +} + +func createCreateCommand(cfg Config) *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + Short: "Create a new migration file", + RunE: func(cmd *cobra.Command, args []string) error { + name, _ := cmd.Flags().GetString("name") + if name == "" { + return cmd.Help() + } + return migris.Create(cfg.MigrationsDir, name) + }, + } + cmd.Flags().StringP("name", "n", "", "Name of the migration (required)") + cmd.MarkFlagRequired("name") + return cmd +} + +func createUpCommand(cfg Config) *cobra.Command { + cmd := &cobra.Command{ + Use: "up", + Short: "Apply all up migrations", + RunE: func(cmd *cobra.Command, args []string) error { + migrator, err := createMigrator(cmd, cfg) + if err != nil { + return err + } + return migrator.UpContext(context.Background()) + }, + } + cmd.Flags().Bool("dry-run", false, "Simulate the migration without applying changes") + return cmd +} + +func createUpToCommand(cfg Config) *cobra.Command { + cmd := &cobra.Command{ + Use: "up-to", + Short: "Apply migrations up to a specific version", + RunE: func(cmd *cobra.Command, args []string) error { + version, _ := cmd.Flags().GetInt64("version") + migrator, err := createMigrator(cmd, cfg) + if err != nil { + return err + } + return migrator.UpToContext(context.Background(), version) + }, + } + cmd.Flags().Bool("dry-run", false, "Simulate the migration without applying changes") + cmd.Flags().Int64P("version", "v", 0, "Target version to migrate up to (required)") + cmd.MarkFlagRequired("version") + return cmd +} + +func createDownCommand(cfg Config) *cobra.Command { + cmd := &cobra.Command{ + Use: "down", + Short: "Rollback the last migration", + RunE: func(cmd *cobra.Command, args []string) error { + migrator, err := createMigrator(cmd, cfg) + if err != nil { + return err + } + return migrator.DownContext(context.Background()) + }, + } + cmd.Flags().Bool("dry-run", false, "Simulate the migration without applying changes") + return cmd +} + +func createDownToCommand(cfg Config) *cobra.Command { + cmd := &cobra.Command{ + Use: "down-to", + Short: "Rollback migrations down to a specific version", + RunE: func(cmd *cobra.Command, args []string) error { + version, _ := cmd.Flags().GetInt64("version") + migrator, err := createMigrator(cmd, cfg) + if err != nil { + return err + } + return migrator.DownToContext(context.Background(), version) + }, + } + cmd.Flags().Bool("dry-run", false, "Simulate the migration without applying changes") + cmd.Flags().Int64P("version", "v", 0, "Target version to migrate down to (required)") + cmd.MarkFlagRequired("version") + return cmd +} + +func createResetCommand(cfg Config) *cobra.Command { + cmd := &cobra.Command{ + Use: "reset", + Short: "Rollback all migrations", + RunE: func(cmd *cobra.Command, args []string) error { + migrator, err := createMigrator(cmd, cfg) + if err != nil { + return err + } + return migrator.ResetContext(context.Background()) + }, + } + cmd.Flags().Bool("dry-run", false, "Simulate the migration without applying changes") + return cmd +} + +func createStatusCommand(cfg Config) *cobra.Command { + cmd := &cobra.Command{ + Use: "status", + Short: "Show the status of migrations", + RunE: func(cmd *cobra.Command, args []string) error { + migrator, err := createMigrator(cmd, cfg) + if err != nil { + return err + } + return migrator.StatusContext(context.Background()) + }, + } + return cmd +} + +func createMigrator(cmd *cobra.Command, cfg Config) (*migris.Migrate, error) { + options := []migris.Option{ + migris.WithDB(cfg.DB), + migris.WithMigrationDir(cfg.MigrationsDir), + } + + if dryRun, _ := cmd.Flags().GetBool("dry-run"); dryRun { + options = append(options, migris.WithDryRun(true)) + } + + migrator, err := migris.New(cfg.Dialect, options...) + if err != nil { + return nil, err + } + + return migrator, nil +}