package migrations import ( "fmt" "os" "path/filepath" "sort" "strings" ) const maxSearchDepth = 4 var candidateLeaves = []string{ "migrations", filepath.Join("server", "migrations"), } // ResolveDir returns the first migrations directory that exists from the // current working directory. func ResolveDir() (string, error) { seen := make(map[string]bool) for _, root := range searchRoots() { base := root for range maxSearchDepth + 1 { for _, leaf := range candidateLeaves { dir := filepath.Clean(filepath.Join(base, leaf)) if seen[dir] { continue } seen[dir] = true info, err := os.Stat(dir) if err == nil && info.IsDir() { return dir, nil } } base = filepath.Join(base, "..") } } return "", fmt.Errorf("migrations directory not found") } func searchRoots() []string { roots := []string{"."} if exe, err := os.Executable(); err == nil { roots = append(roots, filepath.Dir(exe)) } return roots } // Files returns sorted migration files for the given direction ("up" or // "down"). func Files(direction string) ([]string, error) { dir, err := ResolveDir() if err != nil { return nil, err } suffix := "." + direction + ".sql" files, err := filepath.Glob(filepath.Join(dir, "*"+suffix)) if err != nil { return nil, err } if direction == "down" { sort.Sort(sort.Reverse(sort.StringSlice(files))) } else { sort.Strings(files) } return files, nil } // LatestVersion returns the latest "up" migration version found on disk. func LatestVersion() (string, error) { files, err := Files("up") if err != nil { return "", err } if len(files) == 0 { return "", fmt.Errorf("no up migrations found") } return ExtractVersion(files[len(files)-1]), nil } // ExtractVersion strips the .up.sql / .down.sql suffix from a migration file. func ExtractVersion(filename string) string { base := filepath.Base(filename) base = strings.TrimSuffix(base, ".up.sql") base = strings.TrimSuffix(base, ".down.sql") return base }