105 lines
2.6 KiB
Go
105 lines
2.6 KiB
Go
package service
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
pluginv1 "github.com/Wei-Shaw/sub2api/pkg/pluginapi/v1"
|
|
"golang.org/x/mod/semver"
|
|
)
|
|
|
|
type PluginHostInfo struct {
|
|
Version string
|
|
BuildType string
|
|
}
|
|
|
|
func EvaluatePluginCompatibility(manifest PluginManifest, host PluginHostInfo) PluginCompatibility {
|
|
result := PluginCompatibility{
|
|
CurrentSub2API: host.Version,
|
|
RequiredSub2API: manifest.Requires.Sub2API,
|
|
RecommendedSub2API: manifest.Requires.RecommendedSub2APIVersion,
|
|
PluginProtocol: manifest.Requires.PluginProtocol,
|
|
TransportAPI: manifest.Requires.TransportAPI,
|
|
UIBridge: manifest.Requires.UIBridge,
|
|
}
|
|
if manifest.Requires.PluginProtocol != pluginv1.ProtocolVersion ||
|
|
manifest.Requires.TransportAPI != pluginv1.TransportAPIVersion ||
|
|
manifest.Requires.UIBridge != pluginv1.UIBridgeVersion {
|
|
result.Status = "incompatible"
|
|
result.Message = "插件协议版本与当前 Sub2API 不兼容"
|
|
return result
|
|
}
|
|
if !matchesSemverRange(host.Version, manifest.Requires.Sub2API) {
|
|
result.Status = "incompatible"
|
|
result.Message = fmt.Sprintf("当前 Sub2API %s 不满足插件要求 %s", host.Version, manifest.Requires.Sub2API)
|
|
return result
|
|
}
|
|
result.Compatible = true
|
|
for _, tested := range manifest.Requires.TestedSub2APIVersions {
|
|
if normalizeSemver(tested) == normalizeSemver(host.Version) {
|
|
result.Tested = true
|
|
break
|
|
}
|
|
}
|
|
if result.Tested {
|
|
result.Status = "compatible"
|
|
result.Message = "当前 Sub2API 版本已由插件声明测试"
|
|
} else {
|
|
result.Status = "untested"
|
|
result.Message = "版本范围兼容,但插件未声明已测试当前 Sub2API 版本"
|
|
}
|
|
return result
|
|
}
|
|
|
|
func normalizeSemver(version string) string {
|
|
v := strings.TrimSpace(version)
|
|
if v == "" {
|
|
return ""
|
|
}
|
|
if !strings.HasPrefix(v, "v") {
|
|
v = "v" + v
|
|
}
|
|
if !semver.IsValid(v) {
|
|
return ""
|
|
}
|
|
return v
|
|
}
|
|
|
|
func matchesSemverRange(version, expression string) bool {
|
|
v := normalizeSemver(version)
|
|
if v == "" {
|
|
return false
|
|
}
|
|
tokens := strings.Fields(strings.ReplaceAll(expression, ",", " "))
|
|
if len(tokens) == 0 {
|
|
return false
|
|
}
|
|
for _, token := range tokens {
|
|
op := "="
|
|
raw := token
|
|
for _, candidate := range []string{">=", "<=", ">", "<", "="} {
|
|
if strings.HasPrefix(token, candidate) {
|
|
op = candidate
|
|
raw = strings.TrimSpace(strings.TrimPrefix(token, candidate))
|
|
break
|
|
}
|
|
}
|
|
bound := normalizeSemver(raw)
|
|
if bound == "" {
|
|
return false
|
|
}
|
|
comparison := semver.Compare(v, bound)
|
|
matched := map[string]bool{
|
|
">=": comparison >= 0,
|
|
"<=": comparison <= 0,
|
|
">": comparison > 0,
|
|
"<": comparison < 0,
|
|
"=": comparison == 0,
|
|
}[op]
|
|
if !matched {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|