Lets plugins declare icon-pack dependencies (e.g. "tabler", "phosphor") in plugin.mod and PluginRegistration. The CMS loader auto-installs declared packs from the bundled registry before the plugin loads. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
117 lines
3.0 KiB
Go
117 lines
3.0 KiB
Go
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
|
|
}
|