Skip to content

Commit 29e2c63

Browse files
refactor: improve plugin management and configuration handling
Major changes: - Introduce new plugin management system with PluginManager - Refactor runtime and tool configuration handling - Centralize version management for tools and runtimes - Remove hardcoded tool configurations in favor of plugin-based approach - Improve error handling and logging - Add support for dynamic runtime dependencies - Sort tools and runtimes in configuration output The changes make the codebase more maintainable and extensible by: - Reducing code duplication - Improving separation of concerns - Making tool and runtime management more dynamic - Centralizing configuration management
1 parent c7a56ea commit 29e2c63

File tree

26 files changed

+614
-486
lines changed

26 files changed

+614
-486
lines changed

.codacy/codacy.yaml

Lines changed: 7 additions & 7 deletions

.cursor/rules/cursor.mdc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
description:
3-
globs:
4-
alwaysApply: true
3+
globs: *.*
4+
alwaysApply: false
55
---
66

77
# Your rule content

cmd/init.go

Lines changed: 127 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
codacyclient "codacy/cli-v2/codacy-client"
55
"codacy/cli-v2/config"
66
"codacy/cli-v2/domain"
7+
"codacy/cli-v2/plugins"
78
"codacy/cli-v2/tools"
89
"codacy/cli-v2/tools/lizard"
910
"codacy/cli-v2/tools/pylint"
@@ -12,6 +13,7 @@ import (
1213
"log"
1314
"os"
1415
"path/filepath"
16+
"sort"
1517
"strings"
1618

1719
"github.com/spf13/cobra"
@@ -118,46 +120,65 @@ func createConfigurationFiles(tools []domain.Tool, cliLocalMode bool) error {
118120
return nil
119121
}
120122

123+
// Map tool UUIDs to their names
124+
var toolNameMap = map[string]string{
125+
ESLint: "eslint",
126+
Trivy: "trivy",
127+
PyLint: "pylint",
128+
PMD: "pmd",
129+
DartAnalyzer: "dartanalyzer",
130+
Semgrep: "semgrep",
131+
Lizard: "lizard",
132+
}
133+
134+
// RuntimePluginConfig holds the structure of the runtime plugin.yaml file
135+
type RuntimePluginConfig struct {
136+
Name string `yaml:"name"`
137+
Description string `yaml:"description"`
138+
DefaultVersion string `yaml:"default_version"`
139+
}
140+
121141
func configFileTemplate(tools []domain.Tool) string {
122142
// Maps to track which tools are enabled
123143
toolsMap := make(map[string]bool)
124144
toolVersions := make(map[string]string)
125145

126146
// Track needed runtimes
127-
needsNode := false
128-
needsPython := false
129-
needsDart := false
130-
needsJava := false
131-
132-
// Default versions
133-
defaultVersions := map[string]string{
134-
ESLint: "8.57.0",
135-
Trivy: "0.59.1",
136-
PyLint: "3.3.6",
137-
PMD: "6.55.0",
138-
DartAnalyzer: "3.7.2",
139-
Semgrep: "1.78.0",
140-
Lizard: "1.17.19",
141-
}
147+
neededRuntimes := make(map[string]bool)
148+
149+
// Get tool versions from plugin configurations
150+
defaultVersions := plugins.GetToolVersions()
151+
152+
// Get runtime versions all at once
153+
runtimeVersions := plugins.GetRuntimeVersions()
154+
155+
// Get tool runtime dependencies
156+
runtimeDependencies := plugins.GetToolRuntimeDependencies()
142157

143158
// Build map of enabled tools with their versions
144159
for _, tool := range tools {
145160
toolsMap[tool.Uuid] = true
146161
if tool.Version != "" {
147162
toolVersions[tool.Uuid] = tool.Version
148163
} else {
149-
toolVersions[tool.Uuid] = defaultVersions[tool.Uuid]
164+
toolName := toolNameMap[tool.Uuid]
165+
if defaultVersion, ok := defaultVersions[toolName]; ok {
166+
toolVersions[tool.Uuid] = defaultVersion
167+
}
150168
}
151169

152-
// Check if tool needs a runtime
153-
if tool.Uuid == ESLint {
154-
needsNode = true
155-
} else if tool.Uuid == PyLint || tool.Uuid == Lizard {
156-
needsPython = true
157-
} else if tool.Uuid == DartAnalyzer {
158-
needsDart = true
159-
} else if tool.Uuid == PMD {
160-
needsJava = true
170+
// Get the tool's runtime dependency
171+
toolName := toolNameMap[tool.Uuid]
172+
if toolName != "" {
173+
if runtime, ok := runtimeDependencies[toolName]; ok {
174+
// Handle special case for dartanalyzer which can use either dart or flutter
175+
if toolName == "dartanalyzer" {
176+
// For now, default to dart runtime
177+
neededRuntimes["dart"] = true
178+
} else {
179+
neededRuntimes[runtime] = true
180+
}
181+
}
161182
}
162183
}
163184

@@ -167,55 +188,101 @@ func configFileTemplate(tools []domain.Tool) string {
167188

168189
// Only include runtimes needed by the enabled tools
169190
if len(tools) > 0 {
170-
if needsNode {
171-
sb.WriteString(" - [email protected]\n")
191+
// Create a sorted slice of runtimes
192+
var sortedRuntimes []string
193+
for runtime := range neededRuntimes {
194+
sortedRuntimes = append(sortedRuntimes, runtime)
172195
}
173-
if needsPython {
174-
sb.WriteString(" - [email protected]\n")
196+
sort.Strings(sortedRuntimes)
197+
198+
// Write sorted runtimes
199+
for _, runtime := range sortedRuntimes {
200+
sb.WriteString(fmt.Sprintf(" - %s@%s\n", runtime, runtimeVersions[runtime]))
175201
}
176-
if needsDart {
177-
sb.WriteString(" - [email protected]\n")
202+
} else {
203+
// In local mode with no tools specified, include only the necessary runtimes
204+
supportedTools, err := plugins.GetSupportedTools()
205+
if err != nil {
206+
log.Printf("Warning: failed to get supported tools: %v", err)
207+
return sb.String()
178208
}
179-
if needsJava {
180-
sb.WriteString(" - [email protected]\n")
209+
210+
// Get runtimes needed by supported tools
211+
for toolName := range supportedTools {
212+
if runtime, ok := runtimeDependencies[toolName]; ok {
213+
if toolName == "dartanalyzer" {
214+
neededRuntimes["dart"] = true
215+
} else {
216+
neededRuntimes[runtime] = true
217+
}
218+
}
219+
}
220+
221+
// Create a sorted slice of runtimes
222+
var sortedRuntimes []string
223+
for runtime := range neededRuntimes {
224+
sortedRuntimes = append(sortedRuntimes, runtime)
225+
}
226+
sort.Strings(sortedRuntimes)
227+
228+
// Write sorted runtimes
229+
for _, runtime := range sortedRuntimes {
230+
sb.WriteString(fmt.Sprintf(" - %s@%s\n", runtime, runtimeVersions[runtime]))
181231
}
182-
} else {
183-
// In local mode with no tools specified, include all runtimes
184-
sb.WriteString(" - [email protected]\n")
185-
sb.WriteString(" - [email protected]\n")
186-
sb.WriteString(" - [email protected]\n")
187-
sb.WriteString(" - [email protected]\n")
188232
}
189233

190234
sb.WriteString("tools:\n")
191235

192236
// If we have tools from the API (enabled tools), use only those
193237
if len(tools) > 0 {
194-
// Add only the tools that are in the API response (enabled tools)
195-
uuidToName := map[string]string{
196-
ESLint: "eslint",
197-
Trivy: "trivy",
198-
PyLint: "pylint",
199-
PMD: "pmd",
200-
DartAnalyzer: "dartanalyzer",
201-
Semgrep: "semgrep",
202-
Lizard: "lizard",
203-
}
204-
205-
for uuid, name := range uuidToName {
238+
// Create a sorted slice of tool names
239+
var sortedTools []string
240+
for uuid, name := range toolNameMap {
206241
if toolsMap[uuid] {
207-
sb.WriteString(fmt.Sprintf(" - %s@%s\n", name, toolVersions[uuid]))
242+
sortedTools = append(sortedTools, name)
243+
}
244+
}
245+
sort.Strings(sortedTools)
246+
247+
// Write sorted tools
248+
for _, name := range sortedTools {
249+
// Find the UUID for this tool name to get its version
250+
for uuid, toolName := range toolNameMap {
251+
if toolName == name && toolsMap[uuid] {
252+
version := toolVersions[uuid]
253+
sb.WriteString(fmt.Sprintf(" - %s@%s\n", name, version))
254+
break
255+
}
208256
}
209257
}
210258
} else {
211-
// If no tools were specified (local mode), include all defaults
212-
sb.WriteString(fmt.Sprintf(" - eslint@%s\n", defaultVersions[ESLint]))
213-
sb.WriteString(fmt.Sprintf(" - trivy@%s\n", defaultVersions[Trivy]))
214-
sb.WriteString(fmt.Sprintf(" - pylint@%s\n", defaultVersions[PyLint]))
215-
sb.WriteString(fmt.Sprintf(" - pmd@%s\n", defaultVersions[PMD]))
216-
sb.WriteString(fmt.Sprintf(" - dartanalyzer@%s\n", defaultVersions[DartAnalyzer]))
217-
sb.WriteString(fmt.Sprintf(" - semgrep@%s\n", defaultVersions[Semgrep]))
218-
sb.WriteString(fmt.Sprintf(" - lizard@%s\n", defaultVersions[Lizard]))
259+
// If no tools were specified (local mode), include all tools in sorted order
260+
var sortedTools []string
261+
262+
// Get supported tools from plugin system
263+
supportedTools, err := plugins.GetSupportedTools()
264+
if err != nil {
265+
log.Printf("Warning: failed to get supported tools: %v", err)
266+
return sb.String()
267+
}
268+
269+
// Convert map keys to slice and sort them
270+
for toolName := range supportedTools {
271+
if version, ok := defaultVersions[toolName]; ok {
272+
// Skip tools without a version
273+
if version != "" {
274+
sortedTools = append(sortedTools, toolName)
275+
}
276+
}
277+
}
278+
sort.Strings(sortedTools)
279+
280+
// Write sorted tools
281+
for _, toolName := range sortedTools {
282+
if version, ok := defaultVersions[toolName]; ok {
283+
sb.WriteString(fmt.Sprintf(" - %s@%s\n", toolName, version))
284+
}
285+
}
219286
}
220287

221288
return sb.String()

config/runtimes-installer.go

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,8 @@ func InstallRuntimes(config *ConfigType) error {
4747

4848
// InstallRuntime installs a specific runtime
4949
func InstallRuntime(name string, runtimeInfo *plugins.RuntimeInfo) error {
50-
51-
// Check if the runtime is already installed
52-
if isRuntimeInstalled(runtimeInfo) {
50+
// Skip if already installed
51+
if Config.IsRuntimeInstalled(name, runtimeInfo) {
5352
logger.Info("Runtime already installed", logrus.Fields{
5453
"runtime": name,
5554
"version": runtimeInfo.Version,
@@ -65,7 +64,7 @@ func InstallRuntime(name string, runtimeInfo *plugins.RuntimeInfo) error {
6564
}
6665

6766
// Verify that the runtime binaries are available
68-
if !isRuntimeInstalled(runtimeInfo) {
67+
if !Config.IsRuntimeInstalled(name, runtimeInfo) {
6968
logger.Error("Runtime binaries not found after extraction", logrus.Fields{
7069
"runtime": name,
7170
"version": runtimeInfo.Version,
@@ -76,37 +75,6 @@ func InstallRuntime(name string, runtimeInfo *plugins.RuntimeInfo) error {
7675
return nil
7776
}
7877

79-
// isRuntimeInstalled checks if a runtime is already installed by checking for the binary
80-
func isRuntimeInstalled(runtimeInfo *plugins.RuntimeInfo) bool {
81-
// If there are no binaries, check the install directory
82-
if len(runtimeInfo.Binaries) == 0 {
83-
_, err := os.Stat(runtimeInfo.InstallDir)
84-
if err != nil {
85-
logger.Debug("Runtime install directory not found", logrus.Fields{
86-
"directory": runtimeInfo.InstallDir,
87-
"error": err,
88-
})
89-
return false
90-
}
91-
return true
92-
}
93-
94-
// Check if at least one binary exists
95-
for binaryName, binaryPath := range runtimeInfo.Binaries {
96-
_, err := os.Stat(binaryPath)
97-
if err != nil {
98-
logger.Debug("Runtime binary not found", logrus.Fields{
99-
"binary": binaryName,
100-
"path": binaryPath,
101-
"error": err,
102-
})
103-
return false
104-
}
105-
}
106-
107-
return true
108-
}
109-
11078
// downloadAndExtractRuntime downloads and extracts a runtime
11179
func downloadAndExtractRuntime(runtimeInfo *plugins.RuntimeInfo) error {
11280
// Create a file name for the downloaded archive

config/runtimes-installer_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ func TestIsRuntimeInstalled(t *testing.T) {
2424
}
2525

2626
// Test when the install directory doesn't exist
27-
assert.False(t, isRuntimeInstalled(runtimeInfoNoBinaries))
27+
assert.False(t, Config.IsRuntimeInstalled("test-runtime", runtimeInfoNoBinaries))
2828

2929
// Create the install directory
3030
err = os.MkdirAll(runtimeInfoNoBinaries.InstallDir, utils.DefaultDirPerms)
3131
assert.NoError(t, err)
3232

3333
// Test when the install directory exists
34-
assert.True(t, isRuntimeInstalled(runtimeInfoNoBinaries))
34+
assert.True(t, Config.IsRuntimeInstalled("test-runtime", runtimeInfoNoBinaries))
3535

3636
// Create a mock RuntimeInfo with binaries
3737
binPath := filepath.Join(tempDir, "test-runtime-bin")
@@ -45,12 +45,12 @@ func TestIsRuntimeInstalled(t *testing.T) {
4545
}
4646

4747
// Test when the binary doesn't exist
48-
assert.False(t, isRuntimeInstalled(runtimeInfoWithBinaries))
48+
assert.False(t, Config.IsRuntimeInstalled("test-runtime", runtimeInfoWithBinaries))
4949

5050
// Create a mock binary file
5151
_, err = os.Create(binPath)
5252
assert.NoError(t, err)
5353

5454
// Test when the binary exists
55-
assert.True(t, isRuntimeInstalled(runtimeInfoWithBinaries))
55+
assert.True(t, Config.IsRuntimeInstalled("test-runtime", runtimeInfoWithBinaries))
5656
}

config/tools-installer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func InstallTools(config *ConfigType, registry string) error {
5858
// InstallTool installs a specific tool
5959
func InstallTool(name string, toolInfo *plugins.ToolInfo, registry string) error {
6060
// Check if the tool is already installed
61-
if isToolInstalled(toolInfo) {
61+
if Config.IsToolInstalled(name, toolInfo) {
6262
logger.Info("Tool already installed", logrus.Fields{
6363
"tool": name,
6464
"version": toolInfo.Version,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
runtimes:
2+
3+
4+
5+
tools:
6+
7+
8+
9+
10+
11+

0 commit comments

Comments
 (0)