package moose import ( "errors" "fmt" "html/template" "math" "os" "path/filepath" "time" ) const timestampFormat = "20060102150405" type tmplVars struct { Version string CamelName string Package string } const jsonVersionTemplate = "%05v" func CreateWithTemplate(dir string, tmpl *template.Template, name string, migrationType MigrationType, sequential bool) (string, error) { version := time.Now().UTC().Format(timestampFormat) if _, err := os.Stat(dir); os.IsNotExist(err) { if err := os.MkdirAll(dir, os.ModePerm); err != nil { return "", err } } if sequential { // always use osFS here because it's modifying operation migrations, err := collect(osFS{}, dir, math.MinInt64, math.MaxInt64, registeredGoMigrations) if err != nil && !errors.Is(err, ErrNoMigrationFiles) { return "", err } vMigrations, err := Migrations(migrations).versioned(timestampFormat) if err != nil { return "", err } if last, err := vMigrations.Last(); err == nil { version = fmt.Sprintf(jsonVersionTemplate, last.Version+1) } else { version = fmt.Sprintf(jsonVersionTemplate, int64(1)) } } filename := fmt.Sprintf("%v_%v.%v", version, snakeCase(name), migrationType) if tmpl == nil { switch migrationType { case MigrationTypeGo: tmpl = goMigrationTemplate case MigrationTypeJSON: tmpl = jsonMigrationTemplate default: return "", fmt.Errorf("unsupported migration type %q, use go or json", migrationType) } } path := filepath.Join(dir, filename) if _, err := os.Stat(path); !os.IsNotExist(err) { return "", fmt.Errorf("failed to create migration file: %w", err) } f, err := os.Create(path) if err != nil { return "", fmt.Errorf("failed to create migration file: %w", err) } defer func() { _ = f.Close() }() abs, err := filepath.Abs(path) if err != nil { return "", fmt.Errorf("failed to create migration file: %w", err) } vars := tmplVars{ Version: version, CamelName: camelCase(name), Package: filepath.Base(filepath.Dir(abs)), } if err := tmpl.Execute(f, vars); err != nil { return "", fmt.Errorf("failed to execute tmpl: %w", err) } return f.Name(), nil } // Create writes a new blank migration file. func Create(dir, name string, migrationType MigrationType, sequential bool) (string, error) { return CreateWithTemplate(dir, nil, name, migrationType, sequential) } var jsonMigrationTemplate = template.Must(template.New("moose.json-migration").Parse(`{ "up":{ "tx": true, "cmds": [] }, "down": { "tx": true, "cmds": [] } } `)) var goMigrationTemplate = template.Must(template.New("moose.go-migration").Parse(`package {{.Package}} import ( "context" "tangled.org/softprops.tngl.sh/moose" "go.mongodb.org/mongo-driver/v2/mongo" ) func init() { moose.AddMigration(up{{.CamelName}}, down{{.CamelName}}) } func up{{.CamelName}}(ctx context.Context, client *mongo.Database) error { // This code is executed when the migration is applied. return nil } func down{{.CamelName}}(ctx context.Context, client *mongo.Database) error { // This code is executed when the migration is rolled back. return nil } `))