返回 DeepSeek-Reasonix
installed_compat.go
根目录 / internal / pluginpkg / installed_compat.go
1 package pluginpkg
2
3 import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "sort"
8 "strings"
9 )
10
11 func LoadInstalled(reasonixHome string) ([]InstalledPackage, []string) {
12 stateMu.Lock()
13 defer stateMu.Unlock()
14 st, err := LoadState(reasonixHome)
15 if err != nil {
16 return nil, []string{err.Error()}
17 }
18 var out []InstalledPackage
19 var warnings []string
20 stateChanged := false
21 for i := range st.Plugins {
22 installed := st.Plugins[i]
23 if !installed.Enabled {
24 continue
25 }
26 root := ResolveRoot(reasonixHome, installed.Root)
27 pkg, pkgWarnings, err := ParseDir(root)
28 if err != nil && strings.Contains(err.Error(), "missing apiVersion") && managedPluginRoot(reasonixHome, installed, root) {
29 legacy, _, migrateErr := ParseNativeForMigrate(root)
30 if migrateErr == nil {
31 var data []byte
32 data, migrateErr = MigrateManifestToV2(legacy)
33 if migrateErr == nil {
34 migrateErr = WriteMigratedManifestV2(root, data)
35 }
36 }
37 if migrateErr == nil {
38 pkg, pkgWarnings, err = ParseDir(root)
39 if err == nil {
40 warnings = append(warnings, fmt.Sprintf("%s: automatically migrated managed manifest to %s (backup: %s.bak)", installed.Name, ManifestAPIVersionV2, NativeManifest))
41 }
42 } else {
43 err = fmt.Errorf("automatic managed-plugin migration failed: %w", migrateErr)
44 }
45 }
46 if err != nil {
47 reason := err.Error()
48 if st.Plugins[i].Status != PluginStatusDisabledIncompatible || st.Plugins[i].StatusReason != reason {
49 st.Plugins[i].Status = PluginStatusDisabledIncompatible
50 st.Plugins[i].StatusReason = reason
51 stateChanged = true
52 }
53 remediation := "repair or reinstall the plugin"
54 if strings.Contains(reason, "missing apiVersion") {
55 remediation = fmt.Sprintf("reasonix plugin migrate %s --to-v2", installed.Name)
56 }
57 warnings = append(warnings, fmt.Sprintf("%s: %s: %v; remediation: %s", installed.Name, PluginStatusDisabledIncompatible, err, remediation))
58 continue
59 }
60 if st.Plugins[i].Status != "" || st.Plugins[i].StatusReason != "" {
61 st.Plugins[i].Status = ""
62 st.Plugins[i].StatusReason = ""
63 stateChanged = true
64 }
65 out = append(out, InstalledPackage{Installed: installed, Package: pkg, Warnings: pkgWarnings})
66 }
67 if stateChanged {
68 if err := SaveState(reasonixHome, st); err != nil {
69 warnings = append(warnings, "persist plugin compatibility status: "+err.Error())
70 }
71 }
72 sort.SliceStable(out, func(i, j int) bool { return out[i].Installed.Name < out[j].Installed.Name })
73 return out, warnings
74 }
75
76 func managedPluginRoot(reasonixHome string, installed InstalledPlugin, root string) bool {
77 if filepath.IsAbs(strings.TrimSpace(installed.Root)) {
78 return false
79 }
80 managed := filepath.Clean(PluginsDir(reasonixHome))
81 rel, err := filepath.Rel(managed, filepath.Clean(root))
82 if err != nil || rel == "." || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
83 return false
84 }
85 current := managed
86 for part := range strings.SplitSeq(rel, string(filepath.Separator)) {
87 current = filepath.Join(current, part)
88 info, err := os.Lstat(current)
89 if err != nil || info.Mode()&os.ModeSymlink != 0 {
90 return false
91 }
92 }
93 info, err := os.Lstat(filepath.Join(root, NativeManifest))
94 return err == nil && info.Mode()&os.ModeSymlink == 0
95 }
96
96 lines GO