Add version tracking and validation to prevent downgrade issues
Store the last pluginctl version used in plugin.json props and validate before running commands. Prevents issues when using older pluginctl versions on plugins modified by newer versions. - Add Version field to PluginCtlConfig struct - Implement version validation before command execution - Add WritePluginManifest and SavePluginCtlConfig helper functions - Add comprehensive tests for version comparison logic - Skip validation for 'version' command and when no plugin.json exists 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9d3c6b357f
commit
353cc9efc7
5 changed files with 286 additions and 51 deletions
143
plugin.go
143
plugin.go
|
@ -1,143 +0,0 @@
|
|||
package pluginctl
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
const PluginManifestName = "plugin.json"
|
||||
|
||||
// PluginCtlConfig represents the configuration for pluginctl stored in the manifest props.
|
||||
type PluginCtlConfig struct {
|
||||
IgnoreAssets []string `json:"ignore_assets,omitempty"`
|
||||
}
|
||||
|
||||
// LoadPluginManifest loads and parses the plugin.json file from the current directory.
|
||||
func LoadPluginManifest() (*model.Manifest, error) {
|
||||
return LoadPluginManifestFromPath(".")
|
||||
}
|
||||
|
||||
// LoadPluginManifestFromPath loads and parses the plugin.json file from the specified directory.
|
||||
func LoadPluginManifestFromPath(dir string) (*model.Manifest, error) {
|
||||
manifestPath := filepath.Join(dir, PluginManifestName)
|
||||
|
||||
// Check if plugin.json exists
|
||||
if _, err := os.Stat(manifestPath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("plugin.json not found in directory %s", dir)
|
||||
}
|
||||
|
||||
// Read the file
|
||||
data, err := os.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read plugin.json: %w", err)
|
||||
}
|
||||
|
||||
// Parse JSON into Manifest struct
|
||||
var manifest model.Manifest
|
||||
if err := json.Unmarshal(data, &manifest); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse plugin.json: %w", err)
|
||||
}
|
||||
|
||||
return &manifest, nil
|
||||
}
|
||||
|
||||
// HasServerCode checks if the plugin contains server-side code.
|
||||
func HasServerCode(manifest *model.Manifest) bool {
|
||||
return manifest.Server != nil && len(manifest.Server.Executables) > 0
|
||||
}
|
||||
|
||||
// HasWebappCode checks if the plugin contains webapp code.
|
||||
func HasWebappCode(manifest *model.Manifest) bool {
|
||||
return manifest.Webapp != nil && manifest.Webapp.BundlePath != ""
|
||||
}
|
||||
|
||||
// IsValidPluginDirectory checks if the current directory contains a valid plugin.
|
||||
func IsValidPluginDirectory() error {
|
||||
_, err := LoadPluginManifest()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetEffectivePluginPath determines the plugin path from flag, environment variable, or current directory.
|
||||
func GetEffectivePluginPath(flagPath string) string {
|
||||
const EnvPluginPath = "PLUGINCTL_PLUGIN_PATH"
|
||||
|
||||
// Priority: 1. Command line flag, 2. Environment variable, 3. Current directory
|
||||
if flagPath != "" {
|
||||
return flagPath
|
||||
}
|
||||
|
||||
if envPath := os.Getenv(EnvPluginPath); envPath != "" {
|
||||
return envPath
|
||||
}
|
||||
|
||||
// Default to current directory
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "."
|
||||
}
|
||||
|
||||
return cwd
|
||||
}
|
||||
|
||||
// ParsePluginCtlConfig extracts and parses the pluginctl configuration from the manifest props.
|
||||
func ParsePluginCtlConfig(manifest *model.Manifest) (*PluginCtlConfig, error) {
|
||||
// Default configuration
|
||||
config := &PluginCtlConfig{
|
||||
IgnoreAssets: []string{},
|
||||
}
|
||||
|
||||
// Check if props exist
|
||||
if manifest.Props == nil {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// Check if pluginctl config exists in props
|
||||
pluginctlData, exists := manifest.Props["pluginctl"]
|
||||
if !exists {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// Convert to JSON and parse
|
||||
jsonData, err := json.Marshal(pluginctlData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal pluginctl config: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(jsonData, config); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse pluginctl config: %w", err)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
const (
|
||||
HelpFlagLong = "--help"
|
||||
HelpFlagShort = "-h"
|
||||
)
|
||||
|
||||
// CheckForHelpFlag checks if --help is in the arguments and shows help if found.
|
||||
// Returns true if help was shown, false otherwise.
|
||||
func CheckForHelpFlag(args []string, helpText string) bool {
|
||||
for _, arg := range args {
|
||||
if arg == HelpFlagLong || arg == HelpFlagShort {
|
||||
Logger.Info(helpText)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ShowErrorWithHelp displays an error message followed by command help.
|
||||
func ShowErrorWithHelp(errorMsg, helpText string) error {
|
||||
Logger.Error(errorMsg)
|
||||
Logger.Info(helpText)
|
||||
|
||||
return fmt.Errorf("%s", errorMsg)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue