package plugin import ( "bufio" "bytes" "fmt" "strconv" "strings" "golang.org/x/mod/semver" ) // ParseModVersion extracts the version string from an embedded plugin.mod file. // Returns "0.0.0" if parsing fails or version is not found. func ParseModVersion(data []byte) string { scanner := bufio.NewScanner(bytes.NewReader(data)) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if strings.HasPrefix(line, "version") { parts := strings.SplitN(line, "=", 2) if len(parts) != 2 { continue } val := strings.TrimSpace(parts[1]) val = strings.Trim(val, `"`) if val != "" { return val } } } return "0.0.0" } // ParseRequiredIconPacks extracts the required_icon_packs list from an embedded // plugin.mod file. Returns nil if the field is absent or empty. Mirrors the // "read straight from TOML bytes" style of ParseModVersion so plugin // registration.go files can populate PluginRegistration.RequiredIconPacks // without having to duplicate the slugs in Go. func ParseRequiredIconPacks(data []byte) []string { m, err := ParseModFull(data) if err != nil || m == nil { return nil } if len(m.Plugin.RequiredIconPacks) == 0 { return nil } out := make([]string, 0, len(m.Plugin.RequiredIconPacks)) for _, slug := range m.Plugin.RequiredIconPacks { s := strings.TrimSpace(slug) if s == "" { continue } out = append(out, s) } if len(out) == 0 { return nil } return out } // CompareVersions compares two semver strings. // Returns -1 if v1 < v2, 0 if equal, +1 if v1 > v2. // Returns 0 if either version is invalid. func CompareVersions(v1, v2 string) int { if !strings.HasPrefix(v1, "v") { v1 = "v" + v1 } if !strings.HasPrefix(v2, "v") { v2 = "v" + v2 } if !semver.IsValid(v1) || !semver.IsValid(v2) { return 0 } return semver.Compare(v1, v2) } // ParseBaseSemver parses a plain MAJOR.MINOR.PATCH version into integers. // Rejects pre-release suffixes and build metadata. func ParseBaseSemver(s string) (major, minor, patch int, err error) { parts := strings.Split(s, ".") if len(parts) != 3 { return 0, 0, 0, fmt.Errorf("invalid version %q: expected MAJOR.MINOR.PATCH", s) } out := [3]int{} for i, p := range parts { n, perr := strconv.Atoi(p) if perr != nil || n < 0 { return 0, 0, 0, fmt.Errorf("invalid version %q: each part must be a non-negative integer", s) } out[i] = n } return out[0], out[1], out[2], nil } // BumpVersion returns current bumped at the given level ("major", "minor", or "patch"). // Bumping major resets minor and patch to 0; bumping minor resets patch to 0. func BumpVersion(current, level string) (string, error) { major, minor, patch, err := ParseBaseSemver(current) if err != nil { return "", err } switch level { case "major": major++ minor = 0 patch = 0 case "minor": minor++ patch = 0 case "patch": patch++ default: return "", fmt.Errorf("unknown bump level %q (want major|minor|patch)", level) } return fmt.Sprintf("%d.%d.%d", major, minor, patch), nil }